File Coverage

blib/lib/Authen/HTTP/Signature.pm
Criterion Covered Total %
statement 70 73 95.8
branch 19 30 63.3
condition 6 15 40.0
subroutine 12 12 100.0
pod 2 2 100.0
total 109 132 82.5


line stmt bran cond sub pod time code
1             package Authen::HTTP::Signature;
2              
3 5     5   87991 use 5.010;
  5         21  
4 5     5   28 use strict;
  5         8  
  5         109  
5 5     5   27 use warnings;
  5         10  
  5         143  
6              
7 5     5   3373 use Moo;
  5         58792  
  5         29  
8 5     5   6878 use Scalar::Util qw(blessed);
  5         11  
  5         440  
9 5     5   22 use Carp qw(confess);
  5         9  
  5         262  
10              
11 5     5   5124 use HTTP::Date qw(time2str);
  5         21418  
  5         1447  
12 5     5   4845 use Data::Dumper;
  5         51542  
  5         7431  
13              
14             =head1 NAME
15              
16             Authen::HTTP::Signature - Sign and validate HTTP headers
17              
18             =head1 VERSION
19              
20             Version 0.03
21              
22             =cut
23              
24             our $VERSION = '0.03';
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",signature="ATp0r26dbMIxOopqw0OfABDT7CKMIoENumuruOtarj8n/97Q3htHFYpH8yOSQk3Z5zh8UxUym6FYTb5+A0Nz3NRsXJibnYi7brE/4tx5But9kkFGzG+xpUmimN4c3TMN7OFH//+r8hBf7BT9/GmHDUVZT2JzWGLZES2xDOUuMtA="},
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 the IETF HTTP Signatures specification 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             =over
166              
167             =item * C<(request-target)>
168              
169             Use the method, text of the path and query from the request
170             (i.e., C) as part of the signing string.
171              
172             =back
173              
174             =back
175              
176             =cut
177              
178             has 'headers' => (
179             is => 'rw',
180             isa => sub { confess "The 'headers' attribute expects an arrayref.\n" unless ref($_[0]) eq ref([]) },
181             default => sub { [ 'date' ] },
182             );
183              
184             =over
185              
186             =item signing_string
187              
188             The string used to compute the signature digest. It contents are derived from the
189             values of the C array.
190              
191             =back
192              
193             =cut
194              
195             has 'signing_string' => (
196             is => 'rw',
197             predicate => 'has_signing_string',
198             );
199              
200             =over
201              
202             =item signature
203              
204             Contains the digital signature authorization data.
205              
206             =back
207              
208             =cut
209              
210             has 'signature' => (
211             is => 'rw',
212             predicate => 'has_signature',
213             );
214              
215             =over
216              
217             =item extensions
218              
219             There are currently no extentions implemented by this library, but the library will append extension
220             information to the generated header data if this attribute has a value.
221              
222             =back
223              
224             =cut
225              
226             has 'extensions' => (
227             is => 'rw',
228             predicate => 'has_extensions',
229             );
230              
231              
232             =over
233              
234             =item key
235              
236             The key to use for cryptographic operations. The key type may have specific meaning based
237             on the algorithm used. RSA requires private keys for signing and the corresponding public
238             key for validation. See the specific implementation module for more details about what this
239             value should be.
240              
241             =back
242              
243             =cut
244              
245             has 'key' => (
246             is => 'rw',
247             predicate => 'has_key',
248             );
249              
250             =over
251              
252             =item key_id
253              
254             Required.
255              
256             A means to identify the key being used to both sender and receiver. This can be any token which makes
257             sense to the sender and receiver. The exact specification of a token and any necessary key management
258             are outside the scope of this library.
259              
260             =back
261              
262             =cut
263              
264             has 'key_id' => (
265             is => 'rw',
266             predicate => 'has_key_id',
267             required => 1,
268             );
269              
270             =over
271              
272             =item request
273              
274             Holds the request to be parsed. Should be some kind of 'Request' object with an interface to
275             get/set headers.
276              
277             =back
278              
279             =cut
280              
281             has 'request' => (
282             is => 'rw',
283             isa => sub { confess "'request' argument isn't blessed" unless blessed($_[0]) },
284             predicate => 'has_request',
285             );
286              
287             =over
288              
289             =item get_header
290              
291             Expects a C reference.
292              
293             This callback represents the method to get header values from the object in the C attribute.
294              
295             The request will be the first parameter, and name of the header to fetch a value will be provided
296             as the second parameter to the callback.
297              
298             B: The callback should be prepared to handle a "pseudo-header" of C which
299             is the path and query portions of the request's URI and HTTP version string.
300             (For more information see the
301             L.)
302              
303             =back
304              
305             =cut
306              
307             has 'get_header' => (
308             is => 'rw',
309             isa => sub { die "'get_header' expects a CODE ref\n" unless ref($_[0]) eq "CODE" },
310             predicate => 'has_get_header',
311             default => sub {
312             sub {
313             confess "Didn't get 2 arguments" unless @_ == 2;
314             my $request = shift;
315             confess "'request' isn't blessed" unless blessed $request;
316             my $name = lc(shift);
317              
318             if( $name eq 'request-line' ) {
319             sprintf("request-line: %s %s",
320             $request->uri->path_query,
321             $request->protocol);
322             } elsif( $name eq '(request-target)' ) {
323             sprintf("(request-target): %s %s",
324             lc($request->method),
325             $request->uri->path_query);
326             } elsif( $request->header($name) ) {
327             sprintf("%s: %s",
328             $name,
329             $request->header($name) );
330             }
331             };
332             },
333             lazy => 1,
334             );
335              
336             =over
337              
338             =item set_header
339              
340             Expects a C reference.
341              
342             This callback represents the way to set header values on the object in the C attribute.
343              
344             The request will be the first parameter. The name of the header and its value will be the second and
345             third parameters.
346              
347             Returns the request object.
348              
349             =back
350              
351             =cut
352              
353             has 'set_header' => (
354             is => 'rw',
355             isa => sub { die "'set_header' expects a CODE ref\n" unless ref($_[0]) eq "CODE" },
356             predicate => 'has_set_header',
357             default => sub {
358             sub {
359             confess "Didn't get 3 arguments" unless @_ == 3;
360             my ($request, $name, $value) = @_;
361             confess "'request' isn't blessed" unless blessed $request;
362              
363             $request->header( $name => $value );
364              
365             $request;
366             };
367             },
368             lazy => 1,
369             );
370              
371             =over
372              
373             =item authorizaton_string
374              
375             The text to identify the HTTP signature authorization scheme. Currently defined as the string
376             literal 'Signature'. Read-only.
377              
378             =back
379              
380             =cut
381              
382             has 'authorization_string' => (
383             is => 'ro',
384             default => sub { 'Signature' },
385             );
386              
387             =head1 METHODS
388              
389             Errors are generally fatal. Use L for more graceful error handling.
390              
391             =cut
392              
393             sub _update_signing_string {
394 5     5   11 my $self = shift;
395 5   33     19 my $request = shift || $self->request;
396              
397 5 50       20 confess "I can't update the signing string because I don't have a request" unless $request;
398 5 50       21 confess "I can't update the signing string because I don't have a 'get_header' callback" unless $self->has_get_header;
399              
400             my $ss = join "\n", map {
401 16 50       2569 $self->get_header->($request, $_)
402 5         10 or confess "Couldn't get header value for $_\n" } @{ $self->headers };
  5         56  
403              
404 5         222 $self->signing_string( $ss );
405              
406 5         10 return $ss;
407             }
408              
409             sub _format_signature {
410 5     5   10 my $self = shift;
411              
412 5         43 my $rv = sprintf(q{%s keyId="%s",algorithm="%s"},
413             $self->authorization_string,
414             $self->key_id,
415             $self->algorithm
416             );
417              
418 5 100 66     9 if ( scalar @{ $self->headers } == 1 and $self->headers->[0] =~ /^date$/i ) {
  5         126  
419             # if there's only the default header, omit the headers param
420             }
421             else {
422 3         29 $rv .= q{,headers="} . lc(join " ", @{$self->headers}) . q{"};
  3         63  
423             }
424              
425 5 50       137 if ( $self->has_extensions ) {
426 0         0 $rv .= q{,ext="} . $self->extensions . q{"};
427             }
428              
429 5         21 $rv .= q{,signature="} . $self->signature . q{"};
430              
431 5         95 return $rv;
432              
433             }
434              
435             =over
436              
437             =item sign()
438              
439             This method takes signs the values of the specified C using C and C.
440              
441             By default, it uses C as its input. You may optionally pass a request object and it
442             will use that instead. By default, it uses C. You may optionally pass key material and it
443             will use that instead.
444              
445             It will add a C header to the C if there isn't already one in the request
446             object.
447              
448             It adds a C header with the appropriate signature data.
449              
450             The return value is a signed request object.
451              
452             =back
453              
454             =cut
455              
456             sub sign {
457 5     5 1 1216 my $self = shift;
458              
459 5   33     70 my $request = shift || $self->request;
460 5 50       1635 confess "I don't have a request to sign" unless $request;
461              
462 5   33     53 my $key = shift || $self->key;
463 5 50       16 confess "I don't have a key to use for signing" unless $key;
464              
465 5 100       57 unless ( $self->get_header->($request, 'date') ) {
466 2         159 $self->set_header->($request, 'date', time2str());
467             }
468              
469 5         145 $self->_update_signing_string($request);
470              
471 5         5 my $signer;
472 5 100       44 if ( $self->algorithm =~ /^rsa/ ) {
    50          
473 2         688 require Authen::HTTP::Signature::Method::RSA;
474 2         35 $signer = Authen::HTTP::Signature::Method::RSA->new(
475             key => $key,
476             data => $self->signing_string,
477             hash => $self->algorithm
478             );
479             }
480             elsif ( $self->algorithm =~ /^hmac/ ) {
481 3         1462 require Authen::HTTP::Signature::Method::HMAC;
482 3         41 $signer = Authen::HTTP::Signature::Method::HMAC->new(
483             key => $key,
484             data => $self->signing_string,
485             hash => $self->algorithm
486             );
487             }
488             else {
489 0         0 confess "I don't know how to sign using " . $self->algorithm;
490             }
491              
492 5         3566 $self->signature( $signer->sign() );
493              
494 5         22 $self->set_header->($request, 'Authorization', $self->_format_signature);
495              
496 5         40 return $request;
497             }
498              
499             =over
500              
501             =item verify()
502              
503             This method verifies that a signature on a request is valid.
504              
505             By default it uses C. You may optionally pass in key material and it
506             will use that instead.
507              
508             Returns a boolean.
509              
510             =back
511              
512             =cut
513              
514             sub verify {
515 6     6 1 1691 my $self = shift;
516              
517 6   33     38 my $key = shift || $self->key;
518 6 50       17 confess "I don't have a key to use for verification" unless $key;
519              
520 6 50       26 confess "I don't have a signing string" unless $self->has_signing_string;
521 6 50       22 confess "I don't have a signature" unless $self->has_signature;
522              
523 6         6 my $v;
524 6 100       50 if ( $self->algorithm =~ /^rsa/ ) {
    50          
525 2         923 require Authen::HTTP::Signature::Method::RSA;
526 2         37 $v = Authen::HTTP::Signature::Method::RSA->new(
527             key => $key,
528             data => $self->signing_string,
529             hash => $self->algorithm
530             );
531             }
532             elsif ( $self->algorithm =~ /^hmac/ ) {
533 4         23 require Authen::HTTP::Signature::Method::HMAC;
534 4         117 $v = Authen::HTTP::Signature::Method::HMAC->new(
535             key => $key,
536             data => $self->signing_string,
537             hash => $self->algorithm
538             );
539             }
540             else {
541 0         0 confess "I don't know how to verify using " . $self->algorithm;
542             }
543              
544 6         1294 return $v->verify($self->signature);
545             }
546              
547             =head1 AUTHOR
548              
549             Mark Allen, C<< >>
550              
551             =head1 BUGS
552              
553             Please report any bugs or feature requests to C, or through
554             the web interface at L. I will be notified, and then you'll
555             automatically be notified of progress on your bug as I make changes.
556              
557             =head1 SUPPORT
558              
559             You can find documentation for this module with the perldoc command.
560              
561             perldoc Authen::HTTP::Signature
562              
563             You can also look for information at:
564              
565             =over 4
566              
567             =item * RT: CPAN's request tracker (report bugs here)
568              
569             L
570              
571             =item * AnnoCPAN: Annotated CPAN documentation
572              
573             L
574              
575             =item * CPAN Ratings
576              
577             L
578              
579             =item * MetaCPAN
580              
581             L
582              
583             =item * GitHub
584              
585             L
586              
587             =back
588              
589             =head1 SEE ALSO
590              
591             L,
592             L,
593             L
594              
595             L
596              
597             =head1 LICENSE AND COPYRIGHT
598              
599             Copyright 2012 Mark Allen.
600              
601             This program is free software; you can redistribute it and/or modify it
602             under the terms of either: the GNU General Public License as published
603             by the Free Software Foundation; or the Artistic License.
604              
605             See http://dev.perl.org/licenses/ for more information.
606              
607             =cut
608              
609             1; # End of Authen::HTTP::Signature