The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Authen::HTTP::Signature::Method::HMAC;

use 5.010;
use warnings;
use strict;

use Moo;
use Digest::SHA qw(hmac_sha1_base64 hmac_sha256_base64 hmac_sha512_base64);
use Carp qw(confess);

=head1 NAME

Authen::HTTP::Signature::Method::HMAC - Compute digest using a symmetric key

=cut

our $VERSION = '0.02';

=head1 PURPOSE

This class uses a symmetric key to compute a HTTP signature digest. It implements the
HMAC-SHA{1, 256, 512} algorithms.

=head1 ATTRIBUTES

These are Perlish mutators; pass a value to set it, pass no value to get the current value.

=over

=item key

Key material. Read-only. Required.

=back

=cut

has 'key' => (
    is => 'ro',
    required => 1,
);

=over

=item data

The data to be signed. Read-only. Required.

=back

=cut

has 'data' => (
    is => 'ro',
    required => 1,
);

=over

=item hash

The algorithm to generate the digest. Read-only. Required.

=back

=cut

has 'hash' => (
    is => 'ro',
    required => 1,
);

=head1 METHODS

=cut

sub _pad_base64 {
    my $self = shift;
    my $b64_str = shift;

    my $n = length($b64_str) % 4;

    if ( $n ) {
        $b64_str .= '=' x $n;
    }

    return $b64_str;
}

sub _get_digest {
    my $self = shift;
    my $algo = shift;
    my $data = shift;
    my $key = shift;

    my $digest;
    for ( $algo ) {
        when ( /sha1/ ) {
            $digest = hmac_sha1_base64($data, $key);
        }
        when ( /sha256/ ) {
            $digest = hmac_sha256_base64($data, $key);
        }
        when ( /sha512/ ) {
            $digest = hmac_sha512_base64($data, $key);
        }
    }

    confess "I couldn't get a $algo digest\n" unless defined $digest && length $digest;

    return $digest;
}

=over

=item sign()

Signs C<data> with C<key> using C<hash>.

Returns a Base 64 encoded digest.

=back

=cut

sub sign {
    my $self = shift;

    return $self->_generate_signature();
}

sub _generate_signature {
    my $self = shift;

    return $self->_pad_base64( 
        $self->_get_digest(
            $self->hash,
            $self->data,
            $self->key
        )
    );
}

=over

=item verify()

Compares the given signature to a computed one.  Returns true if they are the same. False otherwise.

=back

=cut

sub verify {
    my $self = shift;
    my $candidate = shift;

    confess "How can I validate anything without a signature?" unless $candidate;

    return $self->_generate_signature() eq $candidate;
}

=head1 SEE ALSO

L<Authen::HTTP::Signature>

=cut

1;