File Coverage

blib/lib/Authen/ModAuthPubTkt.pm
Criterion Covered Total %
statement 69 77 89.6
branch 31 54 57.4
condition 10 20 50.0
subroutine 8 9 88.8
pod 3 3 100.0
total 121 163 74.2


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;