File Coverage

blib/lib/Net/OAuth2/Scheme/HmacUtil.pm
Criterion Covered Total %
statement 28 101 27.7
branch 0 26 0.0
condition 0 14 0.0
subroutine 10 21 47.6
pod 0 8 0.0
total 38 170 22.3


line stmt bran cond sub pod time code
1 2     2   7 use warnings;
  2         3  
  2         60  
2 2     2   8 use strict;
  2         2  
  2         69  
3              
4             package Net::OAuth2::Scheme::HmacUtil;
5             BEGIN {
6 2     2   23 $Net::OAuth2::Scheme::HmacUtil::VERSION = '0.020002_099';
7             }
8             # ABSTRACT: hash functions and encodings
9              
10 2     2   918 use Digest::SHA ();
  2         5098  
  2         50  
11             # use MIME::Base64 qw(encode_base64 decode_base64);
12              
13 2     2   11 use parent qw(Exporter);
  2         2  
  2         11  
14             our @EXPORT_OK = qw(
15             hmac_name_to_len_fn
16             sign_binary unsign_binary
17             encode_base64url decode_base64url
18             encode_plainstring decode_plainstring
19             timing_indep_eq
20             );
21              
22             our @Known_HMACs =
23             # list of [id, key_length, underscored_name, dashed-name, hmac_function]
24             (
25             # NIST's HMAC-SHA functions
26             map {[$_->[0], $_->[1], "hmac_sha$_->[2]", "hmac-sha$_->[2]"]}
27             map {[$_%107, $_/8, ($_ == 160 ? 1 : $_)]}
28             160,224,256,384,512,
29             # add more keylengths here as NIST adds new ones
30             ),
31             (
32             # more families
33             );
34              
35             # Why 107?
36             # Short answer:
37             # Why not? Really all that matters is that no id get used twice
38             # and whatever we do to achieve that, nobody should care.
39             # Long answer:
40             # 107 is THE prime smaller than 128 for which the sequence 2^n mod p
41             # whose subsequences starting with 8 (= 256/32) and 12 (= 384/32)
42             # that do not contain either each other or 5 (=160/32) or 7 (=224/32)
43             # are of maximal length. The idea being that we can keep adding new
44             # SHA functions of lengths 256*2^n, 384*2^n and not run into
45             # previously used ids for a VERY long time...
46             # OR (more likely) we'll be able to intersperse other families of
47             # secure hash functions (i.e., once SHA turns out to be inadequate
48             # for whatever reason) and likewise have plenty of room for those to
49             # grow, too, assuming those, too, start with key lengths of 256 and
50             # 384. E.g., for the next family, you could do
51             #
52             # map {[($_*13)%107, $_/8, "hmac_xxx$_", "hmac-xxx$_", \&whatever]}
53             #
54             # Note that all internal id numbers will thus be 106 or smaller, so
55             # if all else fails you can uses id bytes with the high-bit set to
56             # indicate some jackass extension scheme, though, hopefully, by that
57             # point we will have burned through so many families of secure hash
58             # functions that I will be safely dead and won't care anymore.
59             # Actually, I already don't care, so... --rfc
60              
61              
62             our %Known_HMACs_by_name = ( map {$_->[2],$_,$_->[3],$_} @Known_HMACs );
63             our %Known_HMACs_by_id = ( map {$_->[0],$_} @Known_HMACs );
64              
65             die "looks like we used an id number twice"
66             if 2 * keys %Known_HMACs_by_id != keys %Known_HMACs_by_name;
67              
68             our $Default_HMAC = 'hmac_sha256';
69              
70             sub hmac_name_to_len_fn {
71 0     0 0   my ($aname) = @_;
72 0 0         my $a = $Known_HMACs_by_name{$aname} or return ();
73 0           return ($a->[1], _hmac_fn($a));
74             }
75              
76             sub _hmac_fn {
77 0     0     my $a = shift;
78 0   0       return ($a->[4] ||= \&{"Digest::SHA::$a->[2]"});
  0            
79             }
80              
81             sub _hmac_name_to_id_fn {
82 0     0     my ($aname) = @_;
83 0 0         my $a = $Known_HMACs_by_name{$aname} or
84             Carp::croak("unknown hmac function: $aname");
85 0           return ($a->[0], _hmac_fn($a));
86             }
87              
88             sub _hmac_id_to_len_fn {
89 0     0     my ($id) = @_;
90 0 0         my $a = $Known_HMACs_by_id{$id} or return ();
91 0           return ($a->[1], _hmac_fn($a));
92             }
93              
94             sub timing_indep_eq {
95 2     2   702 no warnings 'uninitialized';
  2         7  
  2         1071  
96 0     0 0   my ($x, $y, $len)=@_;
97 0 0 0       warnings::warn('uninitialized','Use of uninitialized value in timing_indep_eq')
      0        
98             if (warnings::enabled('uninitialized') && !(defined($x) && defined($y)));
99              
100 0           my $result=0;
101 0           for (my $i=0; $i<$len; $i++) {
102 0           $result |= ord(substr($x, $i, 1)) ^ ord(substr($y, $i, 1));
103             }
104              
105 0           return !$result;
106             }
107              
108             sub sign_binary {
109 0     0 0   my ($secret, $value, %o) = @_;
110 0   0       my $aname = $o{hmac} || $Default_HMAC;
111 0           my ($id, $fn) = _hmac_name_to_id_fn($aname);
112 0           my $extra = $o{extra};
113 0 0         $extra = '' unless defined $extra;
114 0           return pack 'ww/a*a*', $id, $fn->($secret, $value . $extra), $value;
115             }
116              
117             sub unsign_binary {
118 0     0 0   my ($secret, $bin, $extra) = @_;
119 0           my ($id, $hash, $value) = unpack 'ww/a*a*', $bin;
120 0 0         my ($keylen, $fn) = _hmac_id_to_len_fn($id) or
121             return (undef, "unknown hash function id: $id");
122 0 0         $extra = '' unless defined $extra;
123 0 0 0       return ($value)
124             if length($hash) == $keylen &&
125             timing_indep_eq($hash, $fn->($secret, $value . $extra), $keylen);
126             # implement extensions here but for now, just fail
127 0           return (undef, 'bad hash value');
128             }
129              
130             # base64url is described in RFC 4648: use - and _ in place of + and /
131             # and we leave off trailing =s, all so as not to use characters that
132             # are meaningful in URLs
133              
134             sub encode_base64url {
135 0     0 0   local $_ = join '' , map {pack 'B6',$_} ((unpack 'B*',shift).'0000') =~ m/(.{6})/gs;
  0            
136 0           y(\0\4\10\14\20\24\30\34\40\44\50\54\60\64\70\74\100\104\110\114\120\124\130\134\140\144\150\154\160\164\170\174\200\204\210\214\220\224\230\234\240\244\250\254\260\264\270\274\300\304\310\314\320\324\330\334\340\344\350\354\360\364\370\374)(A-Za-z0-9\-_);
137             # local $_ = encode_base64(shift,'');
138             # y|+/=|-_|d;
139 0           return $_;
140             }
141              
142             sub decode_base64url {
143 0     0 0   local $_ = shift;
144 0           y(A-Za-z0-9\-_)(\0\4\10\14\20\24\30\34\40\44\50\54\60\64\70\74\100\104\110\114\120\124\130\134\140\144\150\154\160\164\170\174\200\204\210\214\220\224\230\234\240\244\250\254\260\264\270\274\300\304\310\314\320\324\330\334\340\344\350\354\360\364\370\374)d;
145 0           return pack 'B'. (((3*length)>>2)<<3) , join '', unpack 'B6'x(length), $_;
146              
147             # # for some reason this is way faster than:
148             # y|-_=|+/|d;
149             # return decode_base64($_ . substr('===',(3+length)>>2))
150             }
151              
152             # plainstring is printable ascii excluding whitespace, backslash,
153             # and double quote (- 128 32 1 1 1 1)
154             sub encode_plainstring {
155 0     0 0   my @ords = ();
156 0           my $m = (length($_[0])+2) % 3 + 1;
157 0           for my $c (split '', $_[0]) {
158 0           my @ords2 = (ord($c), (map {$_*2} @ords));
  0            
159 0           for my $i (0 .. $#ords) {
160 0           $ords[$i] = 72*$ords[$i] + $ords2[$i];
161             }
162 0           push @ords, $ords2[$#ords2];
163 0           my $rc = 0;
164 0 0         unless (--$m) {
165 0           $m = 3;
166 0           for my $i (0 .. $#ords) {
167 2     2   10 use integer;
  2         2  
  2         9  
168 0           $ords[$i] += $rc;
169 0           $rc = $ords[$i]/92;
170 0           $ords[$i] %= 92;
171             }
172 0           while ($rc > 0) {
173 2     2   72 use integer;
  2         3  
  2         4  
174 0           push @ords, $rc % 92;
175 0           $rc /= 92;
176             }
177             }
178             }
179 0 0         return join '', map {$_ >= 58 ? chr($_+35) : ($_ >= 1 ? chr($_+34) : '!')} @ords;
  0 0          
180             }
181              
182             # 33, 35..91 93..126
183             # 0 1..57 58..92
184              
185             sub decode_plainstring {
186 0     0 0   my @ords = ();
187 0           for my $c (reverse split '', $_[0]) {
188 0           @ords = map {$_*92} @ords;
  0            
189 0           my $o = ord($c);
190 0 0         $ords[0] += ($o >= 93 ? $o-35 : $o >= 35 ? $o-34 : 0);
    0          
191 0           my $rc = 0;
192 0           for my $i (0 .. $#ords) {
193 2     2   274 use integer;
  2         2  
  2         5  
194 0           $ords[$i] += $rc;
195 0           $rc = $ords[$i]>>8;
196 0           $ords[$i] &= 255;
197             }
198 0           while ($rc > 0) {
199 2     2   67 use integer;
  2         3  
  2         5  
200 0           push @ords, $rc & 255;
201 0           $rc >>= 8;
202             }
203             }
204 0           return join '', map {chr($_)} reverse @ords;
  0            
205             }
206              
207             1;
208              
209              
210             __END__