File Coverage

blib/lib/Authen/HTTP/Signature.pm
Criterion Covered Total %
statement 53 74 71.6
branch 14 30 46.6
condition 4 15 26.6
subroutine 11 12 91.6
pod 2 2 100.0
total 84 133 63.1


line stmt bran cond sub pod time code
1             package Authen::HTTP::Signature;
2              
3 5     5   141132 use 5.010;
  5         27  
  5         200  
4 5     5   26 use strict;
  5         9  
  5         167  
5 5     5   25 use warnings;
  5         18  
  5         150  
6              
7 5     5   6510 use Moo;
  5         77487  
  5         37  
8 5     5   9280 use Scalar::Util qw(blessed);
  5         31  
  5         658  
9 5     5   29 use Carp qw(confess);
  5         13  
  5         272  
10              
11 5     5   7339 use HTTP::Date qw(time2str);
  5         34536  
  5         388  
12 5     5   7092 use Data::Dumper;
  5         71201  
  5         15133  
13              
14             =head1 NAME
15              
16             Authen::HTTP::Signature - Sign and validate HTTP headers
17              
18             =head1 VERSION
19              
20             Version 0.02
21              
22             =cut
23              
24             our $VERSION = '0.02';
25              
26             =head1 SYNOPSIS
27              
28             Create signatures:
29              
30             use 5.010;
31             use Authen::HTTP::Signature;
32             use File::Slurp qw(read_file);
33             use HTTP::Request::Common;
34              
35             my $key_string = read_file("/my/priv/key.pem") or die $!;
36              
37             my $signer = Authen::HTTP::Signature->new(
38             key => $key_string,
39             key_id => 'Test',
40             );
41              
42             my $req = POST('http://example.com/foo?param=value&pet=dog',
43             Content_Type => 'application/json',
44             Content_MD5 => 'Sd/dVLAcvNLSq16eXua5uQ==',
45             Content_Length => 18,
46             Content => '{"hello": "world"}'
47             );
48              
49             my $signed_req = $signer->sign($req);
50              
51             # adds then signs the 'Date' header with private key using
52             # RSA-SHA256, then adds 'Authorization' header to
53             # $req
54              
55             Validate signatures:
56              
57             use 5.010;
58             use Authen::HTTP::Signature::Parser;
59             use HTTP::Request::Common;
60             use File::Slurp qw(read_file);
61             use Try::Tiny;
62              
63             my $req = POST('http://example.com/foo?param=value&pet=dog',
64             Content_Type => 'application/json',
65             Content_MD5 => 'Sd/dVLAcvNLSq16eXua5uQ==',
66             Content_Length => 18,
67             Date => 'Thu, 05 Jan 2012 21:31:40 GMT',
68             Authorization => q{Signature keyId="Test",algorithm="rsa-sha256" MDyO5tSvin5FBVdq3gMBTwtVgE8U/JpzSwFvY7gu7Q2tiZ5TvfHzf/RzmRoYwO8PoV1UGaw6IMwWzxDQkcoYOwvG/w4ljQBBoNusO/mYSvKrbqxUmZi8rNtrMcb82MS33bai5IeLnOGl31W1UbL4qE/wL8U9wCPGRJlCFLsTgD8=},
69             Content => '{"hello": "world"}'
70             );
71              
72             my $p;
73             try {
74             $p = Authen::HTTP::Signature::Parser->new($req)->parse();
75             }
76             catch {
77             die "Parse failed: $_\n";
78             };
79              
80             my $key_string = read_file("/my/pub/key.pem") or die $!;
81             $p->key( $key_string );
82              
83             if ( $p->verify() ) {
84             say "Request is valid!"
85             }
86             else {
87             say "Request isn't valid";
88             };
89              
90             =head1 PURPOSE
91              
92             This is an implementation of Joyent's HTTP signature authentication scheme. The idea is to authenticate
93             connections (over HTTPS ideally) using either an RSA keypair or a symmetric key by signing a set of header
94             values.
95              
96             If you wish to use SSH keys for validation as in Joyent's proposal, check out L.
97              
98             =head1 ATTRIBUTES
99              
100             These are Perlish mutators; give an argument to set a value or no argument to get the current value.
101              
102             =over
103              
104             =item algorithm
105              
106             The algorithm to use for signing. Read-only.
107              
108             One of:
109              
110             =over
111              
112             =item * C
113              
114             =item * C (B)
115              
116             =item * C
117              
118             =item * C
119              
120             =item * C
121              
122             =item * C
123              
124             =back
125              
126             =back
127              
128             =cut
129              
130             has 'algorithm' => (
131             is => 'ro',
132             isa => sub {
133             my $n = lc shift;
134             confess "$n doesn't match any supported algorithm.\n" unless
135             scalar grep { $_ eq $n } qw(
136             rsa-sha1
137             rsa-sha256
138             rsa-sha512
139             hmac-sha1
140             hmac-sha256
141             hmac-sha512
142             );
143             },
144             default => sub { 'rsa-sha256' },
145             );
146              
147             =over
148              
149             =item headers
150              
151             The list of headers to be signed (or already signed.) Defaults to the 'Date' header. The order of the headers
152             in this list will be used to build the order of the text in the signing string.
153              
154             This attribute can have a psuedo-value. It is:
155              
156             =over
157              
158             =item * C
159              
160             Use the method, text of the path and query from the request, and the protocol version signature
161             (i.e., C) as part of the signing string.
162              
163             =back
164              
165             =back
166              
167             =cut
168              
169             has 'headers' => (
170             is => 'rw',
171             isa => sub { confess "The 'headers' attribute expects an arrayref.\n" unless ref($_[0]) eq ref([]) },
172             default => sub { [ 'date' ] },
173             );
174              
175             =over
176              
177             =item signing_string
178              
179             The string used to compute the signature digest. It contents are derived from the
180             values of the C array.
181              
182             =back
183              
184             =cut
185              
186             has 'signing_string' => (
187             is => 'rw',
188             predicate => 'has_signing_string',
189             );
190              
191             =over
192              
193             =item signature
194              
195             Contains the digital signature authorization data.
196              
197             =back
198              
199             =cut
200              
201             has 'signature' => (
202             is => 'rw',
203             predicate => 'has_signature',
204             );
205              
206             =over
207              
208             =item extensions
209              
210             There are currently no extentions implemented by this library, but the library will append extension
211             information to the generated header data if this attribute has a value.
212              
213             =back
214              
215             =cut
216              
217             has 'extensions' => (
218             is => 'rw',
219             predicate => 'has_extensions',
220             );
221              
222              
223             =over
224              
225             =item key
226              
227             The key to use for cryptographic operations. The key type may have specific meaning based
228             on the algorithm used. RSA requires private keys for signing and the corresponding public
229             key for validation. See the specific implementation module for more details about what this
230             value should be.
231              
232             =back
233              
234             =cut
235              
236             has 'key' => (
237             is => 'rw',
238             predicate => 'has_key',
239             );
240              
241             =over
242              
243             =item key_id
244              
245             Required.
246              
247             A means to identify the key being used to both sender and receiver. This can be any token which makes
248             sense to the sender and receiver. The exact specification of a token and any necessary key management
249             are outside the scope of this library.
250              
251             =back
252              
253             =cut
254              
255             has 'key_id' => (
256             is => 'rw',
257             predicate => 'has_key_id',
258             required => 1,
259             );
260              
261             =over
262              
263             =item request
264              
265             Holds the request to be parsed. Should be some kind of 'Request' object with an interface to
266             get/set headers.
267              
268             =back
269              
270             =cut
271              
272             has 'request' => (
273             is => 'rw',
274             isa => sub { confess "'request' argument isn't blessed" unless blessed($_[0]) },
275             predicate => 'has_request',
276             );
277              
278             =over
279              
280             =item get_header
281              
282             Expects a C reference.
283              
284             This callback represents the method to get header values from the object in the C attribute.
285              
286             The request will be the first parameter, and name of the header to fetch a value will be provided
287             as the second parameter to the callback.
288              
289             B: The callback should be prepared to handle a "psuedo-header" of C which
290             is the path and query portions of the request's URI and HTTP version string.
291             (For more information see the
292             L.)
293              
294             =back
295              
296             =cut
297              
298             has 'get_header' => (
299             is => 'rw',
300             isa => sub { die "'get_header' expects a CODE ref\n" unless ref($_[0]) eq "CODE" },
301             predicate => 'has_get_header',
302             default => sub {
303             sub {
304             confess "Didn't get 2 arguments" unless @_ == 2;
305             my $request = shift;
306             confess "'request' isn't blessed" unless blessed $request;
307             my $name = lc(shift);
308              
309             $name eq 'request-line' ?
310             sprintf("%s %s",
311             $request->uri->path_query,
312             $request->protocol)
313             : $request->header($name);
314             };
315             },
316             lazy => 1,
317             );
318              
319             =over
320              
321             =item set_header
322              
323             Expects a C reference.
324              
325             This callback represents the way to set header values on the object in the C attribute.
326              
327             The request will be the first parameter. The name of the header and its value will be the second and
328             third parameters.
329              
330             Returns the request object.
331              
332             =back
333              
334             =cut
335              
336             has 'set_header' => (
337             is => 'rw',
338             isa => sub { die "'set_header' expects a CODE ref\n" unless ref($_[0]) eq "CODE" },
339             predicate => 'has_set_header',
340             default => sub {
341             sub {
342             confess "Didn't get 3 arguments" unless @_ == 3;
343             my ($request, $name, $value) = @_;
344             confess "'request' isn't blessed" unless blessed $request;
345              
346             $request->header( $name => $value );
347              
348             $request;
349             };
350             },
351             lazy => 1,
352             );
353              
354             =over
355              
356             =item authorizaton_string
357              
358             The text to identify the HTTP signature authorization scheme. Currently defined as the string
359             literal 'Signature'. Read-only.
360              
361             =back
362              
363             =cut
364              
365             has 'authorization_string' => (
366             is => 'ro',
367             default => sub { 'Signature' },
368             );
369              
370             =head1 METHODS
371              
372             Errors are generally fatal. Use L for more graceful error handling.
373              
374             =cut
375              
376             sub _update_signing_string {
377 3     3   6 my $self = shift;
378 3   33     9 my $request = shift || $self->request;
379              
380 3 50       18 confess "I can't update the signing string because I don't have a request" unless $request;
381 3 50       16 confess "I can't update the signing string because I don't have a 'get_header' callback" unless $self->has_get_header;
382              
383 4 50       1741 my $ss = join "\n", map {
384 3         13 $self->get_header->($request, $_)
385 3         7 or confess "Couldn't get header value for $_\n" } @{ $self->headers };
386              
387 3         133 $self->signing_string( $ss );
388            
389 3         5 return $ss;
390             }
391              
392             sub _format_signature {
393 0     0   0 my $self = shift;
394            
395 0         0 my $rv = sprintf(q{%s keyId="%s",algorithm="%s"},
396             $self->authorization_string,
397             $self->key_id,
398             $self->algorithm
399             );
400              
401 0 0 0     0 if ( scalar @{ $self->headers } == 1 and $self->headers->[0] =~ /^date$/i ) {
  0         0  
402             # if there's only the default header, omit the headers param
403             }
404             else {
405 0         0 $rv .= q{,headers="} . lc(join " ", @{$self->headers}) . q{"};
  0         0  
406             }
407              
408 0 0       0 if ( $self->has_extensions ) {
409 0         0 $rv .= q{,ext="} . $self->extensions . q{"};
410             }
411              
412 0         0 $rv .= q{ } . $self->signature;
413              
414 0         0 return $rv;
415              
416             }
417              
418             =over
419              
420             =item sign()
421              
422             This method takes signs the values of the specified C using C and C.
423              
424             By default, it uses C as its input. You may optionally pass a request object and it
425             will use that instead. By default, it uses C. You may optionally pass key material and it
426             will use that instead.
427              
428             It will add a C header to the C if there isn't already one in the request
429             object.
430              
431             It adds a C header with the appropriate signature data.
432              
433             The return value is a signed request object.
434              
435             =back
436              
437             =cut
438              
439             sub sign {
440 3     3 1 1351 my $self = shift;
441              
442 3   33     25 my $request = shift || $self->request;
443 3 50       1761 confess "I don't have a request to sign" unless $request;
444              
445 3   33     56 my $key = shift || $self->key;
446 3 50       13 confess "I don't have a key to use for signing" unless $key;
447              
448 3 100       16 unless ( $self->get_header->($request, 'date') ) {
449 2         162 $self->set_header->($request, 'date', time2str());
450             }
451              
452 3         99 $self->_update_signing_string($request);
453              
454 3         5 my $signer;
455 3 100       30 if ( $self->algorithm =~ /^rsa/ ) {
    50          
456 1         759 require Authen::HTTP::Signature::Method::RSA;
457 0         0 $signer = Authen::HTTP::Signature::Method::RSA->new(
458             key => $key,
459             data => $self->signing_string,
460             hash => $self->algorithm
461             );
462             }
463             elsif ( $self->algorithm =~ /^hmac/ ) {
464 2         1624 require Authen::HTTP::Signature::Method::HMAC;
465 0         0 $signer = Authen::HTTP::Signature::Method::HMAC->new(
466             key => $key,
467             data => $self->signing_string,
468             hash => $self->algorithm
469             );
470             }
471             else {
472 0         0 confess "I don't know how to sign using " . $self->algorithm;
473             }
474              
475 0         0 $self->signature( $signer->sign() );
476              
477 0         0 $self->set_header->($request, 'Authorization', $self->_format_signature);
478              
479 0         0 return $request;
480             }
481              
482             =over
483              
484             =item verify()
485              
486             This method verifies that a signature on a request is valid.
487              
488             By default it uses C. You may optionally pass in key material and it
489             will use that instead.
490              
491             Returns a boolean.
492              
493             =back
494              
495             =cut
496              
497             sub verify {
498 1     1 1 689 my $self = shift;
499              
500 1   33     14 my $key = shift || $self->key;
501 1 50       5 confess "I don't have a key to use for verification" unless $key;
502              
503 1 50       9 confess "I don't have a signing string" unless $self->has_signing_string;
504 1 50       7 confess "I don't have a signature" unless $self->has_signature;
505              
506 1         2 my $v;
507 1 50       16 if ( $self->algorithm =~ /^rsa/ ) {
    0          
508 1         1227 require Authen::HTTP::Signature::Method::RSA;
509 0           $v = Authen::HTTP::Signature::Method::RSA->new(
510             key => $key,
511             data => $self->signing_string,
512             hash => $self->algorithm
513             );
514             }
515             elsif ( $self->algorithm =~ /^hmac/ ) {
516 0           require Authen::HTTP::Signature::Method::HMAC;
517 0           $v = Authen::HTTP::Signature::Method::HMAC->new(
518             key => $key,
519             data => $self->signing_string,
520             hash => $self->algorithm
521             );
522             }
523             else {
524 0           confess "I don't know how to verify using " . $self->algorithm;
525             }
526              
527 0           return $v->verify($self->signature);
528             }
529              
530             =head1 AUTHOR
531              
532             Mark Allen, C<< >>
533              
534             =head1 BUGS
535              
536             Please report any bugs or feature requests to C, or through
537             the web interface at L. I will be notified, and then you'll
538             automatically be notified of progress on your bug as I make changes.
539              
540             =head1 SUPPORT
541              
542             You can find documentation for this module with the perldoc command.
543              
544             perldoc Authen::HTTP::Signature
545              
546             You can also look for information at:
547              
548             =over 4
549              
550             =item * RT: CPAN's request tracker (report bugs here)
551              
552             L
553              
554             =item * AnnoCPAN: Annotated CPAN documentation
555              
556             L
557              
558             =item * CPAN Ratings
559              
560             L
561              
562             =item * MetaCPAN
563              
564             L
565              
566             =item * GitHub
567              
568             L
569              
570             =back
571              
572             =head1 SEE ALSO
573              
574             L,
575             L,
576             L
577              
578             L
579              
580             =head1 LICENSE AND COPYRIGHT
581              
582             Copyright 2012 Mark Allen.
583              
584             This program is free software; you can redistribute it and/or modify it
585             under the terms of either: the GNU General Public License as published
586             by the Free Software Foundation; or the Artistic License.
587              
588             See http://dev.perl.org/licenses/ for more information.
589              
590             =cut
591              
592             1; # End of Authen::HTTP::Signature