Authen-HTTP-Signature

 view release on metacpan or  search on metacpan

lib/Authen/HTTP/Signature.pm  view on Meta::CPAN

package Authen::HTTP::Signature;

use 5.010;
use strict;
use warnings;

use Moo;
use Scalar::Util qw(blessed);
use Carp qw(confess);

use HTTP::Date qw(time2str);
use Data::Dumper;

=head1 NAME

Authen::HTTP::Signature - Sign and validate HTTP headers

=head1 VERSION

Version 0.03

=cut

our $VERSION = '0.03';

=head1 SYNOPSIS

Create signatures:

    use 5.010;
    use Authen::HTTP::Signature;
    use File::Slurp qw(read_file);
    use HTTP::Request::Common;

    my $key_string = read_file("/my/priv/key.pem") or die $!;

    my $signer = Authen::HTTP::Signature->new(
        key => $key_string,
        key_id => 'Test',
    );

    my $req = POST('http://example.com/foo?param=value&pet=dog',
            Content_Type => 'application/json',
            Content_MD5 => 'Sd/dVLAcvNLSq16eXua5uQ==',
            Content_Length => 18,
            Content => '{"hello": "world"}'
    );

    my $signed_req = $signer->sign($req);

    # adds then signs the 'Date' header with private key using
    # RSA-SHA256, then adds 'Authorization' header to
    # $req

Validate signatures:

    use 5.010;
    use Authen::HTTP::Signature::Parser;
    use HTTP::Request::Common;
    use File::Slurp qw(read_file);
    use Try::Tiny;

    my $req = POST('http://example.com/foo?param=value&pet=dog',
            Content_Type => 'application/json',
            Content_MD5 => 'Sd/dVLAcvNLSq16eXua5uQ==',
            Content_Length => 18,
            Date => 'Thu, 05 Jan 2012 21:31:40 GMT',
            Authorization => q{Signature keyId="Test",algorithm="rsa-sha256",signature="ATp0r26dbMIxOopqw0OfABDT7CKMIoENumuruOtarj8n/97Q3htHFYpH8yOSQk3Z5zh8UxUym6FYTb5+A0Nz3NRsXJibnYi7brE/4tx5But9kkFGzG+xpUmimN4c3TMN7OFH//+r8hBf7BT9/GmHDUVZT2JzWGLZES...
            Content => '{"hello": "world"}'
    );

    my $p;
    try {
        $p = Authen::HTTP::Signature::Parser->new($req)->parse();
    }
    catch {
        die "Parse failed: $_\n";
    };

    my $key_string = read_file("/my/pub/key.pem") or die $!;
    $p->key( $key_string );

    if ( $p->verify() ) {
        say "Request is valid!"
    }
    else {
        say "Request isn't valid";
    };

=head1 PURPOSE

This is an implementation of the IETF HTTP Signatures specification authentication scheme. The idea is to authenticate
connections (over HTTPS ideally) using either an RSA keypair or a symmetric key by signing a set of header
values.

If you wish to use SSH keys for validation as in Joyent's proposal, check out L<Convert::SSH2>.

=head1 ATTRIBUTES

These are Perlish mutators; give an argument to set a value or no argument to get the current value.

=over

=item algorithm

The algorithm to use for signing. Read-only.

One of:

=over

=item * C<rsa-sha1>

=item * C<rsa-sha256> (B<default>)

=item * C<rsa-sha512>

=item * C<hmac-sha1>

=item * C<hmac-sha256>

lib/Authen/HTTP/Signature.pm  view on Meta::CPAN

                    $request->header($name) );
            }
        };
    },
    lazy => 1,
);

=over

=item set_header

Expects a C<CODE> reference.

This callback represents the way to set header values on the object in the C<request> attribute.

The request will be the first parameter.  The name of the header and its value will be the second and
third parameters.

Returns the request object.

=back

=cut

has 'set_header' => (
    is => 'rw',
    isa => sub { die "'set_header' expects a CODE ref\n" unless ref($_[0]) eq "CODE" },
    predicate => 'has_set_header',
    default => sub {
        sub {
            confess "Didn't get 3 arguments" unless @_ == 3;
            my ($request, $name, $value) = @_;
            confess "'request' isn't blessed" unless blessed $request;

            $request->header( $name => $value );

            $request;
        };
    },
    lazy => 1,
);

=over

=item authorizaton_string

The text to identify the HTTP signature authorization scheme. Currently defined as the string
literal 'Signature'.  Read-only.

=back

=cut

has 'authorization_string' => (
    is => 'ro',
    default => sub { 'Signature' },
);

=head1 METHODS

Errors are generally fatal. Use L<Try::Tiny> for more graceful error handling.

=cut

sub _update_signing_string {
    my $self = shift;
    my $request = shift || $self->request;

    confess "I can't update the signing string because I don't have a request" unless $request;
    confess "I can't update the signing string because I don't have a 'get_header' callback" unless $self->has_get_header;

    my $ss = join "\n", map {
        $self->get_header->($request, $_)
            or confess "Couldn't get header value for $_\n" } @{ $self->headers };

    $self->signing_string( $ss );

    return $ss;
}

sub _format_signature {
    my $self = shift;

    my $rv = sprintf(q{%s keyId="%s",algorithm="%s"},
                $self->authorization_string,
                $self->key_id,
                $self->algorithm
             );

    if ( scalar @{ $self->headers } == 1 and $self->headers->[0] =~ /^date$/i ) {
        # if there's only the default header, omit the headers param
    }
    else {
        $rv .= q{,headers="} . lc(join " ", @{$self->headers}) . q{"};
    }

    if ( $self->has_extensions ) {
        $rv .= q{,ext="} . $self->extensions . q{"};
    }

    $rv .= q{,signature="} . $self->signature . q{"};

    return $rv;

}

=over

=item sign()

This method takes signs the values of the specified C<headers> using C<algorithm> and C<key>.

By default, it uses C<request> as its input. You may optionally pass a request object and it
will use that instead.  By default, it uses C<key>. You may optionally pass key material and it
will use that instead.

It will add a C<Date> header to the C<request> if there isn't already one in the request
object.

It adds a C<Authorization> header with the appropriate signature data.



( run in 1.781 second using v1.01-cache-2.11-cpan-39bf76dae61 )