line
stmt
bran
cond
sub
pod
time
code
1
package Authen::ModAuthPubTkt;
2
require Exporter;
3
our @ISA=qw(Exporter);
4
our @EXPORT = qw/pubtkt_generate
5
pubtkt_verify
6
pubtkt_parse/;
7
8
7
7
212460
use strict;
7
19
7
292
9
7
7
42
use warnings;
7
16
7
215
10
7
7
43
use Carp;
7
16
7
902
11
7
7
7464
use MIME::Base64;
7
6411
7
503
12
7
7
9753
use File::Temp qw/tempfile/;
7
220311
7
530
13
7
7
8575
use IPC::Run3;
7
114732
7
8358
14
15
16
# ABSTRACT: A Module to generate Mod-Auth-PubTkt compatible Cookies
17
18
=pod
19
20
=head1 NAME
21
22
Authen::ModAuthPubTkt - Generate Tickets (Signed HTTP Cookies) for mod_auth_pubtkt protected websites.
23
24
=head1 VERSION
25
26
version 0.1.1
27
28
=cut
29
our $VERSION = '0.1.1';
30
31
=pod
32
33
=head1 SYNOPSIS
34
35
On the command-line, generate the public + private keys:
36
(More details available at L)
37
38
$ openssl genrsa -out key.priv.pem 1024
39
$ openssl rsa -in key.priv.pem -out key.pub.pem -pubout
40
41
42
Then in your perl script (which is probably the your custom login website), use the following code to issue tickets:
43
44
use Authen::ModAuthPubTkt;
45
46
my $ticket = pubtkt_generate(
47
privatekey => "key.priv.pem",
48
keytype => "rsa",
49
clientip => undef, # or a valid IP address
50
userid => "102", # or any ID that makes sense to your application, e.g. email
51
validuntil => time() + 86400, # valid for one day
52
graceperiod=> 3600, # grace period of an hour
53
tokens => undef, # comma separated string of tokens.
54
userdata => undef # any application specific data to pass.
55
);
56
57
## $ticket string will look something like:
58
## "uid=102;validuntil=1337899939;graceperiod=1337896339;tokens=;udata=;sig=h5qR" \
59
## "yZZDl8PfW8wNxPYkcOMlAxtWuEyU5bNAwEFT9lztN3I7V13SaGOHl+U6wB+aMkvvLQiaAfD2xF/Hl" \
60
## "+QmLDEvpywp98+5nRS+GeihXTvEMRaA4YVyxb4NnZujCZgX8IBhP6XBlw3s7180jxE9I8DoDV8bDV" \
61
## "k/2em7yMEzLns="
62
63
64
To verify a ticket, use the following code:
65
66
my $ok = pubtkt_verify (
67
publickey => "key.pub.pem",
68
keytype => "rsa",
69
ticket => $ticket
70
);
71
die "Ticket verification failed.\n" if not $ok;
72
73
To extract items from a ticket, use the following code:
74
75
my %items = pubtkt_parse($ticket);
76
77
## %items will be something like:
78
## {
79
## 'uid' => 102,
80
## 'validuntil' => 1337899939,
81
## 'graceperiod => 1337896339,
82
## 'tokens' => "",
83
## 'udata' => "",
84
## 'sig' => 'h5qRyZZDl8PfW8wNxPYkcOMlAxtWuEyU5bNAwEFT9lztN3 (....)'
85
## }
86
87
88
Also, a command-line utility (C) will be installed, and can be used to generate/verify keys:
89
90
$ mod_auth_pubtkt.pl --generate --private-key key.priv.pem --rsa
91
$ mod_auth_pubtkt.pl --verify --public-key key.pub.pem --rsa
92
$ mod_autH_pubtkt.pl --help
93
94
95
=head1 DESCRIPTION
96
97
This module generates and verify a mod_auth_pubtkt-compatible ticket string, which should be used
98
as a cookie with the rest of the B ( L ) system.
99
100
=head3 Common scenario:
101
102
=over 2
103
104
=item 1.
105
On the login server side, write perl code to authenticate users (using Apache's authenetication, LDAP, DB, etc.).
106
107
=item 2.
108
Once the user is authenticated, call C to generate a ticket, and send it back to the user as a cookie.
109
110
=item 3.
111
Redirect the user back to the server he/she came from.
112
113
=back
114
115
116
=head1 Working Example
117
118
A working (but minimal) perl login example is available at L
119
120
=cut
121
122
123
## On unix, assume it's on the $PATH.
124
## On Windows - you're on your own.
125
## TODO: make this user-configurable.
126
my $openssl_bin = "openssl";
127
128
=pod
129
130
=head1 METHODS
131
132
=head2 pubtkt_generate
133
134
Generates a signed ticket.
135
136
If successful, returns a signed ticket string (to be sent back to the user as a cookie).
137
138
On any failure (bad key, failure to run C, etc.) returns C.
139
140
Accepts a hash of parameters:
141
142
=over 4
143
144
=item B
145
146
String containing the private key filename (full path). The key can be either DSA or RSA key (see B).
147
148
=item B
149
150
either "rsa" or "dsa" - depending on how you created the private/public key files.
151
152
=item B
153
154
String containing the user ID. No specific format is enforced: can by a number, a string, an email address, etc. It will be encoded as "uid=XXXX" in the signed ticket.
155
156
=item B
157
158
Numeric value, containing the validity period, in seconds since epoch (use C function).
159
160
=item B
161
162
Optional. Numeric value. If given, will be added to the signed ticket string.
163
164
=item B
165
166
Optional. A string with an IP address. If given. will be added to the signed ticket string.
167
168
=item B
169
170
Optional. Any textual string. If given. will be added to the signed ticket string.
171
172
=item B
173
174
Optional. Any textual string. If given. will be added to the signed ticket string.
175
176
=back
177
178
=cut
179
sub pubtkt_generate
180
{
181
6
6
1
4323
my %args = @_;
182
6
50
41
my $private_key_file = $args{privatekey} or croak "Missing \"privatekey\" parameter";
183
6
50
218
croak "Invalid \"privatekey\" value ($private_key_file): file doesn't exist/not readable"
184
unless -r $private_key_file;
185
186
6
50
44
my $keytype = $args{keytype} or croak "Missing \"keytype\" parameter";
187
6
50
66
60
croak "Invalid \"keytype\" value ($keytype): expecting 'dsa' or 'rsa'\n"
188
unless $keytype eq "dsa" || $keytype eq "rsa";
189
190
6
50
35
my $user_id = $args{userid} or croak "Missing \"userid\" parameter";
191
192
6
50
32
my $valid_until = $args{validuntil} or croak "Missing \"validuntil\" parameter";
193
6
50
51
croak "Invalid \"validuntil\" value ($valid_until), expecting a numeric value."
194
unless $valid_until =~ /^\d+$/;
195
196
6
50
28
my $grace_period = $args{graceperiod} || "";
197
6
50
33
62
croak "Invalid \"graceperiod\" value ($grace_period), expecting a numeric value."
198
unless $grace_period eq "" || $grace_period =~ /^\d+$/;
199
200
6
50
40
my $client_ip = $args{clientip} || "";
201
##TODO: better IP address validation
202
6
50
33
67
croak "Invalid \"client_ip\" value ($client_ip), expecting a valid IP address."
203
unless $client_ip eq "" || $client_ip =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
204
205
6
50
40
my $tokens = $args{token} || "";
206
6
50
54
my $user_data = $args{userdata} || "";
207
208
# Generate Ticket String
209
6
21
my $tkt = "uid=$user_id;" ;
210
6
50
33
$tkt .= "cip=$client_ip;" if $client_ip;
211
6
23
$tkt .= "validuntil=$valid_until;";
212
6
50
35
$tkt .= "graceperiod=" . ($valid_until - $grace_period) . ";" if $grace_period;
213
6
21
$tkt .= "tokens=$tokens;";
214
6
15
$tkt .= "udata=$user_data";
215
216
6
100
29
my $algorithm_param = ( $keytype eq "dsa" ) ? "-dss1" : "-sha1";
217
218
6
42
my @cmd = ( $openssl_bin,
219
"dgst", $algorithm_param,
220
"-binary",
221
"-sign", $private_key_file ) ;
222
223
6
13
my ($stdin, $stdout, $stderr);
224
225
6
14
$stdin = $tkt;
226
6
63
run3 \@cmd, \$stdin, \$stdout, \$stderr;
227
6
134206
my $exitcode = $?;
228
229
6
50
1112
if ($exitcode != 0) {
230
0
0
warn "pubtkt_generate failed: openssl returned exit code $exitcode, stderr = $stderr\n";
231
0
0
return;
232
}
233
234
6
178
$tkt .= ";sig=" . encode_base64($stdout,""); #2nd param = no EOL.
235
236
6
424
return $tkt;
237
}
238
239
=head2 pubtkt_verify
240
241
Verifies a signed ticket string.
242
243
If successful (i.e. the ticket's signature is valid), returns TRUE (=1).
244
245
On any failure (bad key, failure to run C, etc.) returns C.
246
247
B: B That is: The function will return TRUE if the ticket is properly signed, but possibly expired.
248
249
Accepts a hash of parameters:
250
251
=over 4
252
253
=item B
254
255
String containing the public key filename (full path). The key can be either DSA or RSA key (see B).
256
257
=item B
258
259
either "rsa" or "dsa" - depending on how you created the private/public key files.
260
261
=item B
262
263
The string of the ticket (such as returned by C).
264
265
=back
266
267
=cut
268
sub pubtkt_verify
269
{
270
204
204
1
272505
my %args = @_;
271
204
50
1372
my $public_key_file = $args{publickey} or croak "Missing \"publickey\" parameter";
272
204
50
9345
croak "Invalid \"publickey\" value ($public_key_file): file doesn't exist/not readable"
273
unless -r $public_key_file;
274
275
204
50
1537
my $keytype = $args{keytype} or croak "Missing \"keytype\" parameter";
276
204
50
66
1814
croak "Invalid \"keytype\" value ($keytype): expecting 'dsa' or 'rsa'\n"
277
unless $keytype eq "dsa" || $keytype eq "rsa";
278
204
100
1188
my $algorithm_param = ( $keytype eq "dsa" ) ? "-dss1" : "-sha1";
279
280
204
50
1058
my $ticket_str = $args{ticket} or croak "Missing \"ticket\" parameter";
281
282
# Extract base64'd signature text
283
204
2464
my ($ticket_data, $sig_base64) = split /;sig=/, $ticket_str;
284
204
100
1358
warn "Pubtkt.pm: missing \"sig=\" in ticket ($ticket_str)" unless $sig_base64;
285
204
100
685
return unless $sig_base64;
286
287
# Decode base64 signature, and store in a temporary file
288
197
1957
my $sig_bin = decode_base64($sig_base64);
289
197
50
1015
warn "Pubtkt.pm: invalid base64 signature from ticket ($ticket_str)" unless length($sig_bin)>0;
290
291
197
4587
my ($fh, $temp_sig_file) = tempfile("pubtkt.XXXXXXXXX", UNLINK=>1, TMPDIR=>1);
292
197
50
326044
print $fh $sig_bin or die "Failed to write signature data: $!";
293
197
50
22850
close $fh or die "Failed to write signature data: $!";
294
295
# verify signature using openssl
296
197
1514
my @cmd = ( $openssl_bin,
297
"dgst", $algorithm_param,
298
"-verify", $public_key_file,
299
"-signature", $temp_sig_file);
300
197
633
my ($stdin, $stdout, $stderr);
301
197
464
$stdin = $ticket_data;
302
197
2280
run3 \@cmd, \$stdin, \$stdout, \$stderr;
303
197
6280697
my $exitcode = $?;
304
197
100
9631
return unless $exitcode == 0;
305
306
3
50
114
return 1 if ( $stdout eq "Verified OK\n" ) ;
307
308
0
return ;
309
}
310
311
=head2 pubtkt_parse($ticket)
312
313
Utility function to parse a ticket string into a Perl hash.
314
315
B: No validation is performed. The given ticket might be expired, or even forged.
316
317
=cut
318
sub pubtkt_parse
319
{
320
0
0
0
1
my $tkt = shift or croak "missing ticket string parameter";
321
0
my @fields = split /;/, $tkt;
322
0
my %values = map { split (/=/, $_, 2) } @fields;
0
323
0
return %values;
324
}
325
326
=head1 PREREQUISITES
327
328
B must be installed (and available on the $PATH).
329
330
L is required to run the openssl executables.
331
332
=head1 BUGS
333
334
Probably many.
335
336
=head1 TODO
337
338
Use Perl's L and L instead of the running C executable.
339
340
Don't assume C binary is on the $PATH.
341
342
Refactor into OO interface.
343
344
=head1 LICENSE
345
346
Copyright (C) 2012 A. Gordon ( gordon at cshl dot edu ).
347
348
Apache License, same as the rest of B
349
350
=head1 AUTHORS
351
352
A. Gordon, heavily based on the PHP code from B.
353
354
=head1 SEE ALSO
355
356
ModAuthPubTkt main website: L
357
358
ModAuthPubTkt github repository: L
359
360
This module's github repository: L
361
362
Examples in the C<./eg> directory:
363
364
=over 4
365
366
=item B
367
368
Generates a pair of RSA key files.
369
370
=item B
371
372
Generates a pair of DSA key files.
373
374
=item B
375
376
A command-line utility to generate/verify tickets.
377
378
=back
379
380
=cut
381
382
1;