· 6 years ago · Mar 17, 2019, 02:22 PM
1package Mojo::UserAgent::Role::AWSSignature4;
2use Mojo::Base -role;
3
4use Digest::SHA;
5use Time::Piece;
6
7has access_key => sub { $ENV{AWS_ACCESS_KEY} or die 'missing "access_key"' };
8has aws_algorithm => 'AWS4-HMAC-SHA256';
9has expires => 86_400;
10has region => 'us-east-1';
11has secret_key => sub { $ENV{AWS_SECRET_KEY} or die 'missing "secret_key"' };
12has service => sub { die 'missing "service"' };
13has session_token => sub { $ENV{AWS_SESSION_TOKEN} || undef };
14has unsigned_payload => 0;
15has _tx => sub { die };
16
17around build_tx => sub {
18 my ($orig, $self) = (shift, shift);
19 $self->transactor->add_generator(awssig4 => sub {
20 my ($transactor, $tx, $config) = @_;
21 my $aws = $self->new({%$config, _tx => $tx});
22 $tx->req->headers->host($tx->req->url->host);
23 $tx->req->headers->header('X-Amz-Date' => $aws->date_timestamp);
24 $tx->req->headers->header('X-Amz-Expires' => $aws->expires) if $aws->expires;
25 $tx->req->headers->authorization($aws->authorization);
26 });
27 $orig->($self, @_);
28};
29
30sub authorization {
31 my $self = shift;
32 sprintf '%s Credential=%s/%s, SignedHeaders=%s, Signature=%s',
33 $self->aws_algorithm,
34 $self->access_key,
35 $self->credential_scope,
36 $self->signed_header_list,
37 $self->signature
38}
39
40sub canonical_headers {
41 my $self = shift;
42 join '', map { lc($_) . ":" . $self->_tx->req->headers->to_hash->{$_} . "\n" } @{$self->header_list};
43}
44
45sub canonical_qstring { shift->_tx->req->url->query->to_string }
46
47sub canonical_request {
48 my $self = shift;
49 join "\n", $self->_tx->req->method,
50 $self->_tx->req->url->path->to_abs_string,
51 $self->canonical_qstring,
52 $self->canonical_headers,
53 $self->signed_header_list,
54 $self->hashed_payload;
55}
56
57sub credential_scope {
58 my $self = shift;
59 join '/', $self->date, $self->region, $self->service, 'aws4_request';
60}
61
62sub date { shift->time->ymd('') }
63
64sub date_timestamp { $_[0]->time->ymd('').'T'.$_[0]->time->hms('').'Z' }
65
66sub hashed_payload {
67 my $self = shift;
68 return $self->unsigned_payload ? 'UNSIGNED-PAYLOAD' : Digest::SHA::sha256_hex($self->_tx->req->body);
69}
70
71sub header_list { [sort keys %{shift->_tx->req->headers->to_hash}] }
72
73sub signature {
74 my $self = shift;
75 Digest::SHA::hmac_sha256_hex($self->string_to_sign, $self->signing_key);
76}
77
78sub signed_header_list { join ';', map { lc($_) } @{shift->header_list} }
79
80sub signed_qstring {
81 my $self = shift;
82 $self->_tx->req->url->query(['X-Amz-Signature' => $self->signature]);
83}
84
85sub signing_key {
86 my $self = shift;
87 my $kSecret = "AWS4" . $self->secret_key;
88 my $kDate = Digest::SHA::hmac_sha256($self->date, $kSecret);
89 my $kRegion = Digest::SHA::hmac_sha256($self->region, $kDate);
90 my $kService = Digest::SHA::hmac_sha256($self->service, $kRegion);
91 return Digest::SHA::hmac_sha256("aws4_request", $kService);
92}
93
94sub string_to_sign {
95 my $self = shift;
96 join "\n", $self->aws_algorithm,
97 $self->date_timestamp,
98 $self->credential_scope,
99 Digest::SHA::sha256_hex($self->canonical_request);
100}
101
102sub time { gmtime }
103
1041;