Crypt-JWT

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN


0.038   2026-05-16
        - SECURITY:
            * constant-time MAC compare;
            * enforce JWK alg/use/key_ops and EC alg/crv consistency;
            * reject mixed-symmetry or duplicate-kid keysets;
            * cap PBES2 p2c and inflated payload size;
            * new $MIN_HMAC_KEY_LEN (4) and $MIN_RSA_BITS (2048);
            * new section SECURITY CONSIDERATIONS in POD
        - fix: ConcatKDF: INTEROP BREAK with <=0.037 for ECDH-ES + A192CBC-HS384 / A256CBC-HS512 only
        - fix: ECDH-ES apu/apv header values are base64url-decoded before KDF input
        - fix: AAD bit-length encoding (only diverged at AAD >= 512 MB)
        - fix: accepted_alg / accepted_enc now croak on unsupported types
        - aes_key_wrap/unwrap:
            * strict RFC 3394 (KW) vs RFC 5649 (KWP) modes;
            * ct length validation
            * fix unwrap of aligned KWP messages
        - require Compress::Raw::Zlib >= 2.057
        - new author-only Wycheproof harness t/wycheproof.t (AUTHOR_MODE=1)

0.037   2025-04-27

README.md  view on Meta::CPAN


    use Crypt::JWT ':all';

# FUNCTIONS

## decode\_jwt

    my $data              = decode_jwt(%named_args);
    my ($header, $data)   = decode_jwt(%named_args, decode_header=>1);

Returns the decoded payload (in scalar context) or the decoded header
followed by the decoded payload (when `decode_header => 1`). Croaks
on any verification, decryption, or claim-check failure.

Named arguments:

- token

    Mandatory. The serialized JWS or JWE token as a string. Both compact
    (`.`-separated, 3 segments for JWS / 5 for JWE) and flattened JSON
    serialization are accepted.

README.md  view on Meta::CPAN

- decode\_payload

    `0` - do not decode payload, return it as a raw string (octets).

    `1` - decode payload from JSON string, return it as perl hash ref (or array ref) - decode\_json failure means fatal error (croak).

    `undef` (default) - if possible decode payload from JSON string, if decode\_json fails return payload as a raw string (octets).

- decode\_header

    `0` (default) - `decode_jwt` returns just the decoded payload (scalar
    context).

    `1` - `decode_jwt` returns `($header, $payload)`; useful when you need
    to inspect the JWT header (e.g. `alg`, `kid`, `typ`).

        my $payload            = decode_jwt(token=>$t, key=>$k);
        my ($header, $payload) = decode_jwt(token=>$t, key=>$k, decode_header=>1);

- verify\_iss

lib/Crypt/JWT.pm  view on Meta::CPAN

    if !defined($value) || ref($value) || "$value" !~ /\A(?:0|[1-9][0-9]*)(?:\.[0-9]+)?\z/;
}

sub _verify_claims {
  my ($payload, %args) = @_;

  return if $args{ignore_claims};

  if (ref($payload) ne 'HASH') {
    # https://github.com/DCIT/perl-Crypt-JWT/issues/31
    # payload needs to be decoded into a HASH for checking any verify_XXXX
    for my $claim (qw(exp nbf iat iss sub aud jti)) {
      if (defined $args{"verify_$claim"} && $args{"verify_$claim"} != 0) {
        croak "JWT: cannot check verify_$claim (payload not decoded JSON/HASH)";
      }
    }
    return; # nothing to check
  }

  my $leeway = $args{leeway} || 0;
  my $now = time;

  ### exp
  if(defined $payload->{exp}) {

lib/Crypt/JWT.pm  view on Meta::CPAN


  use Crypt::JWT ':all';

=head1 FUNCTIONS

=head2 decode_jwt

 my $data              = decode_jwt(%named_args);
 my ($header, $data)   = decode_jwt(%named_args, decode_header=>1);

Returns the decoded payload (in scalar context) or the decoded header
followed by the decoded payload (when C<decode_header =E<gt> 1>). Croaks
on any verification, decryption, or claim-check failure.

Named arguments:

=over

=item token

Mandatory. The serialized JWS or JWE token as a string. Both compact
(C<.>-separated, 3 segments for JWS / 5 for JWE) and flattened JSON

lib/Crypt/JWT.pm  view on Meta::CPAN

=item decode_payload

C<0> - do not decode payload, return it as a raw string (octets).

C<1> - decode payload from JSON string, return it as perl hash ref (or array ref) - decode_json failure means fatal error (croak).

C<undef> (default) - if possible decode payload from JSON string, if decode_json fails return payload as a raw string (octets).

=item decode_header

C<0> (default) - C<decode_jwt> returns just the decoded payload (scalar
context).

C<1> - C<decode_jwt> returns C<($header, $payload)>; useful when you need
to inspect the JWT header (e.g. C<alg>, C<kid>, C<typ>).

 my $payload            = decode_jwt(token=>$t, key=>$k);
 my ($header, $payload) = decode_jwt(token=>$t, key=>$k, decode_header=>1);

=item verify_iss

lib/Crypt/KeyWrap.pm  view on Meta::CPAN

    return pack("N", 0) . pack("N", $n) if $Config{uvsize} == 4; #workaround
    return pack("N", $n >> 32) . pack("N", $n & 0xFFFFFFFF);
  }
  return pack("N", $n & 0xFFFFFFFF) if $bytes == 4;
}

sub _decode_ecdh_info {
  my ($name, $value) = @_;
  return '' unless defined $value;
  croak "concat_kdf: invalid $name" if ref $value;
  my $decoded = decode_b64u($value);
  croak "concat_kdf: invalid $name" unless defined $decoded && encode_b64u($decoded) eq $value;
  return $decoded;
}

sub aes_key_wrap {
  my ($kek, $pt_data, $cipher, $padding, $inverse) = @_;
  $cipher  = 'AES' unless defined $cipher;
  $padding = $cipher eq 'AES' ? 1 : 0 unless defined $padding;

  my ($A, $B, $P, $R);

  croak "aes_key_wrap: no KEK"     unless defined $kek;

t/jws_with_padding.t  view on Meta::CPAN

# MEQEIP////8AAAABAAAAAAAAAAAAAAAA///////////////8BCBaxjXYqjqT57Pr
# vVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwRBBGsX0fLhLEJH+Lzm5WOkQPJ3A32BLesz
# oPShOUXYmMKWT+NC4v4af5uO5+tKfA+eFivOM1drMV7Oy7ZAaDe/UfUCIQD/////
# AAAAAP//////////vOb6racXnoTzucrC/GMlUQIBAaFEA0IABLCZs5f55I9TnS52
# ClM2LY7Ui+9fVn1W7BAEmsgDbrY2J74jFoU+Rw4AxlGgQNgAcsaX6u9exFUjJHQL
# L8wnZ0o=
# -----END EC PRIVATE KEY-----
# EOF

my $token = 'eyJhbGciOiJFUzI1NiJ9.eyJoZWxsbyI6IndvcmxkIn0=.FOGAeCGvhKs-sQPUWQEmpdM0kC_yfi986ZW7XoT4pnlTKRLn43wDw6zVHdzEFFuy_JgsQFGYCfJQQds-5FF05w==';
my $decoded;

$decoded = eval { decode_jwt(token => $token, decode_payload => 0, key => \$ecc256Pub) };
is($decoded, undef, 'default (tolerate_padding => 0)');
$decoded = eval { decode_jwt(token => $token, tolerate_padding => 0, decode_payload => 0, key => \$ecc256Pub) };
is($decoded, undef, 'tolerate_padding => 0');
$decoded = eval { decode_jwt(token => $token, tolerate_padding => 1, decode_payload => 0, key => \$ecc256Pub) };
is($decoded, '{"hello":"world"}', 'tolerate_padding => 1');

done_testing;

t/jwt_encode_decode.t  view on Meta::CPAN

    'ES256K'=> [Crypt::PK::ECC->new($Ecc256KPrivate), Crypt::PK::ECC->new($Ecc256KPublic)],
    'ES384' => [Crypt::PK::ECC->new($Ecc512Private),  Crypt::PK::ECC->new($Ecc512Public)],
    'ES512' => [Crypt::PK::ECC->new($Ecc384Private),  Crypt::PK::ECC->new($Ecc384Public)],
);

for my $alg (sort keys %jwsalg) {
  my $k = ref $jwsalg{$alg} ? $jwsalg{$alg} : [ $jwsalg{$alg}, $jwsalg{$alg} ];
  my $payload = 'testik';
  my $token = encode_jwt(key=>$k->[0], payload=>$payload, alg=>$alg, allow_none=>1);
  ok($token, "token: alg=>$alg");
  my $decoded = decode_jwt(key=>$k->[1], token=>$token);
  is($decoded, 'testik', "decoded: alg=>$alg");
}

for my $alg (sort keys %jwealg) {
  for my $enc (@enclist) {
    my $k = ref $jwealg{$alg} ? $jwealg{$alg} : [ $jwealg{$alg}, $jwealg{$alg} ];
    my $payload = 'testik';
    my $token = encode_jwt(key=>$k->[1], payload=>$payload, alg=>$alg, enc=>$enc);
    ok($token, "token: enc=>$enc alg=>$alg");
    my $decoded = decode_jwt(key=>$k->[0], token=>$token);
    is($decoded, 'testik', "decoded: enc=>$enc alg=>$alg");
  }
}

for my $enc (@enclist) {
  my $alg = 'dir';
  my $key_size;
  if ($enc =~ /^A(128|192|256)CBC-HS/) {
    $key_size = 2*$1/8;
  }
  elsif ($enc =~ /^A(128|192|256)GCM/) {
    $key_size = $1/8;
  }
  my $k = 'x' x $key_size;
  my $payload = 'testik';
  my $token = encode_jwt(key=>$k, payload=>$payload, alg=>$alg, enc=>$enc);
  ok($token, "token: enc=>$enc alg=>$alg");
  my $decoded = decode_jwt(key=>$k, token=>$token, alg=>$alg, enc=>$enc);
  is($decoded, 'testik', "decoded: enc=>$enc alg=>$alg");

  my $payload_h = {str=>'žluťoučký kůň'};
  my $token_h = encode_jwt(key=>$k, payload=>$payload_h, alg=>$alg, enc=>$enc);
  ok($token_h, "token_h: enc=>$enc alg=>$alg");
  my $decoded_h = decode_jwt(key=>$k, token=>$token_h, alg=>$alg, enc=>$enc, decode_payload=>1);
  is($decoded_h->{str}, 'žluťoučký kůň', "decoded: enc=>$enc alg=>$alg");
}

{ # Ed25519 Signing: https://tools.ietf.org/html/rfc8037#appendix-A.4
  my $sk = '{"kty":"OKP","crv":"Ed25519","d":"nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A","x":"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"}';
  my $pk = '{"kty":"OKP","crv":"Ed25519","x":"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"}';
  my $payload = 'Example of Ed25519 signing';
  my $token = encode_jwt(key=>\$sk, payload=>$payload, alg=>'EdDSA');
  is($token, "eyJhbGciOiJFZERTQSJ9.RXhhbXBsZSBvZiBFZDI1NTE5IHNpZ25pbmc.hgyY0il_MGCjP0JzlnLWG1PPOt7-09PGcvMg3AIbQR6dWbhijcNR4ki4iylGjg5BhVsPt9g7sVvpAr_MuM0KAg");
  my $decoded = decode_jwt(key=>\$pk, token=>$token);
  is($decoded, "Example of Ed25519 signing");
}

{ # ECDH-ES with X25519 (XXX-TODO are there some official test vectors?)
  my $pk = { curve => "x25519", pub => "178395b303d01458736dd006f4f004c09d4514108ea269886bf1e864d8bc9864" };
  my $sk = { curve => "x25519", priv => "a807d597e769248f2c428e38ecb401ce97e229bcbe3055d92c1c9e82dabe10c3" };
  my $payload = 'Hello!';
  my $token = encode_jwt(key=>$pk, payload=>$payload, alg=>'ECDH-ES+A256KW', enc=>'A256GCM');
  my $decoded = decode_jwt(key=>$sk, token=>$token);
  is($decoded, "Hello!");
}

{ # https://github.com/DCIT/perl-Crypt-JWT/issues/31
  # verify_xxx options do not work with decode_payload=0
  my $h = { hello => 'world' };
  my $token = encode_jwt(key=>\$rsaPriv, payload=>$h, alg=>'RS256', relative_exp => 1000);
  ok($token);
  ok( decode_jwt(key=>\$rsaPub, token=>$token) );
  ok( decode_jwt(key=>\$rsaPub, token=>$token, verify_exp=>1, decode_payload=>undef) );
  ok( decode_jwt(key=>\$rsaPub, token=>$token, verify_exp=>1, decode_payload=>1) );

t/jwt_params.t  view on Meta::CPAN

OltsrOxscHq3xPYsJgxmmHGmhrlTKIv1YHjzZsteqZLokH3kr1sCEX+vS3lqaQP8
rmIjf2vAWi3inteZifZ2v48V8XPTOUky/YQvTEGDstHWVd74hhrCVfx+Jk7vjipr
-----END RSA PRIVATE KEY-----
EOF

my $k = '68yYPz1F17s4VWIIbEOB';
my $p = 'testik RANDOM=kDSIHckuMyz1JmCyKhhx Blexx!';
my $h = { body=>"hash", number=>123456, text=>"Hello" };
my $l = [ 11, 22, 33, 44, 55 ];
my ($alg, $enc);
my ($token, $decoded, $decoded_h);

for ([qw/PBES2-HS256+A128KW A128GCM/], ['HS512', '']) {
  ($alg, $enc) = @$_;

  $token = encode_jwt(key=>$k, payload=>$p, alg=>$alg, enc=>$enc, zip=>'deflate');
  ok($token, "deflate: enc=>'$enc' alg=>'$alg'");
  $decoded = decode_jwt(key=>$k, token=>$token);
  is($decoded, $p, "decoded - deflate: enc=>'$enc' alg=>'$alg'");

  $decoded = decode_jwt(key=>$k, token=>$token, accepted_alg=>$alg);
  is($decoded, $p, "decoded - accepted_alg/1: enc=>'$enc' alg=>qr/.+/");
  $decoded = decode_jwt(key=>$k, token=>$token, accepted_alg=>$alg);
  is($decoded, $p, "decoded - accepted_alg/2: enc=>'$enc' alg=>'$alg'");
  $decoded = decode_jwt(key=>$k, token=>$token, accepted_alg=>["XX", $alg, "YY"]);
  is($decoded, $p, "decoded - accepted_alg/3: enc=>'$enc' alg=>'$alg'");
  $decoded = eval { decode_jwt(key=>$k, token=>$token, accepted_alg=>["XX", "YY"]) };
  is($decoded, undef, "decoded - accepted_alg/4: enc=>'$enc' alg=>'$alg'");
  $decoded = eval { decode_jwt(key=>$k, token=>$token, accepted_alg=>"YY") };
  is($decoded, undef, "decoded - accepted_alg/5: enc=>'$enc' alg=>'$alg'");
  $decoded = eval { decode_jwt(key=>$k, token=>$token, accepted_alg=>qr/NOTFOUND/) };
  is($decoded, undef, "decoded - accepted_alg/6: enc=>'$enc' alg=>'$alg'");

  if ($enc) {
    # JWE only
    $decoded = decode_jwt(key=>$k, token=>$token, accepted_enc=>$enc);
    is($decoded, $p, "decoded - accepted_enc/1: enc=>'$enc' alg=>qr/.+/");
    $decoded = decode_jwt(key=>$k, token=>$token, accepted_enc=>$enc);
    is($decoded, $p, "decoded - accepted_enc/2: enc=>'$enc' alg=>'$alg'");
    $decoded = decode_jwt(key=>$k, token=>$token, accepted_enc=>["XX", $enc, "YY"]);
    is($decoded, $p, "decoded - accepted_enc/3: enc=>'$enc' alg=>'$alg'");
    $decoded = eval { decode_jwt(key=>$k, token=>$token, accepted_enc=>["XX", "YY"]) };
    is($decoded, undef, "decoded - accepted_enc/4: enc=>'$enc' alg=>'$alg'");
    $decoded = eval { decode_jwt(key=>$k, token=>$token, accepted_enc=>"YY") };
    is($decoded, undef, "decoded - accepted_enc/5: enc=>'$enc' alg=>'$alg'");
    $decoded = eval { decode_jwt(key=>$k, token=>$token, accepted_enc=>qr/NOTFOUND/) };
    is($decoded, undef, "decoded - accepted_enc/6: enc=>'$enc' alg=>'$alg'");
  }

  $token = encode_jwt(key=>$k, payload=>$p, alg=>$alg, enc=>$enc, zip=>['deflate', 1]);
  ok($token, "deflate+1: enc=>'$enc' alg=>'$alg'");
  $decoded = decode_jwt(key=>$k, token=>$token);
  is($decoded, $p, "decoded - deflate+1: enc=>'$enc' alg=>'$alg'");

  $token = encode_jwt(key=>$k, payload=>$h, alg=>$alg, enc=>$enc);
  ok($token, "hash: enc=>'$enc' alg=>'$alg'");
  $decoded = decode_jwt(key=>$k, token=>$token, decode_payload=>0);
  like($decoded, qr/"text":"Hello"/, "decoded - hash/1: enc=>'$enc' alg=>'$alg'");
  $decoded = decode_jwt(key=>$k, token=>$token, decode_payload=>1);
  is($decoded->{text}, "Hello", "decoded - hash/2: enc=>'$enc' alg=>'$alg'");
  $decoded = decode_jwt(key=>$k, token=>$token);
  is($decoded->{text}, "Hello", "decoded - hash/3: enc=>'$enc' alg=>'$alg'");

  $token = encode_jwt(key=>$k, payload=>$l, alg=>$alg, enc=>$enc);
  ok($token, "array: enc=>'$enc' alg=>'$alg'");
  $decoded = decode_jwt(key=>$k, token=>$token, decode_payload=>0);
  like($decoded, qr/\[11,22,33,44,55\]/, "decoded - array/1: enc=>'$enc' alg=>'$alg'");
  $decoded = decode_jwt(key=>$k, token=>$token, decode_payload=>1);
  is($decoded->[0], 11, "decoded - array/2: enc=>'$enc' alg=>'$alg'");
  $decoded = decode_jwt(key=>$k, token=>$token);
  is($decoded->[0], 11, "decoded - array/3: enc=>'$enc' alg=>'$alg'");

  my $keylist = {
    keys => [
      { kid=>"key1", kty=>"oct", k=>"GawgguFyGrWKav7AX4VKUg" },
      { kid=>"key2", kty=>"oct", k=>"ulxLGy4XqhbpkR5ObGh1gX" },
    ]
  };
  my $keylist_json = encode_json($keylist);
  $token = encode_jwt(key=>$keylist->{keys}->[1], extra_headers=>{kid=>"key2"}, payload=>$p, alg=>$alg, enc=>$enc);
  ok($token, "kid_keys: enc=>'$enc' alg=>'$alg'");
  $decoded = decode_jwt(kid_keys=>$keylist, token=>$token);
  is($decoded, $p, "decoded - kid_keys/1: enc=>'$enc' alg=>'$alg'");
  $decoded = decode_jwt(kid_keys=>$keylist_json, token=>$token);
  is($decoded, $p, "decoded - kid_keys/2: enc=>'$enc' alg=>'$alg'");

  $token = encode_jwt(key=>$k, payload=>$p, alg=>$alg, enc=>$enc, extra_headers=>{extra1=>11, extra2=>22});
  ($decoded_h, $decoded) = decode_jwt(key=>$k, token=>$token, decode_header=>1);
  is($decoded, $p, "decoded - decode_header/1: enc=>'$enc' alg=>'$alg'");
  is($decoded_h->{extra1}, 11, "decoded - decode_header/2: enc=>'$enc' alg=>'$alg'");

  if (!$enc) {
    #JWS only
    $token = encode_jwt(key=>$k, payload=>$p, alg=>$alg);
    ok($token, "ignore_signature: alg=>'$alg'");
    $decoded = decode_jwt(token=>$token, ignore_signature=>1);
    is($decoded, $p, "decoded - ignore_signature: alg=>'$alg'");

    my $claims = {
      iss => 'iss-string',
      aud => 'aud-string',
      sub => 'sub-string',
      jti => 'jti-string',
      iat => time,
      nbf => time,
      exp => time + 10,
      data => 'Hello',
    };
    $token = encode_jwt(key=>$k, payload=>$claims, alg=>$alg);
    ok($token, "claims: alg=>'$alg'");
    $decoded = decode_jwt(key=>$k, token=>$token);
    is($decoded->{data}, 'Hello', "decoded - claims/1: alg=>'$alg'");

    $decoded = decode_jwt(key=>$k, token=>$token, verify_iss=>sub { return 1 }, verify_aud=>sub { return 1 }, verify_sub=>sub { return 1 }, verify_jti=>sub { return 1 });
    is($decoded->{data}, 'Hello', "decoded - claims/2: alg=>'$alg'");
    $decoded = eval { decode_jwt(key=>$k, token=>$token, verify_iss=>sub { return 0 }, verify_aud=>sub { return 1 }, verify_sub=>sub { return 1 }, verify_jti=>sub { return 1 }) };
    is($decoded, undef, "decoded - claims/3: alg=>'$alg'");
    $decoded = eval { decode_jwt(key=>$k, token=>$token, verify_iss=>sub { return 1 }, verify_aud=>sub { return 0 }, verify_sub=>sub { return 1 }, verify_jti=>sub { return 1 }) };
    is($decoded, undef, "decoded - claims/4: alg=>'$alg'");
    $decoded = eval { decode_jwt(key=>$k, token=>$token, verify_iss=>sub { return 1 }, verify_aud=>sub { return 1 }, verify_sub=>sub { return 0 }, verify_jti=>sub { return 1 }) };
    is($decoded, undef, "decoded - claims/5: alg=>'$alg'");
    $decoded = eval { decode_jwt(key=>$k, token=>$token, verify_iss=>sub { return 1 }, verify_aud=>sub { return 1 }, verify_sub=>sub { return 1 }, verify_jti=>sub { return 0 }) };
    is($decoded, undef, "decoded - claims/6: alg=>'$alg'");

    $decoded = decode_jwt(key=>$k, token=>$token, verify_iss=>qr/string/, verify_aud=>qr/string/, verify_sub=>qr/string/, verify_jti=>qr/string/);
    is($decoded->{data}, 'Hello', "decoded - claims/7: alg=>'$alg'");
    $decoded = eval { decode_jwt(key=>$k, token=>$token, verify_iss=>qr/BADVAL/, verify_aud=>qr/string/, verify_sub=>qr/string/, verify_jti=>qr/string/) };
    is($decoded, undef, "decoded - claims/8: alg=>'$alg'");
    $decoded = eval { decode_jwt(key=>$k, token=>$token, verify_iss=>qr/string/, verify_aud=>qr/BADVAL/, verify_sub=>qr/string/, verify_jti=>qr/string/) };
    is($decoded, undef, "decoded - claims/9: alg=>'$alg'");
    $decoded = eval { decode_jwt(key=>$k, token=>$token, verify_iss=>qr/string/, verify_aud=>qr/string/, verify_sub=>qr/BADVAL/, verify_jti=>qr/string/) };
    is($decoded, undef, "decoded - claims/10: alg=>'$alg'");
    $decoded = eval { decode_jwt(key=>$k, token=>$token, verify_iss=>qr/string/, verify_aud=>qr/string/, verify_sub=>qr/string/, verify_jti=>qr/BADVAL/) };
    is($decoded, undef, "decoded - claims/11: alg=>'$alg'");

    $decoded = decode_jwt(key=>$k, token=>$token, verify_iss=>'iss-string', verify_aud=>'aud-string', verify_sub=>'sub-string', verify_jti=>'jti-string');
    is($decoded->{data}, 'Hello', "decoded - claims/12: alg=>'$alg'");
    $decoded = eval { decode_jwt(key=>$k, token=>$token, verify_iss=>'BADVAL', verify_aud=>'aud-string', verify_sub=>'sub-string', verify_jti=>'jti-string') };
    is($decoded, undef, "decoded - claims/13: alg=>'$alg'");
    $decoded = eval { decode_jwt(key=>$k, token=>$token, verify_iss=>'iss-string', verify_aud=>'BADVAL', verify_sub=>'sub-string', verify_jti=>'jti-string') };
    is($decoded, undef, "decoded - claims/14: alg=>'$alg'");
    $decoded = eval { decode_jwt(key=>$k, token=>$token, verify_iss=>'iss-string', verify_aud=>'aud-string', verify_sub=>'BADVAL', verify_jti=>'jti-string') };
    is($decoded, undef, "decoded - claims/15: alg=>'$alg'");
    $decoded = eval { decode_jwt(key=>$k, token=>$token, verify_iss=>'iss-string', verify_aud=>'aud-string', verify_sub=>'sub-string', verify_jti=>'BADVAL') };
    is($decoded, undef, "decoded - claims/16: alg=>'$alg'");

    # check for undef payload values or undef verify args
    $token = encode_jwt(key=>$k, payload=>{iat=>time, nbf=>time, exp=>time+10, iss=>undef, aud=>undef, sub=>undef, jti=>undef, data=>'Hello'}, alg=>$alg);
    $decoded = eval { decode_jwt(key=>$k, token=>$token, verify_iss=>qr/string/, verify_aud=>qr/string/, verify_sub=>qr/string/, verify_jti=>qr/BADVAL/) };
    is($decoded, undef, "decoded - claims_undef/1: alg=>'$alg'");
    $decoded = decode_jwt(key=>$k, token=>$token, verify_iss=>undef, verify_aud=>undef, verify_sub=>undef, verify_jti=>undef);
    is($decoded->{data}, 'Hello', "decoded - claims_undef/2: alg=>'$alg'");

    # iat
    $token = encode_jwt(key=>$k, payload=>{iat=>time+10, nbf=>time, exp=>time+10, data=>'Hello'}, alg=>$alg);
    $decoded = eval { decode_jwt(key=>$k, token=>$token) };
    is($decoded->{data}, 'Hello', "decoded - iat/1: alg=>'$alg'");
    $decoded = eval { decode_jwt(key=>$k, token=>$token, verify_iat=>undef) };
    is($decoded, undef, "decoded - iat/2: alg=>'$alg'");
    $decoded = eval { decode_jwt(key=>$k, token=>$token, verify_iat=>1) };
    is($decoded, undef, "decoded - iat/3: alg=>'$alg'");
    $decoded = eval { decode_jwt(key=>$k, token=>$token, verify_iat=>0) };
    is($decoded->{data}, 'Hello', "decoded - iat/4: alg=>'$alg'");
    $decoded = eval { decode_jwt(key=>$k, token=>$token, verify_iat=>1, leeway=>20) };
    is($decoded->{data}, 'Hello', "decoded - iat/5: alg=>'$alg'");

    # nbf
    $token = encode_jwt(key=>$k, payload=>{nbf=>time+10, exp=>time+20, data=>'Hello'}, alg=>$alg);
    $decoded = eval { decode_jwt(key=>$k, token=>$token) };
    is($decoded, undef, "decoded - nbf/1: alg=>'$alg'");
    $decoded = eval { decode_jwt(key=>$k, token=>$token, verify_nbf=>undef) };
    is($decoded, undef, "decoded - nbf/2: alg=>'$alg'");
    $decoded = eval { decode_jwt(key=>$k, token=>$token, verify_nbf=>1) };
    is($decoded, undef, "decoded - nbf/3: alg=>'$alg'");
    $decoded = eval { decode_jwt(key=>$k, token=>$token, verify_nbf=>0) };
    is($decoded->{data}, 'Hello', "decoded - nbf/4: alg=>'$alg'");
    $decoded = eval { decode_jwt(key=>$k, token=>$token, leeway=>20) };
    is($decoded->{data}, 'Hello', "decoded - nbf/5: alg=>'$alg'");

    # exp
    $token = encode_jwt(key=>$k, payload=>{exp=>time-5, data=>'Hello'}, alg=>$alg);
    $decoded = eval { decode_jwt(key=>$k, token=>$token) };
    is($decoded, undef, "decoded - exp/1: alg=>'$alg'");
    $decoded = eval { decode_jwt(key=>$k, token=>$token, verify_exp=>undef) };
    is($decoded, undef, "decoded - exp/2: alg=>'$alg'");
    $decoded = eval { decode_jwt(key=>$k, token=>$token, verify_exp=>1) };
    is($decoded, undef, "decoded - exp/3: alg=>'$alg'");
    $decoded = eval { decode_jwt(key=>$k, token=>$token, verify_exp=>0) };
    is($decoded->{data}, 'Hello', "decoded - exp/4: alg=>'$alg'");
    $decoded = eval { decode_jwt(key=>$k, token=>$token, leeway=>20) };
    is($decoded->{data}, 'Hello', "decoded - exp/5: alg=>'$alg'");

    $token = encode_jwt(key=>$k, payload=>{nbf=>0, iat=>0, exp=>time+10.5, data=>'Hello'}, alg=>$alg);
    $decoded = eval { decode_jwt(key=>$k, token=>$token, verify_iat=>1) };
    is($decoded->{data}, 'Hello', "decoded - NumericDate zero/fraction: alg=>'$alg'");

    for my $claim (qw(exp nbf iat)) {
      $token = encode_jwt(key=>$k, payload=>{$claim=>'not-a-number', data=>'Hello'}, alg=>$alg);
      my %verify = $claim eq 'iat' ? (verify_iat=>1) : ();
      $decoded = eval { decode_jwt(key=>$k, token=>$token, %verify) };
      is($decoded, undef, "decoded - invalid NumericDate/$claim: alg=>'$alg'");
    }

    $token = encode_jwt(key=>$k, payload=>{nbf=>time+10, iat=>time+10, exp=>time-10, data=>'Hello'}, alg=>$alg);
    $decoded = eval { decode_jwt(key=>$k, token=>$token) };
    is($decoded, undef, "ignore_claims/1: alg=>'$alg'");
    $decoded = eval { decode_jwt(key=>$k, token=>$token, ignore_claims=>1) };
    is($decoded->{data}, 'Hello', "ignore_claims/2: alg=>'$alg'");

    $token = encode_jwt(key=>$k, auto_iat=>1, relative_exp=>14, relative_nbf=>3, payload=>{data=>'Hello'}, alg=>$alg);
    ($decoded_h, $decoded) = decode_jwt(key=>$k, token=>$token, decode_header=>1, leeway=>4);
    my $iat = $decoded->{iat};
    ok(time - $iat < 2, "auto_iat/1");
    is($decoded->{nbf}, $iat+3,  "relative_nbf/1: alg=>'$alg'");
    is($decoded->{exp}, $iat+14, "relative_exp/1: alg=>'$alg'");

    $token = encode_jwt(key=>$k, auto_iat=>1, relative_exp=>-4, relative_nbf=>-13, payload=>{data=>'Hello'}, alg=>$alg);
    ($decoded_h, $decoded) = decode_jwt(key=>$k, token=>$token, decode_header=>1, leeway=>14);
    $iat = $decoded->{iat};
    ok(time - $iat < 2, "auto_iat/2");
    is($decoded->{nbf}, $iat-13,  "relative_nbf/2: alg=>'$alg'");
    is($decoded->{exp}, $iat-4, "relative_exp/2: alg=>'$alg'");

    $token = encode_jwt(key=>$k, payload=>{}, alg=>$alg);
    $decoded = eval { decode_jwt(key=>$k, token=>$token, verify_iss=>sub { return 1 }) };
    is($decoded, undef, "decoded - missing_claims/1: alg=>'$alg'");
    $decoded = eval { decode_jwt(key=>$k, token=>$token, verify_sub=>sub { return 1 }) };
    is($decoded, undef, "decoded - missing_claims/2: alg=>'$alg'");
    $decoded = eval { decode_jwt(key=>$k, token=>$token, verify_aud=>sub { return 1 }) };
    is($decoded, undef, "decoded - missing_claims/3: alg=>'$alg'");
    $decoded = eval { decode_jwt(key=>$k, token=>$token, verify_jti=>sub { return 1 }) };
    is($decoded, undef, "decoded - missing_claims/4: alg=>'$alg'");
  }
}

{
  $token = eval { encode_jwt(payload=>$p, alg=>'none') };
  ok(!defined $token, "allow_none/1: alg=>'$alg'");
  $token = encode_jwt(payload=>$p, alg=>'none', allow_none=>1);
  ok($token, "allow_none/2: alg=>'$alg'");
  $decoded = eval { decode_jwt(token=>$token) };
  is($decoded, undef, "decoded - allow_none/1: alg=>'$alg'");
  $decoded = decode_jwt(token=>$token, allow_none=>1);
  is($decoded, $p, "decoded - allow_none/2: alg=>'$alg'");
}

{
  my @tokens1 = (
    encode_jwt(key=>'Secretkey', payload=>{ x => 1, y => 2 }, alg=>'HS512'),
    encode_jwt(key=>'Secretkey', payload=>{ x => 1, y => 2 }, alg=>'PBES2-HS256+A128KW', enc=>'A128GCM'),
  );
  my @tokens2 = (
    encode_jwt(key=>'Secretkey', payload=>{ x => 1, y => 2 }, alg=>'HS512', extra_headers=>{ typ => "JWS" }),
    encode_jwt(key=>'Secretkey', payload=>{ x => 1, y => 2 }, alg=>'PBES2-HS256+A128KW', enc=>'A128GCM', extra_headers=>{ typ => "JWS" }),
  );
  for my $token (@tokens1) { ## w/o typ header
    $decoded = eval { decode_jwt(key=>'Secretkey', token=>$token, verify_typ=>'JWS') };
    is($decoded, undef, "typ_test/a: decoded - missing typ header (scalar)");
    $decoded = eval { decode_jwt(key=>'Secretkey', token=>$token, verify_typ=>qr/^JWS$/) };
    is($decoded, undef, "typ_test/a: decoded - missing typ header (re)");
    $decoded = eval { decode_jwt(key=>'Secretkey', token=>$token, verify_typ=>sub { return 1 }) };
    is($decoded, undef, "typ_test/a: decoded - missing typ header (sub)");
  }
  for my $token (@tokens2) { ## with typ header
    $decoded = eval { decode_jwt(key=>'Secretkey', token=>$token, verify_typ=>'JWS') };
    is($decoded->{x}, 1, "typ_test/b: typ (scalar)");
    $decoded = eval { decode_jwt(key=>'Secretkey', token=>$token, verify_typ=>'JXY') };
    is($decoded, undef, "typ_test/b: typ (scalar) FAIL");
    $decoded = eval { decode_jwt(key=>'Secretkey', token=>$token, verify_typ=>qr/^JWS$/) };
    is($decoded->{x}, 1, "typ_test/b: typ (re)");
    $decoded = eval { decode_jwt(key=>'Secretkey', token=>$token, verify_typ=>qr/JXY/) };
    is($decoded, undef, "typ_test/b: typ (re) FAIL");
    $decoded = eval { decode_jwt(key=>'Secretkey', token=>$token, verify_typ=>sub { return $_[0] eq 'JWS' }) };
    is($decoded->{x}, 1, "typ_test/b: typ (sub)");
    $decoded = eval { decode_jwt(key=>'Secretkey', token=>$token, verify_typ=>sub { return $_[0] eq 'JXY' }) };
    is($decoded, undef, "typ_test/b: typ (sub) FAIL");
  }
}

{
  $token = encode_jwt(key=>'Secretkey', payload=>{ aud => ['A1','A2'], x => 1, y => 2 }, alg=>'HS512'),
  $decoded = eval { decode_jwt(key=>'Secretkey', token=>$token, verify_aud => 'A1') };
  is($decoded->{x}, 1, "aud_list: scalar");
  $decoded = eval { decode_jwt(key=>'Secretkey', token=>$token, verify_aud => qr/^(A1|XYZ)$/) };
  is($decoded->{x}, 1, "aud_list: re");
  $decoded = eval { decode_jwt(key=>'Secretkey', token=>$token, verify_aud => sub { $_[0] eq 'A1' }) };
  is($decoded->{x}, 1, "aud_list: sub");
  $decoded = eval { decode_jwt(key=>'Secretkey', token=>$token, verify_aud => 'A9') };
  is($decoded, undef, "aud_list: scalar FAIL");
  $decoded = eval { decode_jwt(key=>'Secretkey', token=>$token, verify_aud => qr/^A9$/) };
  is($decoded, undef, "aud_list: re FAIL");
  $decoded = eval { decode_jwt(key=>'Secretkey', token=>$token, verify_aud => sub { $_[0] eq 'A9' }) };
  is($decoded, undef, "aud_list: sub FAIL");
}

done_testing;

t/kw_ecdh.t  view on Meta::CPAN

use warnings;
use Test::More;
use Crypt::KeyWrap qw(ecdh_key_wrap ecdh_key_unwrap);
use Crypt::PK::ECC;
use Crypt::Misc qw(decode_b64u);

my $kek_private=Crypt::PK::ECC->new(\'{"kty":"EC","crv":"P-256","x":"BHId3zoDv6pDgOUh8rKdloUZ0YumRTcaVDCppUPoYgk","y":"g3QIDhaWEksYtZ9OWjNHn9a6-i_P9o5_NrdISP0VWDU","d":"KpTnMOHEpskXvuXHFCfiRtGUHUZ9Dq5CCcZQ-19rYs4"}');

{
  # RFC 7518 Appendix C: apu/apv are base64url-encoded header values
  # ("Alice" => QWxpY2U, "Bob" => Qm9i) and must be decoded before KDF input.
  my $bob_private = Crypt::PK::ECC->new(\'{"kty":"EC","crv":"P-256","x":"weNJy2HscCSM6AEDTDg04biOvhFhyyWvOHQfeF_PxMQ","y":"e8lnCO-AlStT-NJVX-crhB7QRYhiix03illJOVAOyck","d":"VEmDZpDXXK8p8N0Cndsxs924q6nS1RXFASRl6BfUqdw"}');
  my $header = {
    alg => "ECDH-ES",
    enc => "A128GCM",
    apu => "QWxpY2U",
    apv => "Qm9i",
    epk => {
      kty => "EC",
      crv => "P-256",
      x   => "gI0GAILBdu7T53akrFmMyGcsF3n5dO7MmwNBHKW5SV0",

t/rfc8037.t  view on Meta::CPAN

#----------------------------------------------------------------------
# A.4 / A.5: Ed25519 signing and validation.
#----------------------------------------------------------------------
{
    my $jws_expected = "eyJhbGciOiJFZERTQSJ9".
                       ".RXhhbXBsZSBvZiBFZDI1NTE5IHNpZ25pbmc".
                       ".hgyY0il_MGCjP0JzlnLWG1PPOt7-09PGcvMg3AIbQR6dWbhijcNR4ki4iylGjg5BhVsPt9g7sVvpAr_MuM0KAg";
    my $payload = "Example of Ed25519 signing";

    # A.5: validation against the RFC's exact JWS bytes.
    my $decoded = decode_jwt(token => $jws_expected, key => $ed25519_pub_jwk, decode_payload => 0);
    is($decoded, $payload, "A.5 Ed25519 JWS validates and yields original payload");

    # A.4: Ed25519 is deterministic, so re-signing the same payload with
    # the same private key must reproduce the RFC token bit-for-bit.
    my $jws_produced = encode_jwt(
        payload => $payload,
        alg     => 'EdDSA',
        key     => $ed25519_priv_jwk,
    );
    is($jws_produced, $jws_expected, "A.4 Ed25519 signing reproduces RFC compact JWS");



( run in 2.616 seconds using v1.01-cache-2.11-cpan-99c4e6809bf )