File Coverage

blib/lib/Net/OAuth2/Scheme/Mixin/HMac.pm
Criterion Covered Total %
statement 16 63 25.4
branch 0 22 0.0
condition 0 12 0.0
subroutine 6 10 60.0
pod n/a
total 22 107 20.5


line stmt bran cond sub pod time code
1 2     2   842 use warnings;
  2         2  
  2         56  
2 2     2   7 use strict;
  2         2  
  2         64  
3              
4             package Net::OAuth2::Scheme::Mixin::HMac;
5             BEGIN {
6 2     2   27 $Net::OAuth2::Scheme::Mixin::HMac::VERSION = '0.020002_099';
7             }
8             # ABSTRACT: implement http_hmac token scheme
9              
10 2     2   6 use Net::OAuth2::Scheme::Option::Defines;
  2         2  
  2         97  
11             use Net::OAuth2::Scheme::HmacUtil
12 2     2   8 qw(hmac_name_to_len_fn encode_plainstring decode_plainstring timing_indep_eq);
  2         7  
  2         95  
13 2     2   751 use MIME::Base64 qw(encode_base64 decode_base64);
  2         923  
  2         2129  
14              
15             # HMAC token
16              
17             # IMPLEMENTATION (transport_)http_hmac
18             # (http_hmac_)nonce_length = 8
19             # (http_hmac_)ext_body ($request, 'server'|'client') -> ext
20             # SUMMARY
21             # http_hmac token
22             # REQUIRES
23             # random
24              
25             Default_Value http_hmac_token_type => 'mac';
26             Default_Value http_hmac_scheme => 'MAC';
27             Default_Value http_hmac_nonce_length => 8;
28             Default_Value http_hmac_ext_body => sub {''};
29              
30             sub pkg_transport_http_hmac {
31 0     0     my __PACKAGE__ $self = shift;
32 0           $self->parameter_prefix(http_hmac_ => @_);
33 0           $self->make_alias(http_hmac_header => 'transport_header');
34 0           $self->make_alias(http_hmac_header_re => 'transport_header_re');
35 0           $self->make_alias(http_hmac_scheme => 'transport_auth_scheme');
36 0           $self->make_alias(http_hmac_scheme_re => 'transport_auth_scheme_re');
37              
38 0           $self->install(token_type => $self->uses('http_hmac_token_type'));
39              
40 0           my $http_hmac_ext_body = $self->uses('http_hmac_ext_body');
41 0 0         if ($self->is_resource_server) {
42             $self->install( psgi_extract =>
43             $self->http_header_extractor
44             (parse_auth => sub {
45 0     0     my ($auth, $req) = @_;
46 0           my %attr = ();
47 0           while ($auth =~ m{\G([^=[:space:]]+)\s*=\s*"([^"]*)"\s*,?\s*}gs) {
48 0           $attr{$1} = $2;
49             }
50 0 0         return () if grep {!defined} (my ($id, $nonce, $mac) = @attr{qw(id nonce mac)});
  0            
51 0 0         my $ext = defined($attr{ext}) ? $attr{ext} : '';
52              
53 0           my $uri = $req->uri;
54 0   0       my ($host,$port) = split ':',($req->headers->{host} || $uri->host_port);
55 0 0 0       $port ||= $uri->scheme eq 'https' ? 443 : 80;
56              
57 0           return ($id, $mac, $nonce, $req->method, $uri->path_query, $host, $port,
58             $ext, $http_hmac_ext_body->($req, 'server'));
59 0           }));
60             }
61 0 0         if ($self->is_client) {
62 0           my $random = $self->uses('random');
63 0           my $nonce_length = $self->uses('http_hmac_nonce_length');
64              
65 0           $self->install( accept_needs => [qw(mac_key mac_algorithm mac_received)] );
66             $self->install( accept_hook => sub {
67 0     0     my $params = shift;
68 0           $params->{mac_received} = time();
69             return ("unknown_algorithm")
70 0 0         unless hmac_name_to_len_fn($params->{mac_algorithm});
71 0           return ();
72 0           });
73              
74             $self->http_header_inserter
75             (make_auth => sub {
76 0     0     my ($http_req, $token, %o) = @_;
77              
78 0           my @missing;
79             my ($key, $alg, $received) =
80 0 0         map {defined $o{$_} ? $o{$_} : do { push @missing, @_; undef }}
  0            
  0            
  0            
81             (qw(mac_key mac_algorithm mac_received));
82 0 0         return ("missing_$missing[0]")
83             if @missing;
84              
85 0 0         return ("unknown_algorithm")
86             unless my (undef, $alg_fn) = hmac_name_to_len_fn($alg);
87              
88 0           my $nonce = (time() - $received) . ':' . encode_plainstring($random->($nonce_length));
89              
90 0           my $uri = $http_req->uri;
91              
92 0   0       my ($host,$port) = split ':',($http_req->header('Host') || $uri->host_port);
93 0 0 0       $port ||= $uri->scheme eq 'https' ? 443 : 80;
94              
95 0           my $ext = $http_hmac_ext_body->($http_req, 'client');
96              
97 0           my $normalized = join "\n",
98             $nonce, $http_req->method, $uri->path_query, $host, $port, $ext, '';
99             return
100             (undef,
101 0 0         join ",\n ", qq(id="$token"), qq(nonce="$nonce"),
102             qq(mac=").encode_base64($alg_fn->($key,$normalized), '').qq("),
103             (length($ext) ? (qq(ext="$ext")) : ()));
104 0           });
105             }
106 0           return $self;
107             }
108              
109             # IMPLEMENTATION (format_)http_hmac
110             # (http_hmac_)hmac
111             # SUMMARY
112             # HMAC-HTTP tokens
113             # REQUIRES
114             # v_id_next
115             # v_table_insert
116              
117             sub pkg_format_http_hmac {
118             my __PACKAGE__ $self = shift;
119             $self->parameter_prefix(http_hmac_ => @_);
120              
121             # CANNOT be used for authcodes and refresh tokens
122             $self->install(format_no_params => 0);
123              
124             my $mac_alg_name = $self->uses('http_hmac_hmac');
125             $mac_alg_name =~ y/_/-/;
126             my ($mac_alg_keylen, $mac_alg) = hmac_name_to_len_fn($mac_alg_name)
127             or $self->croak("unknown/unavailable hmac function: $mac_alg_name");
128              
129             if ($self->is_auth_server) {
130             my ($random, $v_id_next, $vtable_insert, $token_type) = $self->uses_all
131             qw(random v_id_next vtable_insert token_type);
132             $self->install( token_create => sub {
133             my ($now, $expires_in, @bindings) = @_;
134             my $v_id = $v_id_next->();
135             my $key = encode_plainstring($random->($mac_alg_keylen));
136             my $error = $vtable_insert->($v_id, $now + $expires_in, $now, $key, @bindings);
137             return ($error,
138             ($error ? () :
139             (encode_plainstring($v_id),
140             token_type => $token_type,
141             mac_key => $key,
142             mac_algorithm => $mac_alg_name)));
143             });
144             }
145              
146             if ($self->is_resource_server) {
147             $self->install( token_parse => sub {
148             my ($v_id, @rest) = @_;
149             return (decode_plainstring($v_id), @rest);
150             });
151              
152             $self->install( token_finish => sub {
153             my ($v, $mac, $nonce, $method, $uri, $host, $port, $ext, $ext_body) = @_; # ($validator, @payload)
154             my ($expiration, $issuance, $key, @bindings) = @$v;
155             $mac = decode_base64($mac);
156             my $normalized = join "\n",$nonce,$method,$uri,$host,$port,$ext,$ext_body;
157             return ('bad_hash')
158             unless
159             length($mac) == $mac_alg_keylen &&
160             timing_indep_eq($mac, $mac_alg->($key, $normalized), $mac_alg_keylen) &&
161             length ($ext) == length($ext_body) &&
162             timing_indep_eq($ext, $ext_body);
163             return (undef, $issuance, $expiration - $issuance, @bindings);
164             });
165             }
166             return $self;
167             }
168              
169             1;
170              
171              
172             __END__