AWS-Signature-V4

 view release on metacpan or  search on metacpan

t/security.t  view on Meta::CPAN

   }
   for my $name ("x-n:1\nfoo", 'x n', 'x:n', '', "x\x{e9}") {
      my $shown = $name =~ s/([^ -~])/sprintf '\\x%02X', ord $1/ger;
      is dies { $s3->sign(method => 'GET', url => $url, headers => {$name => 'v'}) },
         bad_request(qr/header/), "name '$shown' rejected";
   }
   ok lives { $s3->sign(method => 'GET', url => $url,
      headers => {"X-Ok_1.a!#\$%&'*+^`|~" => "tab\there, spaces  ok"}) },
      'valid token names and values with tabs and spaces are fine';
   is dies { $s3->presign(url => $url, headers => {'X-V' => "a\nb"}) },
      bad_request(qr/header/), 'presign too';
};

subtest 'the derived signing key is not exposed' => sub {
   my $r = $s3->sign(method => 'PUT', url => $url, time => 0,
      streaming => 1, decoded_content_length => 1);
   my $ck = $r->{chunker};
   ok !$ck->can('key'), 'no key accessor';
   ok !$ck->can('previous'), 'no previous accessor';
   my $key = unpack 'H*', AWS::Signature::V4::Credentials->new(%cred)
      ->signing_key($r->{scope});
   unlike dump_of($r), qr/\Q$key\E/, 'the hex key is not in a dump of the result';
   my $raw = pack 'H*', $key;
   unlike dump_of($r), qr/\Q@{[ quotemeta $raw ]}\E/, 'nor the raw key';
   like $ck->chunk('x'), qr/\A1;chunk-signature=[0-9a-f]{64}\r\nx\r\n\z/, 'chunks are still signed';
};

subtest 'presign refuses authentication parameters in the url' => sub {
   for my $param (qw<
      X-Amz-Algorithm X-Amz-Credential X-Amz-Date X-Amz-Expires
      X-Amz-SignedHeaders X-Amz-Signature X-Amz-Security-Token
      X-Amz-X509 X-Amz-X509-Chain x-amz-signature X-AMZ-EXPIRES
   >) {
      is dies { $s3->presign(url => "$url?$param=1") },
         bad_request(qr/\Q$param\E/i), "$param rejected";
   }
   is dies { $s3->presign(url => "$url?X%2DAmz%2DCredential=1") },
      bad_request(qr/credential/i), 'also when percent-encoded';
   ok lives { $s3->presign(url => "$url?X-Amz-Meta-Foo=1&versionId=3") },
      'other parameters are fine';
};

subtest 'host is always signed' => sub {
   my $r = $s3->sign(method => 'GET', url => $url, time => 0,
      signed_headers => ['x-amz-date']);
   is $r->{signed_headers}, 'host;x-amz-content-sha256;x-amz-date',
      'sign adds host, and the x-amz-* headers';
   $r = $s3->presign(url => $url, time => 0, signed_headers => ['x-amz-date'],
      headers => {'x-amz-date' => 'x'});
   is $r->{signed_headers}, 'host;x-amz-date', 'presign adds host';
};

subtest 'secrets of the x509 key are not kept around' => sub {
   my $dir = File::Temp::tempdir(CLEANUP => 1);
   system(qq{openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 }
      . qq{-aes256 -pass pass:TopSecretPass -out $dir/k.pem 2>/dev/null}) == 0
      or skip_all 'openssl needed';
   system(qq{openssl req -new -x509 -key $dir/k.pem -passin pass:TopSecretPass }
      . qq{-subj /CN=t -days 1 -out $dir/c.pem 2>/dev/null}) == 0
      or skip_all 'openssl needed';
   my $pem = do { local (@ARGV, $/) = "$dir/k.pem"; <> };
   my $s = AWS::Signature::V4->new(service => 'rolesanywhere', region => 'r',
      x509 => {key_type => 'ECDSA', certificate_file => "$dir/c.pem",
               private_key => $pem, private_key_password => 'TopSecretPass'});
   ok !exists $s->x509->{private_key}, 'no key text in ->x509';
   ok !exists $s->x509->{private_key_password}, 'no password in ->x509';
   my $dump = dump_of($s);
   unlike $dump, qr/TopSecretPass/, 'no password in a dump';
   unlike $dump, qr/ENCRYPTED PRIVATE KEY/, 'no key text in a dump';
   like $s->sign(method => 'GET', url => 'https://h/', time => 0)->{signature},
      qr/\A[0-9a-f]+\z/, 'signing still works';
};

subtest 'userinfo in the url is refused' => sub {
   for my $u ('http://a@evil.example@good.example/', 'http://user@good.example/') {
      is dies { $s3->sign(method => 'GET', url => $u) }, bad_request(qr/user/), $u;
   }
};

subtest 'caller input is escaped in error messages' => sub {
   for my $case (
      [sub { AWS::Signature::V4->new(service => 's', region => 'r',
         x509 => {key_type => "DSA\nFAKE LOG LINE"}) }, 'key_type'],
      [sub { $s3->sign(method => 'PUT', url => $url, streaming => 1,
         decoded_content_length => 1, checksum => "md5\nFAKE") }, 'checksum'],
      [sub { $s3->sign(method => 'PUT', url => $url, streaming => 1,
         decoded_content_length => 1, trailers => ["x\nFAKE"]) }, 'trailer name'],
      [sub { $s3->sign(method => 'GET', url => $url, signed_headers => ["x\nFAKE"]) },
         'signed header name'],
      [sub { AWS::Signature::V4->new(service => 's', region => 'r',
         x509 => {key_type => 'RSA', certificate => "\x30\x03\x02\x01\x05", serial => 1,
            private_key_file => "/no/such\nFAKE LOG LINE"}) }, 'private key file'],
      [sub { AWS::Signature::V4->new(service => 's', region => 'r',
         x509 => {key_type => 'RSA', certificate_file => "/no/such\nFAKE LOG LINE",
            serial => 1, signer => sub { 'x' }}) }, 'certificate file'],
   ) {
      my ($code, $name) = @$case;
      my $e = dies { $code->() };
      isa_ok $e, ['Ouch'], $name;
      unlike $e->message, qr/[\r\n]/, "$name: no line breaks in the message";
   }
};

done_testing;



( run in 0.581 second using v1.01-cache-2.11-cpan-64eb572602a )