File Coverage

blib/lib/Crypt/OdinAuth.pm
Criterion Covered Total %
statement 48 49 97.9
branch 10 12 83.3
condition n/a
subroutine 11 11 100.0
pod 3 3 100.0
total 72 75 96.0


line stmt bran cond sub pod time code
1             package Crypt::OdinAuth;
2              
3 2     2   57224 use 5.006;
  2         8  
  2         82  
4 2     2   12 use strict;
  2         3  
  2         63  
5 2     2   10 use warnings;
  2         15  
  2         91  
6              
7             =head1 NAME
8              
9             Crypt::OdinAuth - Cryptographic calculations for the OdinAuth SSO system
10              
11             =head1 VERSION
12              
13             Version 0.2
14              
15             =cut
16              
17             our $VERSION = '0.2';
18              
19 2     2   1238385 use Digest;
  2         9042  
  2         64  
20 2     2   1666 use Digest::HMAC;
  2         1076  
  2         89  
21 2     2   1484 use Digest::SHA256;
  2         7493  
  2         118  
22 2     2   751 use MIME::Base64 qw(encode_base64url decode_base64url);
  2         804  
  2         144  
23              
24 2     2   13 use constant OLD_COOKIE => 24*60*60; # cookie older than 24h is discarded
  2         5  
  2         1430  
25              
26             =head1 SYNOPSIS
27              
28             This module exports functions for calculating and verifying signed
29             cookies for OdinAuth SSO Apache handler.
30              
31             use Crypt::OdinAuth;
32              
33             Crypt::OdinAuth::hmac_for('secret', 'login_name', 'role1,role2,role3', 1337357387, 'netcat')
34             #=> '349b7135f43bd4c0111564960e7d9d583dde0c5c'
35              
36             Crypt::OdinAuth::cookie_for('secret', 'login_name', 'role1,role2,role3', 'netcat')
37             #=> 'login_name-role1,role2,role3-1337357638-7ec415a6816c8e9dab7b788e1262769ef80af7d8'
38              
39             =head1 SUBROUTINES
40              
41             =head2 hmac_for(secret, user, roles, timestamp, user_agent)
42              
43             =cut
44             sub hmac_for ($$$$$) {
45 16     16 1 1072 my ( $secret, $user, $roles, $ts, $ua ) = @_;
46 16         63 my $hmac = Digest::HMAC->new($secret, Digest->new("SHA-256"));
47              
48 16 50       862722 if ($ua =~ /AppleWebKit/) {
49 0         0 $ua = "StupidAppleWebkitHacksGRRR";
50             }
51 16         28 $ua =~ s/ FirePHP\/\d+\.\d+//;
52              
53 16         52 $hmac->add(join(',',
54             encode_base64url($user),
55             encode_base64url($roles),
56             $ts,
57             encode_base64url($ua)));
58 16         489 return $hmac->hexdigest;
59             }
60              
61             =head2 cookie_for(secret, user, roles, user_agent)
62              
63             =cut
64              
65             sub cookie_for {
66 8     8 1 4848 my ( $secret, $user, $roles, $ua, $ts ) = @_;
67 8 100       27 $ts = time() unless defined($ts);
68              
69 8         19 my $hmac = hmac_for($secret, $user, $roles, $ts, $ua);
70 8         226 return join(',',
71             encode_base64url($user),
72             encode_base64url($roles),
73             $ts,
74             $hmac);
75             }
76              
77             =head2 check_cookie(secret, cookie, user_agent)
78              
79             =cut
80              
81             sub check_cookie ($$$) {
82 6     6 1 176 my ( $secret, $cookie, $ua ) = @_;
83              
84 6 50       37 $cookie =~ /^\s*([a-zA-Z0-9_-]+),([a-zA-Z0-9_-]+),(\d+),([0-9a-f]+)\s*$/
85             or die "Wrong cookie format\n";
86              
87 6         17 my $user = decode_base64url($1);
88 6         74 my $roles = decode_base64url($2);
89 6         50 my $ts = $3;
90 6         13 my $hmac = $4;
91              
92             # Use double hmac to prevent timing attacks
93             # http://codahale.com/a-lesson-in-timing-attacks/
94             # http://www.isecpartners.com/blog/2011/2/18/double-hmac-verification.html
95 6         20 my $hmac_received = Digest::HMAC->new($secret, Digest->new("SHA-256"));
96 6         317 my $hmac_calculated = Digest::HMAC->new($secret, Digest->new("SHA-256"));
97              
98 6         319 $hmac_received->add($hmac);
99 6         40 $hmac_calculated->add(hmac_for($secret, $user, $roles, $ts, $ua));
100              
101 6 100       206 die "Invalid signature\n"
102             if ( $hmac_received->digest ne $hmac_calculated->digest );
103              
104 4 100       239 die "Cookie is old\n"
105             if ( $ts < time() - OLD_COOKIE );
106              
107 3 100       15 die "Cookie is in future\n"
108             if ( $ts > time() + 5*60 );
109              
110 2         12 return $user, $roles;
111             }
112              
113             =head1 AUTHOR
114              
115             Maciej Pasternacki, C<< >>
116              
117             =head1 BUGS
118              
119             Please report any bugs or feature requests to C, or through
120             the web interface at L. I will be notified, and then you'll
121             automatically be notified of progress on your bug as I make changes.
122              
123              
124              
125              
126             =head1 SUPPORT
127              
128             You can find documentation for this module with the perldoc command.
129              
130             perldoc Crypt::OdinAuth
131              
132              
133             You can also look for information at:
134              
135             =over 4
136              
137             =item * RT: CPAN's request tracker (report bugs here)
138              
139             L
140              
141             =item * AnnoCPAN: Annotated CPAN documentation
142              
143             L
144              
145             =item * CPAN Ratings
146              
147             L
148              
149             =item * Search CPAN
150              
151             L
152              
153             =back
154              
155              
156             =head1 ACKNOWLEDGEMENTS
157              
158              
159             =head1 LICENSE AND COPYRIGHT
160              
161             Copyright 2012 Maciej Pasternacki.
162              
163             This program is free software; you can redistribute it and/or modify it
164             under the terms of either: the GNU General Public License as published
165             by the Free Software Foundation; or the Artistic License.
166              
167             See http://dev.perl.org/licenses/ for more information.
168              
169              
170             =cut
171              
172             1; # End of Crypt::OdinAuth