commit fd8ac42b2a7328957060d43fc48fd2f3b6994817 Author: Mario Fetka Date: Thu Mar 22 15:48:49 2018 +0100 Imported Upstream version 0.018 diff --git a/Changes b/Changes new file mode 100644 index 0000000..fe0f88f --- /dev/null +++ b/Changes @@ -0,0 +1,54 @@ +Changes for Crypt-JWT distribution + +0.018 2016/08/31 + - doc fixes + - file perms fixes + +0.017 2016/06/03 + - doc fixes + +0.016 2016/05/12 + - require CryptX-0.034 (jws_no_key.t fails with older versions) + +0.015 2016/05/12 + - fix broken test jws_no_key.t + +0.014 2016/05/04 + - using Base64 en/decoding routines from CryptX also in tests + +0.013 2016/05/03 + - fix misused utf8:stuff + - fix 5.8.* compatibility + - using Base64 en/decoding routines from CryptX + +0.012 2016/05/02 + - JWS is now able to use the key from jwk header + - added support for Flattened JSON serialization (for both JWS and JWE) + +0.011 2015/10/22 + - switch to JSON::MaybeXS https://github.com/DCIT/perl-Crypt-JWT/pull/1 + +0.010 2015/07/07 + - INCOMPATIBLE CHANGE: 'key' param of decode_jwt and encode_jwt: + PEM/DER/JWK-JSON key strings are not passed as scalars but + as a reference to scalar (see examples in documentation) + - decode_jwt: kid_keys now strictly checks kty (key type) + +0.009 2015/07/04 + - decode_jwt: verify_iat default changed to 0 + - encode_jwt: auto_typ removed + - improved tests + +0.008 2015/07/04 + - decode_jwt: new parameter - ignore_claims + - fix related to nbf, iat, exp checks + +0.007 2015/07/04 + - decode_jwt: new parameter - kid_keys + +0.006 2015/07/03 + - after many changes first candidate for stable API + - incomplete tests + +0.001 2015/06/30 + - initial version diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a46bd29 --- /dev/null +++ b/LICENSE @@ -0,0 +1 @@ +This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. \ No newline at end of file diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..4513276 --- /dev/null +++ b/MANIFEST @@ -0,0 +1,21 @@ +Changes +lib/Crypt/JWT.pm +lib/Crypt/KeyWrap.pm +LICENSE +Makefile.PL +MANIFEST This list of files +README +t/compile.t +t/flattened.t +t/jws_no_key.t +t/jwt_decode_tv.t +t/jwt_encode_decode.t +t/jwt_params.t +t/kw_aes.t +t/kw_ecdh.t +t/kw_ecdhaes.t +t/kw_gcm.t +t/kw_pbes2.t +t/kw_rsa.t +META.yml Module YAML meta-data (added by MakeMaker) +META.json Module JSON meta-data (added by MakeMaker) diff --git a/META.json b/META.json new file mode 100644 index 0000000..e7cee27 --- /dev/null +++ b/META.json @@ -0,0 +1,54 @@ +{ + "abstract" : "JSON Web Token", + "author" : [ + "Karel Miko" + ], + "dynamic_config" : 1, + "generated_by" : "ExtUtils::MakeMaker version 7.18, CPAN::Meta::Converter version 2.150005", + "license" : [ + "perl_5" + ], + "meta-spec" : { + "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", + "version" : "2" + }, + "name" : "Crypt-JWT", + "no_index" : { + "directory" : [ + "t", + "inc" + ] + }, + "prereqs" : { + "build" : { + "requires" : { + "ExtUtils::MakeMaker" : "0" + } + }, + "configure" : { + "requires" : { + "ExtUtils::MakeMaker" : "0" + } + }, + "runtime" : { + "requires" : { + "Compress::Raw::Zlib" : "0", + "CryptX" : "0.034", + "Exporter" : "5.57", + "JSON::MaybeXS" : "1.003005", + "perl" : "5.006" + } + } + }, + "release_status" : "stable", + "resources" : { + "bugtracker" : { + "web" : "https://github.com/DCIT/perl-Crypt-JWT/issues" + }, + "repository" : { + "url" : "https://github.com/DCIT/perl-Crypt-JWT" + } + }, + "version" : "0.018", + "x_serialization_backend" : "JSON::PP version 2.27400" +} diff --git a/META.yml b/META.yml new file mode 100644 index 0000000..f3d4a53 --- /dev/null +++ b/META.yml @@ -0,0 +1,30 @@ +--- +abstract: 'JSON Web Token' +author: + - 'Karel Miko' +build_requires: + ExtUtils::MakeMaker: '0' +configure_requires: + ExtUtils::MakeMaker: '0' +dynamic_config: 1 +generated_by: 'ExtUtils::MakeMaker version 7.18, CPAN::Meta::Converter version 2.150005' +license: perl +meta-spec: + url: http://module-build.sourceforge.net/META-spec-v1.4.html + version: '1.4' +name: Crypt-JWT +no_index: + directory: + - t + - inc +requires: + Compress::Raw::Zlib: '0' + CryptX: '0.034' + Exporter: '5.57' + JSON::MaybeXS: '1.003005' + perl: '5.006' +resources: + bugtracker: https://github.com/DCIT/perl-Crypt-JWT/issues + repository: https://github.com/DCIT/perl-Crypt-JWT +version: '0.018' +x_serialization_backend: 'CPAN::Meta::YAML version 0.018' diff --git a/Makefile.PL b/Makefile.PL new file mode 100644 index 0000000..2834c4e --- /dev/null +++ b/Makefile.PL @@ -0,0 +1,31 @@ +use 5.006; +use strict; +use warnings; +use ExtUtils::MakeMaker; + +WriteMakefile( + NAME => 'Crypt::JWT', + AUTHOR => 'Karel Miko', + VERSION_FROM => 'lib/Crypt/JWT.pm', + ABSTRACT => 'JSON Web Token', + LICENSE => 'perl', + PREREQ_PM => { + 'JSON::MaybeXS' => '1.003005', # we need: (en|de)code_json + 'Exporter' => '5.57', # we need: use Exporter 'import'; + 'Compress::Raw::Zlib' => 0, + 'CryptX' => '0.034', + }, + MIN_PERL_VERSION => '5.006', + META_MERGE => { + resources => { + repository => 'https://github.com/DCIT/perl-Crypt-JWT', + bugtracker => 'https://github.com/DCIT/perl-Crypt-JWT/issues', + }, + }, + dist => { + PREOP => 'pod2text lib/Crypt/JWT.pm > README', + TAR => 'ptar', + TARFLAGS => '-c -C -f' + #TARFLAGS => '--owner=0 --group=0 -cvf', + }, +); diff --git a/README b/README new file mode 100644 index 0000000..42127f6 --- /dev/null +++ b/README @@ -0,0 +1,543 @@ +NAME + Crypt::JWT - JSON Web Token (JWT, JWS, JWE) as defined by RFC7519, + RFC7515, RFC7516 + +SYNOPSIS + # encoding + use Crypt::JWT qw(encode_jwt); + my $jws_token = encode_jwt(payload=>$data, alg=>'HS256', key=>'secret'); + my $jwe_token = encode_jwt(payload=>$data, alg=>'PBES2-HS256+A128KW', enc=>'A128GCM', key=>'secret'); + + # decoding + use Crypt::JWT qw(decode_jwt); + my $data1 = decode_jwt(token=>$jws_token, key=>'secret'); + my $data2 = decode_jwt(token=>$jwe_token, key=>'secret'); + +DESCRIPTION + Implements JSON Web Token (JWT) - . + The implementation covers not only JSON Web Signature (JWS) - + , but also JSON Web Encryption + (JWE) - . + + The module implements all (100%) algorithms defined in + - JSON Web Algorithms (JWA). + + This module supports Compact JWS/JWE and Flattened JWS/JWE JSON + serialization, general JSON serialization is not supported yet. + +EXPORT + Nothing is exported by default. + + You can export selected functions: + + use Crypt::JWT qw(decode_jwt encode_jwt); + + Or all of them at once: + + use Crypt::JWT ':all'; + +FUNCTIONS + decode_jwt + my $data = decode_jwt(%named_args); + + Named arguments: + + token + Mandatory argument, a string with either JWS or JWE JSON Web Token. + + ### JWS token example (3 segments) + $t = "eyJhbGciOiJIUzI1NiJ9.dGVzdA.ujBihtLSr66CEWqN74SpLUkv28lra_CeHnxLmLNp4Jo"; + my $data = decode_jwt(token=>$t, key=>$k); + + ### JWE token example (5 segments) + $t = "eyJlbmMiOiJBMTI4R0NNIiwiYWxnIjoiQTEyOEtXIn0.UusxEbzhGkORxTRq0xkFKhvzPrXb9smw.VGfOuq0Fxt6TsdqLZUpnxw.JajIQQ.pkKZ7MHS0XjyGmRsqgom6w"; + my $data = decode_jwt(token=>$t, key=>$k); + + key A key used for token decryption (JWE) or token signature validation + (JWS). If not given, and the token header contains a "jwk" header + value, that value will be used. The value depends on the "alg" token + header value. + + JWS alg header key value + ------------------ ---------------------------------- + none no key required + HS256 string (raw octects) of any length (or perl HASH ref with JWK, kty=>'oct') + HS384 dtto + HS512 dtto + RS256 public RSA key, perl HASH ref with JWK key structure, + a reference to SCALAR string with PEM or DER or JSON/JWK data, + object: Crypt::PK::RSA, Crypt::OpenSSL::RSA, Crypt::X509 or Crypt::OpenSSL::X509 + RS384 public RSA key, see RS256 + RS512 public RSA key, see RS256 + PS256 public RSA key, see RS256 + PS384 public RSA key, see RS256 + PS512 public RSA key, see RS256 + ES256 public ECC key, perl HASH ref with JWK key structure, + a reference to SCALAR string with PEM or DER or JSON/JWK data, + an instance of Crypt::PK::ECC + ES384 public ECC key, see ES256 + ES512 public ECC key, see ES256 + + JWE alg header key value + ------------------ ---------------------------------- + dir string (raw octects) or perl HASH ref with JWK, kty=>'oct', length depends on 'enc' algorithm + A128KW string (raw octects) 16 bytes (or perl HASH ref with JWK, kty=>'oct') + A192KW string (raw octects) 24 bytes (or perl HASH ref with JWK, kty=>'oct') + A256KW string (raw octects) 32 bytes (or perl HASH ref with JWK, kty=>'oct') + A128GCMKW string (raw octects) 16 bytes (or perl HASH ref with JWK, kty=>'oct') + A192GCMKW string (raw octects) 24 bytes (or perl HASH ref with JWK, kty=>'oct') + A256GCMKW string (raw octects) 32 bytes (or perl HASH ref with JWK, kty=>'oct') + PBES2-HS256+A128KW string (raw octects) of any length (or perl HASH ref with JWK, kty=>'oct') + PBES2-HS384+A192KW string (raw octects) of any length (or perl HASH ref with JWK, kty=>'oct') + PBES2-HS512+A256KW string (raw octects) of any length (or perl HASH ref with JWK, kty=>'oct') + RSA-OAEP private RSA key, perl HASH ref with JWK key structure, + a reference to SCALAR string with PEM or DER or JSON/JWK data, + an instance of Crypt::PK::RSA or Crypt::OpenSSL::RSA + RSA-OAEP-256 private RSA key, see RSA-OAEP + RSA1_5 private RSA key, see RSA-OAEP + ECDH-ES private ECC key, perl HASH ref with JWK key structure, + a reference to SCALAR string with PEM or DER or JSON/JWK data, + an instance of Crypt::PK::ECC + ECDH-ES+A128KW private ECC key, see ECDH-ES + ECDH-ES+A192KW private ECC key, see ECDH-ES + ECDH-ES+A256KW private ECC key, see ECDH-ES + + Example with key from "jwk" token header: + + my $data = decode_jwt(token=>$t); + my ($header, $data) = decode_jwt(token=>$t, decode_header=>1); + + Examples with raw octet keys: + + #string + my $data = decode_jwt(token=>$t, key=>'secretkey'); + #binary key + my $data = decode_jwt(token=>$t, key=>pack("H*", "788A6E38F36B7596EF6A669E94")); + #perl HASH ref with JWK structure (key type 'oct') + my $data = decode_jwt(token=>$t, key=>{kty=>'oct', k=>"GawgguFyGrWKav7AX4VKUg"}); + + Examples with RSA keys: + + my $pem_key_string = <<'EOF'; + -----BEGIN PRIVATE KEY----- + MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCoVm/Sl5r+Ofky + jioRSZK26GW6WyjyfWKddsSi13/NOtCn0rRErSF/u3QrgGMpWFqKohqbi1VVC+SZ + ... + 8c1vm2YFafgdkSk9Qd1oU2Fv1aOQy4VovOFzJ3CcR+2r7cbRfcpLGnintHtp9yek + 02p+d5g4OChfFNDhDtnIqjvY + -----END PRIVATE KEY----- + EOF + + my $jwk_key_json_string = '{"kty":"RSA","n":"0vx7agoebG...L6tSoc_BJECP","e":"AQAB"}'; + + #a reference to SCALAR string with PEM or DER or JSON/JWK data, + my $data = decode_jwt(token=>$t, key=>\$pem_key_string); + my $data = decode_jwt(token=>$t, key=>\$der_key_string); + my $data = decode_jwt(token=>$t, key=>\$jwk_key_json_string); + + #instance of Crypt::PK::RSA + my $data = decode_jwt(token=>$t, key=>Crypt::PK::RSA->new('keyfile.pem')); + my $data = decode_jwt(token=>$t, key=>Crypt::PK::RSA->new(\$pem_key_string)); + + #instance of Crypt::OpenSSL::RSA + my $data = decode_jwt(token=>$t, key=>Crypt::OpenSSL::RSA->new_private_key($pem_key_string)); + + #instance of Crypt::X509 (public key only) + my $data = decode_jwt(token=>$t, key=>Crypt::X509->new(cert=>$cert)); + + #instance of Crypt::OpenSSL::X509 (public key only) + my $data = decode_jwt(token=>$t, key=>Crypt::OpenSSL::X509->new_from_file('cert.pem')); + my $data = decode_jwt(token=>$t, key=>Crypt::OpenSSL::X509->new_from_string($cert)); + + #perl HASH ref with JWK structure (key type 'RSA') + my $rsa_priv = { + kty => "RSA", + n => "0vx7agoebGcQSuuPiLJXZpt...eZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", + e => "AQAB", + d => "X4cTteJY_gn4FYPsXB8rdXi...FLN5EEaG6RoVH-HLKD9Mdx5ooGURknhnrRwUkC7h5fJLMWbFAKLWY2v7B6NqSzUvx0_YSf", + p => "83i-7IvMGXoMXCskv73TKr8...Z27zvoj6pbUQyLPBQxtPnwD20-60eTmD2ujMt5PoMrm8RmNhVWtjjMmMjOpSicFHjXOuVI", + q => "3dfOR9cuYq-0S-mkFLzgItg...q3hWeMuG0ouqnb3obLyuqjVZQ1dIrdgTnCdYzBcOW5r37AFXjift_NGiovonzhKpoVVS78", + dp => "G4sPXkc6Ya9y8oJW9_ILj4...zi_H7TkS8x5SdX3oE0oiYwxIiemTAu0UOa5pgFGyJ4c8t2VF40XRugKTP8akhFo5tA77Qe", + dq => "s9lAH9fggBsoFR8Oac2R_E...T2kGOhvIllTE1efA6huUvMfBcpn8lqW6vzzYY5SSF7pMd_agI3G8IbpBUb0JiraRNUfLhc", + qi => "GyM_p6JrXySiz1toFgKbWV...4ypu9bMWx3QJBfm0FoYzUIZEVEcOqwmRN81oDAaaBk0KWGDjJHDdDmFW3AN7I-pux_mHZG", + }; + my $data = decode_jwt(token=>$t, key=>$rsa_priv}); + + Examples with ECC keys: + + my $pem_key_string = <<'EOF'; + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIBG1c3z52T8XwMsahGVdOZWgKCQJfv+l7djuJjgetdbDoAoGCCqGSM49 + AwEHoUQDQgAEoBUyo8CQAFPeYPvv78ylh5MwFZjTCLQeb042TjiMJxG+9DLFmRSM + lBQ9T/RsLLc+PmpB1+7yPAR+oR5gZn3kJQ== + -----END EC PRIVATE KEY----- + EOF + + my $jwk_key_json_string = '{"kty":"EC","crv":"P-256","x":"MKB..7D4","y":"4Et..FyM"}'; + + #a reference to SCALAR string with PEM or DER or JSON/JWK data, + my $data = decode_jwt(token=>$t, key=>\$pem_key_string); + my $data = decode_jwt(token=>$t, key=>\$der_key_string); + my $data = decode_jwt(token=>$t, key=>\$jwk_key_json_string); + + #instance of Crypt::PK::ECC + my $data = decode_jwt(token=>$t, key=>Crypt::PK::ECC->new('keyfile.pem')); + my $data = decode_jwt(token=>$t, key=>Crypt::PK::ECC->new(\$pem_key_string)); + + #perl HASH ref with JWK structure (key type 'RSA') + my $ecc_priv = { + kty => "EC", + crv => "P-256", + x => "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", + y => "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", + d => "870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE", + }; + my $data = decode_jwt(token=>$t, key=>$ecc_priv}); + + keypass + When 'key' parameter is an encrypted private RSA or ECC key this + optional parameter may contain a password for private key + decryption. + + kid_keys + This parametes can be either a JWK Set JSON string (see RFC7517) or + a perl HASH ref with JWK Set structure like this: + + my $keylist = { + keys => [ + { kid=>"key1", kty=>"oct", k=>"GawgguFyGrWKav7AX4VKUg" }, + { kid=>"key2", kty=>"oct", k=>"ulxLGy4XqhbpkR5ObGh1gX" }, + ] + }; + my $payload = decode_jwt(token=>$t, kid_keys=>$keylist); + + When the token header contains 'kid' item the corresponding key is + looked up in "kid_keys" list and used for token decoding (you do not + need to pass the explicit key via "key" parameter). + + allow_none + 1 - accept JWS tokens with "none" 'alg' header value (which means + that token has no signature), BEWARE: DANGEROUS, UNSECURE!!! + + 0 (default) - do not allow JWS with "none" 'alg' header value + + ignore_signature + 1 - do not check signature on JWS tokens, BEWARE: DANGEROUS, + UNSECURE!!! + + 0 (default) - check signature on JWS tokens + + accepted_alg + "undef" (default) means accept all 'alg' algorithms except 'none' + (for accepting 'none' use "allow_none") + + "string" name of accepted 'alg' algorithm (only one) + + "ARRAY ref" a list of accepted 'alg' algorithms + + "Regexp" that has to match 'alg' algorithm name + + my $payload = decode_jwt(token=>$t, key=>$k, accepted_alg=>'HS256'); + #or + my $payload = decode_jwt(token=>$t, key=>$k, accepted_alg=>['HS256','HS384']); + #or + my $payload = decode_jwt(token=>$t, key=>$k, accepted_alg=>qr/^HS(256|384|512)$/); + + accepted_enc + "undef" (default) means accept all 'enc' algorithms + + "string" name of accepted 'enc' algorithm (only one) + + "ARRAY ref" a list of accepted 'enc' algorithms + + "Regexp" that has to match 'enc' algorithm name + + my $payload = decode_jwt(token=>$t, key=>$k, accepted_enc=>'A192GCM'); + #or + my $payload = decode_jwt(token=>$t, key=>$k, accepted_enc=>['A192GCM','A256GCM']); + #or + my $payload = decode_jwt(token=>$t, key=>$k, accepted_enc=>qr/^A(128|192|256)GCM$/); + + decode_payload + 0 - do not decode payload, return it as a raw string (octects). + + 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) - do not return decoded header as a return value of + decode_jwt() + + 1 - return decoded header as a return value of decode_jwt() + + my $payload = decode_jwt(token=>$t, key=>$k); + #or + my ($header, $payload) = decode_jwt(token=>$t, key=>$k, decode_header=>1); + + verify_iss + "CODE ref" - subroutine (with 'iss' claim value passed as argument) + should return "true" otherwise verification fails + + "Regexp ref" - 'iss' claim value has to match given regexp otherwise + verification fails + + "undef" (default) - do not verify 'iss' claim + + verify_aud + "CODE ref" - subroutine (with 'aud' claim value passed as argument) + should return "true" otherwise verification fails + + "Regexp ref" - 'aud' claim value has to match given regexp otherwise + verification fails + + "undef" (default) - do not verify 'aud' claim + + verify_sub + "CODE ref" - subroutine (with 'sub' claim value passed as argument) + should return "true" otherwise verification fails + + "Regexp ref" - 'sub' claim value has to match given regexp otherwise + verification fails + + "undef" (default) - do not verify 'sub' claim + + verify_jti + "CODE ref" - subroutine (with 'jti' claim value passed as argument) + should return "true" otherwise verification fails + + "Regexp ref" - 'jti' claim value has to match given regexp otherwise + verification fails + + "undef" (default) - do not verify 'jti' claim + + verify_iat + "undef" - Issued At 'iat' claim must be valid (not in the future) if + present + + 0 (default) - ignore 'iat' claim + + 1 - require valid 'iat' claim + + verify_nbf + "undef" (default) - Not Before 'nbf' claim must be valid if present + + 0 - ignore 'nbf' claim + + 1 - require valid 'nbf' claim + + verify_exp + "undef" (default) - Expiration Time 'exp' claim must be valid if + present + + 0 - ignore 'exp' claim + + 1 - require valid 'exp' claim + + leeway + Tolerance in seconds related to "verify_exp", "verify_nbf" and + "verify_iat". Default is 0. + + ignore_claims + 1 - do not check claims (iat, exp, nbf, iss, aud, sub, jti), BEWARE: + DANGEROUS, UNSECURE!!! + + 0 (default) - check claims + + encode_jwt + my $token = encode_jwt(%named_args); + + Named arguments: + + payload + Value of this mandatory parameter can be a string/buffer or HASH ref + or ARRAY ref + + my $token = encode_jwt(payload=>"any raw data", key=>$k, alg=>'HS256'); + #or + my $token = encode_jwt(payload=>{a=>1,b=>2}, key=>$k, alg=>'HS256'); + #or + my $token = encode_jwt(payload=>[11,22,33,44], key=>$k, alg=>'HS256'); + + HASH refs and ARRAY refs payloads are serialized as JSON strings + + alg The 'alg' header value is mandatory for both JWE and JWS tokens. + + Supported JWE 'alg' algorithms: + + dir + A128KW + A192KW + A256KW + A128GCMKW + A192GCMKW + A256GCMKW + PBES2-HS256+A128KW + PBES2-HS384+A192KW + PBES2-HS512+A256KW + RSA-OAEP + RSA-OAEP-256 + RSA1_5 + ECDH-ES+A128KW + ECDH-ES+A192KW + ECDH-ES+A256KW + ECDH-ES + + Supported JWS algorithms: + + none ... no integrity (NOTE: disabled by default) + HS256 ... HMAC+SHA256 integrity + HS384 ... HMAC+SHA384 integrity + HS512 ... HMAC+SHA512 integrity + RS256 ... RSA+PKCS1-V1_5 + SHA256 signature + RS384 ... RSA+PKCS1-V1_5 + SHA384 signature + RS512 ... RSA+PKCS1-V1_5 + SHA512 signature + PS256 ... RSA+PSS + SHA256 signature + PS384 ... RSA+PSS + SHA384 signature + PS512 ... RSA+PSS + SHA512 signature + ES256 ... ECDSA + SHA256 signature + ES384 ... ECDSA + SHA384 signature + ES512 ... ECDSA + SHA512 signature + + enc The 'enc' header is mandatory for JWE tokens. + + Supported 'enc' algorithms: + + A128GCM + A192GCM + A256GCM + A128CBC-HS256 + A192CBC-HS384 + A256CBC-HS512 + + key A key used for token encryption (JWE) or token signing (JWS). The + value depends on "alg" token header value. + + JWS alg header key value + ------------------ ---------------------------------- + none no key required + HS256 string (raw octects) of any length (or perl HASH ref with JWK, kty=>'oct') + HS384 dtto + HS512 dtto + RS256 private RSA key, perl HASH ref with JWK key structure, + a reference to SCALAR string with PEM or DER or JSON/JWK data, + object: Crypt::PK::RSA, Crypt::OpenSSL::RSA, Crypt::X509 or Crypt::OpenSSL::X509 + RS384 private RSA key, see RS256 + RS512 private RSA key, see RS256 + PS256 private RSA key, see RS256 + PS384 private RSA key, see RS256 + PS512 private RSA key, see RS256 + ES256 private ECC key, perl HASH ref with JWK key structure, + a reference to SCALAR string with PEM or DER or JSON/JWK data, + an instance of Crypt::PK::ECC + ES384 private ECC key, see ES256 + ES512 private ECC key, see ES256 + + JWE alg header key value + ------------------ ---------------------------------- + dir string (raw octects) or perl HASH ref with JWK, kty=>'oct', length depends on 'enc' algorithm + A128KW string (raw octects) 16 bytes (or perl HASH ref with JWK, kty=>'oct') + A192KW string (raw octects) 24 bytes (or perl HASH ref with JWK, kty=>'oct') + A256KW string (raw octects) 32 bytes (or perl HASH ref with JWK, kty=>'oct') + A128GCMKW string (raw octects) 16 bytes (or perl HASH ref with JWK, kty=>'oct') + A192GCMKW string (raw octects) 24 bytes (or perl HASH ref with JWK, kty=>'oct') + A256GCMKW string (raw octects) 32 bytes (or perl HASH ref with JWK, kty=>'oct') + PBES2-HS256+A128KW string (raw octects) of any length (or perl HASH ref with JWK, kty=>'oct') + PBES2-HS384+A192KW string (raw octects) of any length (or perl HASH ref with JWK, kty=>'oct') + PBES2-HS512+A256KW string (raw octects) of any length (or perl HASH ref with JWK, kty=>'oct') + RSA-OAEP public RSA key, perl HASH ref with JWK key structure, + a reference to SCALAR string with PEM or DER or JSON/JWK data, + an instance of Crypt::PK::RSA or Crypt::OpenSSL::RSA + RSA-OAEP-256 public RSA key, see RSA-OAEP + RSA1_5 public RSA key, see RSA-OAEP + ECDH-ES public ECC key, perl HASH ref with JWK key structure, + a reference to SCALAR string with PEM or DER or JSON/JWK data, + an instance of Crypt::PK::ECC + ECDH-ES+A128KW public ECC key, see ECDH-ES + ECDH-ES+A192KW public ECC key, see ECDH-ES + ECDH-ES+A256KW public ECC key, see ECDH-ES + + keypass + When 'key' parameter is an encrypted private RSA or ECC key this + optional parameter may contain a password for private key + decryption. + + allow_none + 1 - allow JWS with "none" 'alg' header value (which means that token + has no signature), BEWARE: DANGEROUS, UNSECURE!!! + + 0 (default) - do not allow JWS with "none" 'alg' header value + + extra_headers + This optional parameter may contain a HASH ref with items that will + be added to JWT header. + + If you want to use PBES2-based 'alg' like "PBES2-HS512+A256KW" you + can set PBES2 salt len (p2s) in bytes and iteration count (p2c) via + "extra_headers" like this: + + my $token = encode_jwt(payload=>$p, key=>$k, alg=>'PBES2-HS512+A256KW', extra_headers=>{p2c=8000, p2s=>32}); + #NOTE: handling of p2s header is a special case, in the end it is replaced with the generated salt + + unprotected_headers + A hash with additional integrity unprotected headers - JWS and JWE + (not available for "compact" serialization); + + shared_unprotected_headers + A hash with additional integrity unprotected headers - JWE only (not + available for "compact" serialization); + + aad Additional Authenticated Data - scalar value with any (even raw + octects) data - JWE only (not available for "compact" + serialization); + + serialization + Specify serialization method: "compat" (= default) for Compact + JWS/JWE serialization or "flattened" for Flattened JWS/JWE JSON + serialization. + + General JSON serialization is not supported yet. + + zip Compression method, currently 'deflate' is the only one supported. + "undef" (default) means no compression. + + my $token = encode_jwt(payload=>$p, key=>$k, alg=>'HS256', zip=>'deflate'); + #or define compression level + my $token = encode_jwt(payload=>$p, key=>$k, alg=>'HS256', zip=>['deflate', 9]); + + auto_iat + 1 - set 'iat' (Issued At) claim to current time (epoch seconds since + 1970) at the moment of token encoding + + 0 (default) - do not set 'iat' claim + + NOTE: claims are part of the payload and can be used only if the + payload is a HASH ref! + + relative_exp + Set 'exp' claim (Expiration Time) to current time + "relative_exp" + value (in seconds). + + NOTE: claims are part of the payload and can be used only if the + payload is a HASH ref! + + relative_nbf + Set 'nbf' claim (Not Before) to current time + "relative_nbf" value + (in seconds). + + NOTE: claims are part of the payload and can be used only if the + payload is a HASH ref! + +SEE ALSO + Crypt::Cipher::AES, Crypt::AuthEnc::GCM, Crypt::PK::RSA, Crypt::PK::ECC, + Crypt::KeyDerivation, Crypt::KeyWrap + +LICENSE + This program is free software; you can redistribute it and/or modify it + under the same terms as Perl itself. + +COPYRIGHT + Copyright (c) 2015 DCIT, a.s. / Karel Miko + diff --git a/lib/Crypt/JWT.pm b/lib/Crypt/JWT.pm new file mode 100644 index 0000000..db61939 --- /dev/null +++ b/lib/Crypt/JWT.pm @@ -0,0 +1,1262 @@ +package Crypt::JWT; + +use strict; +use warnings; + +our $VERSION = '0.018'; + +use Exporter 'import'; +our %EXPORT_TAGS = ( all => [qw(decode_jwt encode_jwt)] ); +our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); +our @EXPORT = qw(); + +use Carp; +use Crypt::Misc qw(decode_b64u encode_b64u); +use JSON::MaybeXS qw(decode_json encode_json); +use Crypt::PK::RSA; +use Crypt::PK::ECC; +use Crypt::PRNG qw(random_bytes); +use Crypt::KeyWrap ':all'; +use Crypt::AuthEnc::GCM qw(gcm_encrypt_authenticate gcm_decrypt_verify); +use Crypt::Mac::HMAC qw(hmac); +use Compress::Raw::Zlib; +use Scalar::Util qw(looks_like_number); + +# JWS: https://tools.ietf.org/html/rfc7515 +# JWE: https://tools.ietf.org/html/rfc7516 +# JWK: https://tools.ietf.org/html/rfc7517 +# JWA: https://tools.ietf.org/html/rfc7518 +# JWT: https://tools.ietf.org/html/rfc7519 + +sub _prepare_rsa_key { + my ($key) = @_; + croak "JWT: undefined RSA key" unless defined $key; + croak "JWT: invalid RSA key (cannot be scalar)" unless ref $key; + # we need Crypt::PK::RSA object + return $key if ref($key) eq 'Crypt::PK::RSA'; + return Crypt::PK::RSA->new($key) if ref($key) eq 'HASH' || ref($key) eq 'SCALAR'; + return Crypt::PK::RSA->new(@$key) if ref($key) eq 'ARRAY'; + # handle also: Crypt::OpenSSL::RSA, Crypt::X509, Crypt::OpenSSL::X509 + my $str; + if (ref($key) eq 'Crypt::OpenSSL::RSA') { + # https://metacpan.org/pod/Crypt::OpenSSL::RSA + $str = $key->is_private ? $key->get_private_key_string : $key->get_public_key_string; + } + elsif (ref($key) =~ /^Crypt::(X509|OpenSSL::X509)$/) { + # https://metacpan.org/pod/Crypt::X509 + # https://metacpan.org/pod/Crypt::OpenSSL::X509 + $str = $key->pubkey; + } + return Crypt::PK::RSA->new(\$str) if defined $str && !ref($str); + croak "JWT: invalid RSA key"; +} + +sub _prepare_ecc_key { + my ($key) = @_; + croak "JWT: undefined ECC key" unless defined $key; + croak "JWT: invalid ECC key (cannot be scalar)" unless ref $key; + # we need Crypt::PK::ECC object + return $key if ref($key) eq 'Crypt::PK::ECC'; + return Crypt::PK::ECC->new($key) if ref($key) eq 'HASH' || ref($key) eq 'SCALAR'; + return Crypt::PK::ECC->new(@$key) if ref($key) eq 'ARRAY'; + croak "JWT: invalid ECC key"; +} + +sub _prepare_oct_key { + my ($key) = @_; + croak "JWT: undefined oct key" unless defined $key; + if (ref $key eq 'HASH' && $key->{k} && $key->{kty} && $key->{kty} eq 'oct') { + return decode_b64u($key->{k}); + } + elsif (!ref $key) { + return $key; + } + croak "JWT: invalid oct key"; +} + +sub _kid_lookup { + my ($kid, $kid_keys, $alg) = @_; + $kid_keys = decode_json($kid_keys) unless ref $kid_keys; + return undef unless ref $kid_keys eq 'HASH'; + return undef unless exists $kid_keys->{keys} && ref $kid_keys->{keys} eq 'ARRAY'; + my $found; + for (@{$kid_keys->{keys}}) { + if ($_->{kid} && $_->{kty} && $_->{kid} eq $kid) { + $found = $_; + last; + } + } + return undef if !$found; + return $found if $found->{kty} eq 'oct' && $alg =~ /^(HS|dir|PBES2-HS|A)/; + return $found if $found->{kty} eq 'EC' && $alg =~ /^(ES|EC)/; + return $found if $found->{kty} eq 'RSA' && $alg =~ /^RS/; + croak "JWT: key type '$found->{kty}' cannot be used with alg '$alg'"; +} + +sub _b64u_to_hash { + my $b64url = shift; + return undef unless $b64url; + my $json = decode_b64u($b64url); + return undef unless $json; + my $hash = decode_json($json); + return $hash; +} + +sub _add_claims { + my ($payload, %args) = @_; + #### claims (defined for JWS only) + # "exp" Expiration Time + # "nbf" Not Before + # "iat" Issued At + # "iss" Issuer + # "sub" Subject + # "aud" Audience + # "jti" JWT ID + my $now = time; + $payload->{iat} = $now if $args{auto_iat}; + $payload->{exp} = $now + $args{relative_exp} if defined $args{relative_exp}; + $payload->{nbf} = $now + $args{relative_nbf} if defined $args{relative_nbf}; +} + +sub _verify_claims { + my ($payload, %args) = @_; + + return if $args{ignore_claims}; + + my $leeway = $args{leeway} || 0; + my $now = time; + + ### exp + if(defined $payload->{exp}) { + if (!defined $args{verify_exp} || $args{verify_exp}==1) { + croak "JWT: exp claim check failed ($payload->{exp}/$leeway vs. $now)" if $payload->{exp} + $leeway < $now; + } + } + elsif ($args{verify_exp} && $args{verify_exp}==1) { + croak "JWT: exp claim required but missing" + } + + ### nbf + if(defined $payload->{nbf}) { + if (!defined $args{verify_nbf} || $args{verify_nbf}==1) { + croak "JWT: nbf claim check failed ($payload->{nbf}/$leeway vs. $now)" if $payload->{nbf} - $leeway > $now; + } + } + elsif ($args{verify_nbf} && $args{verify_nbf}==1) { + croak "JWT: nbf claim required but missing" + } + + ### iat + if (exists $args{verify_iat}) { #default (non existing verify_iat) == no iat check + if(defined $payload->{iat}) { + if (!defined $args{verify_iat} || $args{verify_iat}==1) { + croak "JWT: iat claim check failed ($payload->{iat}/$leeway vs. $now)" if $payload->{iat} - $leeway > $now; + } + } + elsif ($args{verify_iat} && $args{verify_iat}==1) { + croak "JWT: iat claim required but missing" + } + } + + ### iss + if(exists $payload->{iss}) { + if (ref $args{verify_iss} eq 'Regexp') { + croak "JWT: iss claim re check failed" unless $payload->{iss} =~ $args{verify_iss}; + } + elsif (ref $args{verify_iss} eq 'CODE') { + croak "JWT: iss claim check failed" unless $args{verify_iss}->($payload->{iss}); + } + } + + ### sub + if(exists $payload->{sub}) { + if (ref $args{verify_sub} eq 'Regexp') { + croak "JWT: sub claim re check failed" unless $payload->{sub} =~ $args{verify_sub}; + } + elsif (ref $args{verify_sub} eq 'CODE') { + croak "JWT: sub claim check failed" unless $args{verify_sub}->($payload->{sub}); + } + } + + ### aud + if(exists $payload->{aud}) { + if (ref $args{verify_aud} eq 'Regexp') { + croak "JWT: aud claim re check failed" unless $payload->{aud} =~ $args{verify_aud}; + } + elsif (ref $args{verify_aud} eq 'CODE') { + croak "JWT: aud claim check failed" unless $args{verify_aud}->($payload->{aud}); + } + } + + ### jti + if(exists $payload->{jti}) { + if (ref $args{verify_jti} eq 'Regexp') { + croak "JWT: jti claim re check failed" unless $payload->{jti} =~ $args{verify_jti}; + } + elsif (ref $args{verify_jti} eq 'CODE') { + croak "JWT: jti claim check failed" unless $args{verify_jti}->($payload->{jti}); + } + } +} + +sub _payload_zip { + my ($payload, $header, $z) = @_; + my @zip = ref $z eq 'ARRAY' ? @$z : ($z); + if ($zip[0] eq 'deflate') { + my $level = defined $zip[1] ? $zip[1] : 6; + $header->{zip} = "DEF"; + my $d = Compress::Raw::Zlib::Deflate->new(-Bufsize => 1024, -WindowBits => -&MAX_WBITS(), -AppendOutput => 1, -Level => $level ); + my $output = ''; + $d->deflate($payload, $output) == Z_OK or croak "JWT: deflate failed"; + $d->flush($output) == Z_OK or croak "JWT: deflate/flush failed"; + croak "JWT: deflate/output failed" unless $output; + $payload = $output; + } + else { + croak "JWT: unknown zip method '$zip[0]'"; + } + return $payload; +} + +sub _payload_unzip { + my ($payload, $z) = @_; + if ($z eq "DEF") { + my $d = Compress::Raw::Zlib::Inflate->new(-Bufsize => 1024, -WindowBits => -&MAX_WBITS()); + my $output = ''; + $d->inflate($payload, $output); + croak "JWT: inflate failed" unless $output; + $payload = $output; + } + else { + croak "JWT: unknown zip method '$z'"; + } + return $payload; +} + +sub _payload_enc { + my ($payload) = @_; + if (ref($payload) =~ /^(HASH|ARRAY)$/) { + $payload = encode_json($payload); + } + else { + utf8::downgrade($payload, 1) or croak "JWT: payload cannot contain wide character"; + } + return $payload; +} + +sub _payload_dec { + my ($payload, $decode_payload) = @_; + return $payload if defined $decode_payload && $decode_payload == 0; + my $de = $payload; + $de = eval { decode_json($de) }; + if ($decode_payload) { + croak "JWT: payload not a valid JSON" unless $de; + return $de; + } + else { + return defined $de ? $de : $payload; + } +} + +sub _encrypt_jwe_cek { + my ($key, $hdr) = @_; + my $alg = $hdr->{alg}; + my $enc = $hdr->{enc}; + + if ($alg eq 'dir') { + return (_prepare_oct_key($key), ''); + } + + my $cek; + my $ecek; + if ($enc =~ /^A(128|192|256)GCM/) { + $cek = random_bytes($1/8); + } + elsif ($enc =~ /^A(128|192|256)CBC/) { + $cek = random_bytes(2*$1/8); + } + + if ($alg =~ /^A(128|192|256)KW$/) { + $ecek = aes_key_wrap(_prepare_oct_key($key), $cek); + return ($cek, $ecek); + } + elsif ($alg =~ /^A(128|192|256)GCMKW$/) { + my ($t, $i); + ($ecek, $t, $i) = gcm_key_wrap(_prepare_oct_key($key), $cek); + $hdr->{tag} = encode_b64u($t); + $hdr->{iv} = encode_b64u($i); + return ($cek, $ecek); + } + elsif ($alg =~ /^PBES2-HS(512|384|256)\+A(128|192|256)KW$/) { + my $len = looks_like_number($hdr->{p2s}) && $hdr->{p2s} >= 8 && $hdr->{p2s} <= 9999 ? $hdr->{p2s} : 16; + my $salt = random_bytes($len); + my $iter = looks_like_number($hdr->{p2c}) ? $hdr->{p2c} : 5000; + $ecek = pbes2_key_wrap(_prepare_oct_key($key), $cek, $alg, $salt, $iter); + $hdr->{p2s} = encode_b64u($salt); + $hdr->{p2c} = $iter; + return ($cek, $ecek); + } + elsif ($alg =~ /^RSA(-OAEP|-OAEP-256|1_5)$/) { + $key = _prepare_rsa_key($key); + $ecek = rsa_key_wrap($key, $cek, $alg); + return ($cek, $ecek); + } + elsif ($alg =~ /^ECDH-ES\+A(128|192|256)KW$/) { + $key = _prepare_ecc_key($key); + ($ecek, $hdr->{epk}) = ecdhaes_key_wrap($key, $cek, $alg, $hdr->{apu}, $hdr->{apv}); + return ($cek, $ecek); + } + elsif ($alg eq 'ECDH-ES') { + $key = _prepare_ecc_key($key); + ($cek, $hdr->{epk}) = ecdh_key_wrap($key, $enc, $hdr->{apu}, $hdr->{apv}); + return ($cek, ''); + } + croak "JWE: unknown alg '$alg'"; +} + +sub _decrypt_jwe_cek { + my ($ecek, $key, $hdr) = @_; + my $alg = $hdr->{alg}; + my $enc = $hdr->{enc}; + + if ($alg eq 'dir') { + return _prepare_oct_key($key); + } + elsif ($alg =~ /^A(128|192|256)KW$/) { + return aes_key_unwrap(_prepare_oct_key($key), $ecek); + } + elsif ($alg =~ /^A(128|192|256)GCMKW$/) { + return gcm_key_unwrap(_prepare_oct_key($key), $ecek, decode_b64u($hdr->{tag}), decode_b64u($hdr->{iv})); + } + elsif ($alg =~ /^PBES2-HS(512|384|256)\+A(128|192|256)KW$/) { + return pbes2_key_unwrap(_prepare_oct_key($key), $ecek, $alg, decode_b64u($hdr->{p2s}), $hdr->{p2c}); + } + elsif ($alg =~ /^RSA(-OAEP|-OAEP-256|1_5)$/) { + $key = _prepare_rsa_key($key); + return rsa_key_unwrap($key, $ecek, $alg); + } + elsif ($alg =~ /^ECDH-ES\+A(128|192|256)KW$/) { + $key = _prepare_ecc_key($key); + return ecdhaes_key_unwrap($key, $ecek, $alg, $hdr->{epk}, $hdr->{apu}, $hdr->{apv}); + } + elsif ($alg eq 'ECDH-ES') { + $key = _prepare_ecc_key($key); + return ecdh_key_unwrap($key, $enc, $hdr->{epk}, $hdr->{apu}, $hdr->{apv}); + } + croak "JWE: unknown alg '$alg'"; +} + +sub _encrypt_jwe_payload { + my ($cek, $enc, $b64u_header, $b64u_aad, $payload) = @_; + my $aad = defined $b64u_aad ? "$b64u_header.$b64u_aad" : $b64u_header; + if ($enc =~ /^A(128|192|256)GCM$/) { + # https://tools.ietf.org/html/rfc7518#section-5.3 + my $len1 = $1/8; + my $len2 = length($cek); + croak "JWE: wrong AES key length ($len1 vs. $len2) for $enc" unless $len1 == $len2; + my $iv = random_bytes(16); # for AES always 16 + my ($ct, $tag) = gcm_encrypt_authenticate('AES', $cek, $iv, $aad, $payload); + return ($ct, $iv, $tag); + } + elsif ($enc =~ /^A(128|192|256)CBC-HS(256|384|512)$/) { + # https://tools.ietf.org/html/rfc7518#section-5.2 + my ($size, $hash) = ($1/8, "SHA$2"); + my $key_len = length($cek) / 2; + my $mac_key = substr($cek, 0, $key_len); + my $aes_key = substr($cek, $key_len, $key_len); + croak "JWE: wrong AES key length ($key_len vs. $size)" unless $key_len == $size; + my $iv = random_bytes(16); # for AES always 16 + my $m = Crypt::Mode::CBC->new('AES'); + my $ct = $m->encrypt($payload, $aes_key, $iv); + my $aad_len = length($aad); + my $mac_input = $aad . $iv . $ct . pack('N2', ($aad_len / 2147483647)*8, ($aad_len % 2147483647)*8); + my $mac = hmac($hash, $mac_key, $mac_input); + my $sig_len = length($mac) / 2; + my $sig = substr($mac, 0, $sig_len); + return ($ct, $iv, $sig); + } + croak "JWE: unsupported enc '$enc'"; +} + +sub _decrypt_jwe_payload { + my ($cek, $enc, $aad, $ct, $iv, $tag) = @_; + if ($enc =~ /^A(128|192|256)GCM$/) { + # https://tools.ietf.org/html/rfc7518#section-5.3 + my $len1 = $1/8; + my $len2 = length($cek); + croak "JWE: wrong AES key length ($len1 vs. $len2) for $enc" unless $len1 == $len2; + return gcm_decrypt_verify('AES', $cek, $iv, $aad, $ct, $tag); + } + elsif ($enc =~ /^A(128|192|256)CBC-HS(256|384|512)$/) { + # https://tools.ietf.org/html/rfc7518#section-5.2 + my ($size, $hash) = ($1/8, "SHA$2"); + my $key_len = length($cek) / 2; + my $mac_key = substr($cek, 0, $key_len); + my $aes_key = substr($cek, $key_len, $key_len); + croak "JWE: wrong AES key length ($key_len vs. $size)" unless $key_len == $size; + my $aad_len = length($aad); # AAD == original encoded header + my $mac_input = $aad . $iv . $ct . pack('N2', ($aad_len / 2147483647)*8, ($aad_len % 2147483647)*8); + my $mac = hmac($hash, $mac_key, $mac_input); + my $sig_len = length($mac) / 2; + my $sig = substr($mac, 0, $sig_len); + croak "JWE: tag mismatch" unless $sig eq $tag; + my $m = Crypt::Mode::CBC->new('AES'); + my $pt = $m->decrypt($ct, $aes_key, $iv); + return $pt; + } + croak "JWE: unsupported enc '$enc'"; +} + +sub _encode_jwe { + my %args = @_; + my $payload = $args{payload}; + my $alg = $args{alg}; + my $enc = $args{enc}; + my $header = $args{extra_headers} ? \%{$args{extra_headers}} : {}; + # add claims to payload + _add_claims($payload, %args) if ref $payload eq 'HASH'; + # serialize payload + $payload = _payload_enc($payload); + # compress payload + $payload = _payload_zip($payload, $header, $args{zip}) if $args{zip}; # may set some header items + # prepare header + $header->{alg} = $alg; + $header->{enc} = $enc; + #REMOVED: $header->{typ} = 'JWT' if !exists $header->{typ} && $args{auto_typ}; + # key + croak "JWE: missing 'key'" if !$args{key}; + my $key = defined $args{keypass} ? [$args{key}, $args{keypass}] : $args{key}; + # prepare cek + my ($cek, $ecek) = _encrypt_jwe_cek($key, $header); # adds some header items + # encode header + my $json_header = encode_json($header); + my $b64u_header = encode_b64u($json_header); + my $b64u_aad = defined $args{aad} ? encode_b64u($args{aad}) : undef; + # encrypt payload + my ($ct, $iv, $tag) = _encrypt_jwe_payload($cek, $enc, $b64u_header, $b64u_aad, $payload); + # return token parts + return ( $b64u_header, + encode_b64u($ecek), + encode_b64u($iv), + encode_b64u($ct), + encode_b64u($tag), + $b64u_aad); +} + +sub _decode_jwe { + my ($b64u_header, $b64u_ecek, $b64u_iv, $b64u_ct, $b64u_tag, $b64u_aad, $unprotected, $shared_unprotected, %args) = @_; + my $header = _b64u_to_hash($b64u_header); + my $ecek = decode_b64u($b64u_ecek); + my $ct = decode_b64u($b64u_ct); + my $iv = decode_b64u($b64u_iv); + my $tag = decode_b64u($b64u_tag); + + my $key = defined $args{keypass} ? [$args{key}, $args{keypass}] : $args{key}; + if ($header->{kid} && $args{kid_keys}) { + my $k = _kid_lookup($header->{kid}, $args{kid_keys}, $header->{alg}); + $key = $k if defined $k; + } + + my $aa = $args{accepted_alg}; + if (ref($aa) eq 'Regexp') { + croak "JWT: alg '$header->{alg}' does not match accepted_alg" if $header->{alg} !~ $aa; + } + elsif ($aa && (ref($aa) eq 'ARRAY' || !ref($aa))) { + my %acca = ref $aa ? map { $_ => 1 } @$aa : ( $aa => 1 ); + croak "JWT: alg '$header->{alg}' not in accepted_alg" if !$acca{$header->{alg}}; + } + + my $ae = $args{accepted_enc}; + if (ref($ae) eq 'Regexp') { + croak "JWT: enc '$header->{enc}' does not match accepted_enc" if $header->{enc} !~ $ae; + } + elsif ($ae && (ref($ae) eq 'ARRAY' || !ref($ae))) { + my %acce = ref $ae ? map { $_ => 1 } @$ae : ( $ae => 1 ); + croak "JWT: enc '$header->{enc}' not in accepted_enc" if !$acce{$header->{enc}}; + } + + croak "JWE: missing 'key'" if !$key; + $header = { %$shared_unprotected, %$unprotected, %$header }; # merge headers + my $cek = _decrypt_jwe_cek($ecek, $key, $header); + my $aad = defined $b64u_aad ? "$b64u_header.$b64u_aad" : $b64u_header; + my $payload = _decrypt_jwe_payload($cek, $header->{enc}, $aad, $ct, $iv, $tag); + $payload = _payload_unzip($payload, $header->{zip}) if $header->{zip}; + $payload = _payload_dec($payload, $args{decode_payload}); + _verify_claims($payload, %args) if ref $payload eq 'HASH'; # croaks on error + return ($header, $payload); +} + +sub _sign_jws { + my ($b64u_header, $b64u_payload, $alg, $key) = @_; + return '' if $alg eq 'none'; # no integrity + my $sig; + my $data = "$b64u_header.$b64u_payload"; + if ($alg =~ /^HS(256|384|512)$/) { # HMAC integrity + $key = _prepare_oct_key($key); + $sig = hmac("SHA$1", $key, $data); + } + elsif ($alg =~ /^RS(256|384|512)/) { # RSA+PKCS1-V1_5 signatures + my $pk = _prepare_rsa_key($key); + $sig = $pk->sign_message($data, "SHA$1", 'v1.5'); + } + elsif ($alg =~ /^PS(256|384|512)/) { # RSA+PSS signatures + my $hash = "SHA$1"; + my $hashlen = $1/8; + my $pk = _prepare_rsa_key($key); + $sig = $pk->sign_message($data, $hash, 'pss', $hashlen); + } + elsif ($alg =~ /^ES(256|384|512)/) { # ECDSA signatures + my $pk = _prepare_ecc_key($key); + $sig = $pk->sign_message_rfc7518($data, "SHA$1"); + } + return encode_b64u($sig); +} + +sub _verify_jws { + my ($b64u_header, $b64u_payload, $b64u_sig, $alg, $key) = @_; + my $sig = decode_b64u($b64u_sig); + my $data = "$b64u_header.$b64u_payload"; + + if ($alg eq 'none' ) { # no integrity + return 1; + } + elsif ($alg =~ /^HS(256|384|512)$/) { # HMAC integrity + $key = _prepare_oct_key($key); + return 1 if $sig eq hmac("SHA$1", $key, $data); + } + elsif ($alg =~ /^RS(256|384|512)/) { # RSA+PKCS1-V1_5 signatures + my $hash = "SHA$1"; + my $pk = _prepare_rsa_key($key); + return 1 if $pk->verify_message($sig, $data, $hash, 'v1.5'); + } + elsif ($alg =~ /^PS(256|384|512)/) { # RSA+PSS signatures + my $hash = "SHA$1"; + my $hashlen = $1/8; + my $pk = _prepare_rsa_key($key); + return 1 if $pk->verify_message($sig, $data, $hash, 'pss', $hashlen); + } + elsif ($alg =~ /^ES(256|384|512)/) { # ECDSA signatures + my $hash = "SHA$1"; + my $pk = _prepare_ecc_key($key); + return 1 if $pk->verify_message_rfc7518($sig, $data, $hash); + } + return 0; +} + +sub _encode_jws { + my %args = @_; + my $payload = $args{payload}; + my $alg = $args{alg}; + my $header = $args{extra_headers} ? \%{$args{extra_headers}} : {}; + croak "JWS: alg 'none' not allowed" if $alg eq 'none' && !$args{allow_none}; + # add claims to payload + _add_claims($payload, %args) if ref $payload eq 'HASH'; + # serialize payload + $payload = _payload_enc($payload); + # compress payload + $payload = _payload_zip($payload, $header, $args{zip}) if $args{zip}; # may set some header items + # encode payload + my $b64u_payload = encode_b64u($payload); + # prepare header + $header->{alg} = $alg; + #REMOVED: $header->{typ} = 'JWT' if !exists $header->{typ} && $args{auto_typ}; + # encode header + my $json_header = encode_json($header); + my $b64u_header = encode_b64u($json_header); + # key + croak "JWS: missing 'key'" if !$args{key} && $alg ne 'none'; + my $key = defined $args{keypass} ? [$args{key}, $args{keypass}] : $args{key}; + # sign header + my $b64u_signature = _sign_jws($b64u_header, $b64u_payload, $alg, $key); + return ($b64u_header, $b64u_payload, $b64u_signature); +} + +sub _decode_jws { + my ($b64u_header, $b64u_payload, $b64u_sig, $unprotected_header, %args) = @_; + my $header = _b64u_to_hash($b64u_header); + $unprotected_header = {} if ref $unprotected_header ne 'HASH'; + + if (!$args{ignore_signature}) { + my $aa = $args{accepted_alg}; + if (ref($aa) eq 'Regexp') { + croak "JWT: alg '$header->{alg}' does not match accepted_alg" if $header->{alg} !~ $aa; + } + elsif ($aa && (ref($aa) eq 'ARRAY' || !ref($aa))) { + my %acca = ref $aa ? map { $_ => 1 } @$aa : ( $aa => 1 ); + croak "JWT: alg '$header->{alg}' not in accepted_alg" if !$acca{$header->{alg}}; + } + my $alg = $header->{alg}; + croak "JWS: missing header 'alg'" unless $alg; + croak "JWS: alg 'none' not allowed" if $alg eq 'none' && !$args{allow_none}; + # key + my $key = defined $args{keypass} ? [$args{key}, $args{keypass}] : $args{key}; + my $kid = exists $header->{kid} ? $header->{kid} : $unprotected_header->{kid}; + if (!defined $key && defined $kid && $args{kid_keys}) { + my $k = _kid_lookup($kid, $args{kid_keys}, $alg); + $key = $k if defined $k; + } + # if no key given, try to use 'jwk' value from header + $key = $header->{jwk} if !$key && $header->{jwk}; + croak "JWS: missing 'key'" if !$key && $alg ne 'none'; + my $valid = _verify_jws($b64u_header, $b64u_payload, $b64u_sig, $alg, $key); + croak "JWS: decode failed" if !$valid; + } + my $payload = decode_b64u($b64u_payload); + $payload = _payload_unzip($payload, $header->{zip}) if $header->{zip}; + $payload = _payload_dec($payload, $args{decode_payload}); + _verify_claims($payload, %args) if ref $payload eq 'HASH'; # croaks on error + $header = { %$unprotected_header, %$header }; # merge headers + return ($header, $payload); +} + +sub encode_jwt { + my %args = @_; + + my $ser = $args{serialization} || 'compact'; + if ($args{alg} =~ /^(none|((HS|RS|PS|ES)(512|384|256)))$/) { + ###JWS + my ($b64u_header, $b64u_payload, $b64u_signature) = _encode_jws(%args); + if ($ser eq 'compact') { # https://tools.ietf.org/html/rfc7515#section-7.1 + croak "JWT: cannot use 'unprotected_headers' with compact serialization" if defined $args{unprotected_headers}; + return "$b64u_header.$b64u_payload.$b64u_signature"; + } + elsif ($ser eq 'flattened') { # https://tools.ietf.org/html/rfc7515#section-7.2.2 + my $token = { protected => $b64u_header, payload => $b64u_payload, signature => $b64u_signature }; + $token->{header} = \%{$args{unprotected_headers}} if ref $args{unprotected_headers} eq 'HASH'; + return encode_json($token); + } + else { + croak "JWT: unsupported JWS serialization '$ser'"; + } + } + else { + ### JWE + my ($b64u_header, $b64u_ecek, $b64u_iv, $b64u_ct, $b64u_tag, $b64u_aad) = _encode_jwe(%args); + if ($ser eq 'compact') { # https://tools.ietf.org/html/rfc7516#section-7.1 + croak "JWT: cannot use 'aad' with compact serialization" if defined $args{aad}; + croak "JWT: cannot use 'unprotected_headers' with compact serialization" if defined $args{unprotected_headers}; + croak "JWT: cannot use 'shared_unprotected_headers' with compact serialization" if defined $args{shared_unprotected_headers}; + return "$b64u_header.$b64u_ecek.$b64u_iv.$b64u_ct.$b64u_tag"; + } + elsif ($ser eq 'flattened') { # https://tools.ietf.org/html/rfc7516#section-7.2.2 + my $token = { + protected => $b64u_header, + encrypted_key => $b64u_ecek, + iv => $b64u_iv, + ciphertext => $b64u_ct, + tag => $b64u_tag, + }; + # header: JWE Per-Recipient Unprotected Header when the JWE Per-Recipient Unprotected Header + $token->{header} = \%{$args{unprotected_headers}} if ref $args{unprotected_headers} eq 'HASH'; + # unprotected: JWE Shared Unprotected Header + $token->{unprotected} = \%{$args{shared_unprotected_headers}} if ref $args{shared_unprotected_headers} eq 'HASH'; + # aad: Additional Authenticated Data (AAD) + $token->{aad} = $b64u_aad if defined $b64u_aad; + return encode_json($token); + } + else { + croak "JWT: unsupported JWE serialization '$ser'"; + } + } +} + +sub decode_jwt { + my %args = @_; + my ($header, $payload); + + if (!$args{token}) { + croak "JWT: missing token"; + } + elsif ($args{token} =~ /^([a-zA-Z0-9_-]+)\.([a-zA-Z0-9_-]*)\.([a-zA-Z0-9_-]+)\.([a-zA-Z0-9_-]+)\.([a-zA-Z0-9_-]+)$/) { + # JWE token (5 segments) + ($header, $payload) = _decode_jwe($1, $2, $3, $4, $5, undef, {}, {}, %args); + } + elsif ($args{token} =~ /^([a-zA-Z0-9_-]+)\.([a-zA-Z0-9_-]+)\.([a-zA-Z0-9_-]*)$/) { + # JWS token (3 segments) + ($header, $payload) = _decode_jws($1, $2, $3, {}, %args); + } + elsif ($args{token} =~ /^\s*\{.*?\}\s*$/s) { + my $hash = decode_json($args{token}); + if ($hash->{payload} && $hash->{protected}) { + # Flattened JWS JSON Serialization + ($header, $payload) = _decode_jws($hash->{protected}, $hash->{payload}, $hash->{signature}, $hash->{header}, %args); + } + elsif ($hash->{ciphertext} && $hash->{protected}) { + # Flattened JWE JSON Serialization + ($header, $payload) = _decode_jwe($hash->{protected}, $hash->{encrypted_key}, $hash->{iv}, $hash->{ciphertext}, $hash->{tag}, $hash->{aad}, $hash->{header}, $hash->{unprotected}, %args); + } + else { + croak "JWT: unsupported JWS/JWT JSON Serialization"; + } + } + else { + croak "JWT: invalid token format"; + } + return ($header, $payload) if $args{decode_header}; + return $payload; +} + +1; + +#### URLs +# https://metacpan.org/pod/JSON::WebToken +# https://metacpan.org/pod/Mojo::JWT +# https://bitbucket.org/b_c/jose4j/wiki/JWE%20Examples +# https://bitbucket.org/b_c/jose4j/wiki/JWS%20Examples +# https://github.com/dvsekhvalnov/jose-jwt/tree/master/JWT/jwe +# https://github.com/progrium/ruby-jwt +# https://github.com/jpadilla/pyjwt/ + +=pod + +=head1 NAME + +Crypt::JWT - JSON Web Token (JWT, JWS, JWE) as defined by RFC7519, RFC7515, RFC7516 + +=head1 SYNOPSIS + + # encoding + use Crypt::JWT qw(encode_jwt); + my $jws_token = encode_jwt(payload=>$data, alg=>'HS256', key=>'secret'); + my $jwe_token = encode_jwt(payload=>$data, alg=>'PBES2-HS256+A128KW', enc=>'A128GCM', key=>'secret'); + + # decoding + use Crypt::JWT qw(decode_jwt); + my $data1 = decode_jwt(token=>$jws_token, key=>'secret'); + my $data2 = decode_jwt(token=>$jwe_token, key=>'secret'); + +=head1 DESCRIPTION + +Implements B - L. +The implementation covers not only B - L, +but also B - L. + +The module implements B defined in L - B. + +This module supports B and B serialization, general JSON serialization is not supported yet. + +=head1 EXPORT + +Nothing is exported by default. + +You can export selected functions: + + use Crypt::JWT qw(decode_jwt encode_jwt); + +Or all of them at once: + + use Crypt::JWT ':all'; + +=head1 FUNCTIONS + +=head2 decode_jwt + + my $data = decode_jwt(%named_args); + +Named arguments: + +=over + +=item token + +Mandatory argument, a string with either JWS or JWE JSON Web Token. + + ### JWS token example (3 segments) + $t = "eyJhbGciOiJIUzI1NiJ9.dGVzdA.ujBihtLSr66CEWqN74SpLUkv28lra_CeHnxLmLNp4Jo"; + my $data = decode_jwt(token=>$t, key=>$k); + + ### JWE token example (5 segments) + $t = "eyJlbmMiOiJBMTI4R0NNIiwiYWxnIjoiQTEyOEtXIn0.UusxEbzhGkORxTRq0xkFKhvzPrXb9smw.VGfOuq0Fxt6TsdqLZUpnxw.JajIQQ.pkKZ7MHS0XjyGmRsqgom6w"; + my $data = decode_jwt(token=>$t, key=>$k); + +=item key + +A key used for token decryption (JWE) or token signature validation (JWS). +If not given, and the token header contains a C header value, that value will be used. +The value depends on the C token header value. + + JWS alg header key value + ------------------ ---------------------------------- + none no key required + HS256 string (raw octects) of any length (or perl HASH ref with JWK, kty=>'oct') + HS384 dtto + HS512 dtto + RS256 public RSA key, perl HASH ref with JWK key structure, + a reference to SCALAR string with PEM or DER or JSON/JWK data, + object: Crypt::PK::RSA, Crypt::OpenSSL::RSA, Crypt::X509 or Crypt::OpenSSL::X509 + RS384 public RSA key, see RS256 + RS512 public RSA key, see RS256 + PS256 public RSA key, see RS256 + PS384 public RSA key, see RS256 + PS512 public RSA key, see RS256 + ES256 public ECC key, perl HASH ref with JWK key structure, + a reference to SCALAR string with PEM or DER or JSON/JWK data, + an instance of Crypt::PK::ECC + ES384 public ECC key, see ES256 + ES512 public ECC key, see ES256 + + JWE alg header key value + ------------------ ---------------------------------- + dir string (raw octects) or perl HASH ref with JWK, kty=>'oct', length depends on 'enc' algorithm + A128KW string (raw octects) 16 bytes (or perl HASH ref with JWK, kty=>'oct') + A192KW string (raw octects) 24 bytes (or perl HASH ref with JWK, kty=>'oct') + A256KW string (raw octects) 32 bytes (or perl HASH ref with JWK, kty=>'oct') + A128GCMKW string (raw octects) 16 bytes (or perl HASH ref with JWK, kty=>'oct') + A192GCMKW string (raw octects) 24 bytes (or perl HASH ref with JWK, kty=>'oct') + A256GCMKW string (raw octects) 32 bytes (or perl HASH ref with JWK, kty=>'oct') + PBES2-HS256+A128KW string (raw octects) of any length (or perl HASH ref with JWK, kty=>'oct') + PBES2-HS384+A192KW string (raw octects) of any length (or perl HASH ref with JWK, kty=>'oct') + PBES2-HS512+A256KW string (raw octects) of any length (or perl HASH ref with JWK, kty=>'oct') + RSA-OAEP private RSA key, perl HASH ref with JWK key structure, + a reference to SCALAR string with PEM or DER or JSON/JWK data, + an instance of Crypt::PK::RSA or Crypt::OpenSSL::RSA + RSA-OAEP-256 private RSA key, see RSA-OAEP + RSA1_5 private RSA key, see RSA-OAEP + ECDH-ES private ECC key, perl HASH ref with JWK key structure, + a reference to SCALAR string with PEM or DER or JSON/JWK data, + an instance of Crypt::PK::ECC + ECDH-ES+A128KW private ECC key, see ECDH-ES + ECDH-ES+A192KW private ECC key, see ECDH-ES + ECDH-ES+A256KW private ECC key, see ECDH-ES + +Example with key from C token header: + + my $data = decode_jwt(token=>$t); + my ($header, $data) = decode_jwt(token=>$t, decode_header=>1); + +Examples with raw octet keys: + + #string + my $data = decode_jwt(token=>$t, key=>'secretkey'); + #binary key + my $data = decode_jwt(token=>$t, key=>pack("H*", "788A6E38F36B7596EF6A669E94")); + #perl HASH ref with JWK structure (key type 'oct') + my $data = decode_jwt(token=>$t, key=>{kty=>'oct', k=>"GawgguFyGrWKav7AX4VKUg"}); + +Examples with RSA keys: + + my $pem_key_string = <<'EOF'; + -----BEGIN PRIVATE KEY----- + MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCoVm/Sl5r+Ofky + jioRSZK26GW6WyjyfWKddsSi13/NOtCn0rRErSF/u3QrgGMpWFqKohqbi1VVC+SZ + ... + 8c1vm2YFafgdkSk9Qd1oU2Fv1aOQy4VovOFzJ3CcR+2r7cbRfcpLGnintHtp9yek + 02p+d5g4OChfFNDhDtnIqjvY + -----END PRIVATE KEY----- + EOF + + my $jwk_key_json_string = '{"kty":"RSA","n":"0vx7agoebG...L6tSoc_BJECP","e":"AQAB"}'; + + #a reference to SCALAR string with PEM or DER or JSON/JWK data, + my $data = decode_jwt(token=>$t, key=>\$pem_key_string); + my $data = decode_jwt(token=>$t, key=>\$der_key_string); + my $data = decode_jwt(token=>$t, key=>\$jwk_key_json_string); + + #instance of Crypt::PK::RSA + my $data = decode_jwt(token=>$t, key=>Crypt::PK::RSA->new('keyfile.pem')); + my $data = decode_jwt(token=>$t, key=>Crypt::PK::RSA->new(\$pem_key_string)); + + #instance of Crypt::OpenSSL::RSA + my $data = decode_jwt(token=>$t, key=>Crypt::OpenSSL::RSA->new_private_key($pem_key_string)); + + #instance of Crypt::X509 (public key only) + my $data = decode_jwt(token=>$t, key=>Crypt::X509->new(cert=>$cert)); + + #instance of Crypt::OpenSSL::X509 (public key only) + my $data = decode_jwt(token=>$t, key=>Crypt::OpenSSL::X509->new_from_file('cert.pem')); + my $data = decode_jwt(token=>$t, key=>Crypt::OpenSSL::X509->new_from_string($cert)); + + #perl HASH ref with JWK structure (key type 'RSA') + my $rsa_priv = { + kty => "RSA", + n => "0vx7agoebGcQSuuPiLJXZpt...eZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw", + e => "AQAB", + d => "X4cTteJY_gn4FYPsXB8rdXi...FLN5EEaG6RoVH-HLKD9Mdx5ooGURknhnrRwUkC7h5fJLMWbFAKLWY2v7B6NqSzUvx0_YSf", + p => "83i-7IvMGXoMXCskv73TKr8...Z27zvoj6pbUQyLPBQxtPnwD20-60eTmD2ujMt5PoMrm8RmNhVWtjjMmMjOpSicFHjXOuVI", + q => "3dfOR9cuYq-0S-mkFLzgItg...q3hWeMuG0ouqnb3obLyuqjVZQ1dIrdgTnCdYzBcOW5r37AFXjift_NGiovonzhKpoVVS78", + dp => "G4sPXkc6Ya9y8oJW9_ILj4...zi_H7TkS8x5SdX3oE0oiYwxIiemTAu0UOa5pgFGyJ4c8t2VF40XRugKTP8akhFo5tA77Qe", + dq => "s9lAH9fggBsoFR8Oac2R_E...T2kGOhvIllTE1efA6huUvMfBcpn8lqW6vzzYY5SSF7pMd_agI3G8IbpBUb0JiraRNUfLhc", + qi => "GyM_p6JrXySiz1toFgKbWV...4ypu9bMWx3QJBfm0FoYzUIZEVEcOqwmRN81oDAaaBk0KWGDjJHDdDmFW3AN7I-pux_mHZG", + }; + my $data = decode_jwt(token=>$t, key=>$rsa_priv}); + +Examples with ECC keys: + + my $pem_key_string = <<'EOF'; + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIBG1c3z52T8XwMsahGVdOZWgKCQJfv+l7djuJjgetdbDoAoGCCqGSM49 + AwEHoUQDQgAEoBUyo8CQAFPeYPvv78ylh5MwFZjTCLQeb042TjiMJxG+9DLFmRSM + lBQ9T/RsLLc+PmpB1+7yPAR+oR5gZn3kJQ== + -----END EC PRIVATE KEY----- + EOF + + my $jwk_key_json_string = '{"kty":"EC","crv":"P-256","x":"MKB..7D4","y":"4Et..FyM"}'; + + #a reference to SCALAR string with PEM or DER or JSON/JWK data, + my $data = decode_jwt(token=>$t, key=>\$pem_key_string); + my $data = decode_jwt(token=>$t, key=>\$der_key_string); + my $data = decode_jwt(token=>$t, key=>\$jwk_key_json_string); + + #instance of Crypt::PK::ECC + my $data = decode_jwt(token=>$t, key=>Crypt::PK::ECC->new('keyfile.pem')); + my $data = decode_jwt(token=>$t, key=>Crypt::PK::ECC->new(\$pem_key_string)); + + #perl HASH ref with JWK structure (key type 'RSA') + my $ecc_priv = { + kty => "EC", + crv => "P-256", + x => "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", + y => "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", + d => "870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE", + }; + my $data = decode_jwt(token=>$t, key=>$ecc_priv}); + +=item keypass + +When 'key' parameter is an encrypted private RSA or ECC key this optional parameter may contain a password for private key decryption. + +=item kid_keys + +This parametes can be either a JWK Set JSON string (see RFC7517) or a perl HASH ref with JWK Set structure like this: + + my $keylist = { + keys => [ + { kid=>"key1", kty=>"oct", k=>"GawgguFyGrWKav7AX4VKUg" }, + { kid=>"key2", kty=>"oct", k=>"ulxLGy4XqhbpkR5ObGh1gX" }, + ] + }; + my $payload = decode_jwt(token=>$t, kid_keys=>$keylist); + +When the token header contains 'kid' item the corresponding key is looked up in C list and used for token +decoding (you do not need to pass the explicit key via C parameter). + +=item allow_none + +C<1> - accept JWS tokens with C 'alg' header value (which means that token has no signature), B + +C<0> (default) - do not allow JWS with C 'alg' header value + +=item ignore_signature + +C<1> - do not check signature on JWS tokens, B + +C<0> (default) - check signature on JWS tokens + +=item accepted_alg + +C (default) means accept all 'alg' algorithms except 'none' (for accepting 'none' use C) + +C name of accepted 'alg' algorithm (only one) + +C a list of accepted 'alg' algorithms + +C that has to match 'alg' algorithm name + + my $payload = decode_jwt(token=>$t, key=>$k, accepted_alg=>'HS256'); + #or + my $payload = decode_jwt(token=>$t, key=>$k, accepted_alg=>['HS256','HS384']); + #or + my $payload = decode_jwt(token=>$t, key=>$k, accepted_alg=>qr/^HS(256|384|512)$/); + +=item accepted_enc + +C (default) means accept all 'enc' algorithms + +C name of accepted 'enc' algorithm (only one) + +C a list of accepted 'enc' algorithms + +C that has to match 'enc' algorithm name + + my $payload = decode_jwt(token=>$t, key=>$k, accepted_enc=>'A192GCM'); + #or + my $payload = decode_jwt(token=>$t, key=>$k, accepted_enc=>['A192GCM','A256GCM']); + #or + my $payload = decode_jwt(token=>$t, key=>$k, accepted_enc=>qr/^A(128|192|256)GCM$/); + +=item decode_payload + +C<0> - do not decode payload, return it as a raw string (octects). + +C<1> - decode payload from JSON string, return it as perl hash ref (or array ref) - decode_json failure means fatal error (croak). + +C (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) - do not return decoded header as a return value of decode_jwt() + +C<1> - return decoded header as a return value of decode_jwt() + + my $payload = decode_jwt(token=>$t, key=>$k); + #or + my ($header, $payload) = decode_jwt(token=>$t, key=>$k, decode_header=>1); + +=item verify_iss + +C - subroutine (with 'iss' claim value passed as argument) should return C otherwise verification fails + +C - 'iss' claim value has to match given regexp otherwise verification fails + +C (default) - do not verify 'iss' claim + +=item verify_aud + +C - subroutine (with 'aud' claim value passed as argument) should return C otherwise verification fails + +C - 'aud' claim value has to match given regexp otherwise verification fails + +C (default) - do not verify 'aud' claim + +=item verify_sub + +C - subroutine (with 'sub' claim value passed as argument) should return C otherwise verification fails + +C - 'sub' claim value has to match given regexp otherwise verification fails + +C (default) - do not verify 'sub' claim + +=item verify_jti + +C - subroutine (with 'jti' claim value passed as argument) should return C otherwise verification fails + +C - 'jti' claim value has to match given regexp otherwise verification fails + +C (default) - do not verify 'jti' claim + +=item verify_iat + +C - Issued At 'iat' claim must be valid (not in the future) if present + +C<0> (default) - ignore 'iat' claim + +C<1> - require valid 'iat' claim + +=item verify_nbf + +C (default) - Not Before 'nbf' claim must be valid if present + +C<0> - ignore 'nbf' claim + +C<1> - require valid 'nbf' claim + +=item verify_exp + +C (default) - Expiration Time 'exp' claim must be valid if present + +C<0> - ignore 'exp' claim + +C<1> - require valid 'exp' claim + +=item leeway + +Tolerance in seconds related to C, C and C. Default is C<0>. + +=item ignore_claims + +C<1> - do not check claims (iat, exp, nbf, iss, aud, sub, jti), B + +C<0> (default) - check claims + +=back + +=head2 encode_jwt + + my $token = encode_jwt(%named_args); + +Named arguments: + +=over + +=item payload + +Value of this mandatory parameter can be a string/buffer or HASH ref or ARRAY ref + + my $token = encode_jwt(payload=>"any raw data", key=>$k, alg=>'HS256'); + #or + my $token = encode_jwt(payload=>{a=>1,b=>2}, key=>$k, alg=>'HS256'); + #or + my $token = encode_jwt(payload=>[11,22,33,44], key=>$k, alg=>'HS256'); + +HASH refs and ARRAY refs payloads are serialized as JSON strings + +=item alg + +The 'alg' header value is mandatory for both JWE and JWS tokens. + +Supported JWE 'alg' algorithms: + + dir + A128KW + A192KW + A256KW + A128GCMKW + A192GCMKW + A256GCMKW + PBES2-HS256+A128KW + PBES2-HS384+A192KW + PBES2-HS512+A256KW + RSA-OAEP + RSA-OAEP-256 + RSA1_5 + ECDH-ES+A128KW + ECDH-ES+A192KW + ECDH-ES+A256KW + ECDH-ES + +Supported JWS algorithms: + + none ... no integrity (NOTE: disabled by default) + HS256 ... HMAC+SHA256 integrity + HS384 ... HMAC+SHA384 integrity + HS512 ... HMAC+SHA512 integrity + RS256 ... RSA+PKCS1-V1_5 + SHA256 signature + RS384 ... RSA+PKCS1-V1_5 + SHA384 signature + RS512 ... RSA+PKCS1-V1_5 + SHA512 signature + PS256 ... RSA+PSS + SHA256 signature + PS384 ... RSA+PSS + SHA384 signature + PS512 ... RSA+PSS + SHA512 signature + ES256 ... ECDSA + SHA256 signature + ES384 ... ECDSA + SHA384 signature + ES512 ... ECDSA + SHA512 signature + +=item enc + +The 'enc' header is mandatory for JWE tokens. + +Supported 'enc' algorithms: + + A128GCM + A192GCM + A256GCM + A128CBC-HS256 + A192CBC-HS384 + A256CBC-HS512 + +=item key + +A key used for token encryption (JWE) or token signing (JWS). The value depends on C token header value. + + JWS alg header key value + ------------------ ---------------------------------- + none no key required + HS256 string (raw octects) of any length (or perl HASH ref with JWK, kty=>'oct') + HS384 dtto + HS512 dtto + RS256 private RSA key, perl HASH ref with JWK key structure, + a reference to SCALAR string with PEM or DER or JSON/JWK data, + object: Crypt::PK::RSA, Crypt::OpenSSL::RSA, Crypt::X509 or Crypt::OpenSSL::X509 + RS384 private RSA key, see RS256 + RS512 private RSA key, see RS256 + PS256 private RSA key, see RS256 + PS384 private RSA key, see RS256 + PS512 private RSA key, see RS256 + ES256 private ECC key, perl HASH ref with JWK key structure, + a reference to SCALAR string with PEM or DER or JSON/JWK data, + an instance of Crypt::PK::ECC + ES384 private ECC key, see ES256 + ES512 private ECC key, see ES256 + + JWE alg header key value + ------------------ ---------------------------------- + dir string (raw octects) or perl HASH ref with JWK, kty=>'oct', length depends on 'enc' algorithm + A128KW string (raw octects) 16 bytes (or perl HASH ref with JWK, kty=>'oct') + A192KW string (raw octects) 24 bytes (or perl HASH ref with JWK, kty=>'oct') + A256KW string (raw octects) 32 bytes (or perl HASH ref with JWK, kty=>'oct') + A128GCMKW string (raw octects) 16 bytes (or perl HASH ref with JWK, kty=>'oct') + A192GCMKW string (raw octects) 24 bytes (or perl HASH ref with JWK, kty=>'oct') + A256GCMKW string (raw octects) 32 bytes (or perl HASH ref with JWK, kty=>'oct') + PBES2-HS256+A128KW string (raw octects) of any length (or perl HASH ref with JWK, kty=>'oct') + PBES2-HS384+A192KW string (raw octects) of any length (or perl HASH ref with JWK, kty=>'oct') + PBES2-HS512+A256KW string (raw octects) of any length (or perl HASH ref with JWK, kty=>'oct') + RSA-OAEP public RSA key, perl HASH ref with JWK key structure, + a reference to SCALAR string with PEM or DER or JSON/JWK data, + an instance of Crypt::PK::RSA or Crypt::OpenSSL::RSA + RSA-OAEP-256 public RSA key, see RSA-OAEP + RSA1_5 public RSA key, see RSA-OAEP + ECDH-ES public ECC key, perl HASH ref with JWK key structure, + a reference to SCALAR string with PEM or DER or JSON/JWK data, + an instance of Crypt::PK::ECC + ECDH-ES+A128KW public ECC key, see ECDH-ES + ECDH-ES+A192KW public ECC key, see ECDH-ES + ECDH-ES+A256KW public ECC key, see ECDH-ES + +=item keypass + +When 'key' parameter is an encrypted private RSA or ECC key this optional parameter may contain a password for private key decryption. + +=item allow_none + +C<1> - allow JWS with C 'alg' header value (which means that token has no signature), B + +C<0> (default) - do not allow JWS with C 'alg' header value + +=item extra_headers + +This optional parameter may contain a HASH ref with items that will be added to JWT header. + +If you want to use PBES2-based 'alg' like C you can set PBES2 salt len (p2s) in bytes and +iteration count (p2c) via C like this: + + my $token = encode_jwt(payload=>$p, key=>$k, alg=>'PBES2-HS512+A256KW', extra_headers=>{p2c=8000, p2s=>32}); + #NOTE: handling of p2s header is a special case, in the end it is replaced with the generated salt + +=item unprotected_headers + +A hash with additional integrity unprotected headers - JWS and JWE (not available for C serialization); + +=item shared_unprotected_headers + +A hash with additional integrity unprotected headers - JWE only (not available for C serialization); + +=item aad + +Additional Authenticated Data - scalar value with any (even raw octects) data - JWE only (not available for C serialization); + +=item serialization + +Specify serialization method: C (= default) for Compact JWS/JWE serialization or C for Flattened JWS/JWE JSON serialization. + +General JSON serialization is not supported yet. + +=item zip + +Compression method, currently 'deflate' is the only one supported. C (default) means no compression. + + my $token = encode_jwt(payload=>$p, key=>$k, alg=>'HS256', zip=>'deflate'); + #or define compression level + my $token = encode_jwt(payload=>$p, key=>$k, alg=>'HS256', zip=>['deflate', 9]); + +=item auto_iat + +C<1> - set 'iat' (Issued At) claim to current time (epoch seconds since 1970) at the moment of token encoding + +C<0> (default) - do not set 'iat' claim + +NOTE: claims are part of the payload and can be used only if the payload is a HASH ref! + +=item relative_exp + +Set 'exp' claim (Expiration Time) to current time + C value (in seconds). + +NOTE: claims are part of the payload and can be used only if the payload is a HASH ref! + +=item relative_nbf + +Set 'nbf' claim (Not Before) to current time + C value (in seconds). + +NOTE: claims are part of the payload and can be used only if the payload is a HASH ref! + +=back + +=head1 SEE ALSO + +L, L, L, L, L, L + +=head1 LICENSE + +This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. + +=head1 COPYRIGHT + +Copyright (c) 2015 DCIT, a.s. L / Karel Miko diff --git a/lib/Crypt/KeyWrap.pm b/lib/Crypt/KeyWrap.pm new file mode 100644 index 0000000..e9ec735 --- /dev/null +++ b/lib/Crypt/KeyWrap.pm @@ -0,0 +1,603 @@ +package Crypt::KeyWrap; + +use strict; +use warnings; + +our $VERSION = '0.018'; + +use Exporter 'import'; +our %EXPORT_TAGS = ( all => [qw(aes_key_wrap aes_key_unwrap gcm_key_wrap gcm_key_unwrap pbes2_key_wrap pbes2_key_unwrap ecdh_key_wrap ecdh_key_unwrap ecdhaes_key_wrap ecdhaes_key_unwrap rsa_key_wrap rsa_key_unwrap)] ); +our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); +our @EXPORT = qw(); + +use Carp; +use Crypt::Mode::ECB; +use Crypt::AuthEnc::GCM qw(gcm_encrypt_authenticate gcm_decrypt_verify); +use Crypt::PRNG qw(random_bytes); +use Crypt::KeyDerivation qw(pbkdf2); +use Crypt::Digest qw(digest_data); +use Config; + +# JWS: https://tools.ietf.org/html/rfc7515 +# JWE: https://tools.ietf.org/html/rfc7516 +# JWK: https://tools.ietf.org/html/rfc7517 +# JWA: https://tools.ietf.org/html/rfc7518 - !!! this is important !!! + +sub _LSB { + my ($bytes, $data) = @_; + my $len = length $data; + return $len > $bytes ? substr($data, $len-$bytes, $bytes) : $data; +} + +sub _MSB { + my ($bytes, $data) = @_; + my $len = length $data; + return $len > $bytes ? substr($data, 0, $bytes) : $data; +} + +sub _N2RAW { + my ($bytes, $n) = @_; + if ($bytes == 8) { + 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 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; + croak "aes_key_wrap: no PT data" unless defined $pt_data; + my $klen = length $kek; + croak "aes_key_wrap: invalid KEK length" unless $klen == 16 || $klen == 24 || $klen == 32; + croak "aes_key_wrap: cipher must be AES or DES_EDE" unless $cipher eq 'AES' || $cipher eq 'DES_EDE'; + croak "aes_key_wrap: padding not allowed with DES_EDE" if $padding && $cipher eq 'DES_EDE'; + + my $ECB = Crypt::Mode::ECB->new($cipher, 0); + my $blck = $cipher eq 'DES_EDE' ? 4 : 8; # semiblock size in bytes, for AES 8, for 3DES 4 + + my $IV = pack("H*", "A6" x $blck); + my $len = length $pt_data; + if ($len % $blck > 0) { + croak "aes_key_wrap: pt_data length not multiply of $blck" if !$padding; + $pt_data .= chr(0) x ($blck - ($len % $blck)); + $IV = pack("H*", "A65959A6") . pack("N", $len); + } + + my $n = length($pt_data) / $blck; + $P->[$_] = substr($pt_data, $_*$blck, $blck) for (0..$n-1); + + if ($n == 1) { + return $inverse ? $ECB->decrypt($IV . $P->[0], $kek) + : $ECB->encrypt($IV . $P->[0], $kek); + } + + $A = $IV; + $R->[$_] = $P->[$_] for (0..$n-1); + + for my $j (0..5) { + for my $i (0..$n-1) { + $B = $inverse ? $ECB->decrypt($A . $R->[$i], $kek) + : $ECB->encrypt($A . $R->[$i], $kek); + $A = _MSB($blck, $B) ^ _N2RAW($blck, ($n*$j)+$i+1); + $R->[$i] = _LSB($blck, $B); + } + } + + my $rv = $A; + $rv .= $R->[$_] for (0..$n-1); + return $rv; +} + +sub aes_key_unwrap { + my ($kek, $ct_data, $cipher, $padding, $inverse) = @_; + $cipher = 'AES' unless defined $cipher; + $padding = $cipher eq 'AES' ? 1 : 0 unless defined $padding; + + my ($A, $B, $C, $P, $R); + + croak "aes_key_unwrap: no KEK" unless defined $kek; + croak "aes_key_unwrap: no CT data" unless defined $ct_data; + my $klen = length $kek; + croak "aes_key_unwrap: invalid KEK length" unless $klen == 16 || $klen == 24 || $klen == 32; + croak "aes_key_unwrap: cipher must be AES or DES_EDE" unless $cipher eq 'AES' || $cipher eq 'DES_EDE'; + croak "aes_key_unwrap: padding not allowed with DES_EDE" if $padding && $cipher eq 'DES_EDE'; + + my $ECB = Crypt::Mode::ECB->new($cipher, 0); + my $blck = $cipher eq 'DES_EDE' ? 4 : 8; # semiblock size in bytes, for AES 8, for 3DES 4 + + my $n = length($ct_data) / $blck - 1; + $C->[$_] = substr($ct_data, $_*$blck, $blck) for (0..$n); # n+1 semiblocks + + if ($n==1) { + $B = $inverse ? $ECB->encrypt($C->[0] . $C->[1], $kek) + : $ECB->decrypt($C->[0] . $C->[1], $kek); + $A = _MSB($blck, $B); + $R->[0] = _LSB($blck, $B); + } + else { + $A = $C->[0]; + $R->[$_] = $C->[$_+1] for (0..$n-1); + for(my $j=5; $j>=0; $j--) { + for(my $i=$n-1; $i>=0; $i--) { + $B = $inverse ? $ECB->encrypt(($A ^ _N2RAW($blck, $n*$j+$i+1)) . $R->[$i], $kek) + : $ECB->decrypt(($A ^ _N2RAW($blck, $n*$j+$i+1)) . $R->[$i], $kek); + $A = _MSB($blck, $B); + $R->[$i] = _LSB($blck, $B); + } + } + } + + my $rv = ''; + $rv .= $R->[$_] for (0..$n-1); + + my $A_hex = unpack("H*", $A); + if ($A_hex eq 'a6'x$blck) { + return $rv; + } + elsif ($A_hex =~ /^a65959a6/ && $blck == 8) { + warn "key_unwrap: unexpected padding" unless $padding; + my $n = unpack("N", substr($A, 4, 4)); + my $z = length($rv) - $n; + my $tail = unpack("H*", substr($rv, -$z)); + croak "aes_key_unwrap: invalid data" unless $tail eq "00"x$z; + return substr($rv, 0, $n); + } + croak "aes_key_unwrap: unexpected data [$cipher/$A_hex]"; +} + +# AES GCM KW - https://tools.ietf.org/html/rfc7518#section-4.7 + +sub gcm_key_wrap { + my ($kek, $pt_data, $aad, $cipher, $iv) = @_; + $cipher = 'AES' unless defined $cipher; + $iv = random_bytes(Crypt::Cipher->blocksize($cipher)) unless defined $iv; + my ($ct_data, $tag) = gcm_encrypt_authenticate($cipher, $kek, $iv, $aad, $pt_data); + return ($ct_data, $tag, $iv); +} + +sub gcm_key_unwrap { + my ($kek, $ct_data, $tag, $iv, $aad, $cipher) = @_; + $cipher ||= 'AES'; + my $pt_data = gcm_decrypt_verify($cipher, $kek, $iv, $aad, $ct_data, $tag); + return $pt_data; +} + +# PBES2/PBKDF2 KW - https://tools.ietf.org/html/rfc7518#section-4.8 + +sub pbes2_key_wrap { + my ($kek, $pt_data, $alg, $salt, $iter) = @_; + my ($hash_name, $len); + if ($alg =~ /^PBES2-HS(256|384|512)\+A(128|192|256)KW$/) { + $hash_name = "SHA$1"; + $len = $2/8; + my $aes_key = pbkdf2($kek, $alg."\x00".$salt, $iter, $hash_name, $len); + my $ct_data = aes_key_wrap($aes_key, $pt_data); + return $ct_data; + } + croak "pbes2_key_wrap: invalid alg '$alg'"; + return undef; +} + +sub pbes2_key_unwrap { + my ($kek, $ct_data, $alg, $salt, $iter) = @_; + my ($hash_name, $len); + if ($alg =~ /^PBES2-HS(256|384|512)\+A(128|192|256)KW$/) { + $hash_name = "SHA$1"; + $len = $2/8; + my $aes_key = pbkdf2($kek, $alg."\x00".$salt, $iter, $hash_name, $len); + my $pt_data = aes_key_unwrap($aes_key, $ct_data); + return $pt_data; + } + croak "pbes2_key_unwrap: invalid alg '$alg'"; + return undef; +} + +# RSA KW +# https://tools.ietf.org/html/rfc7518#section-4.2 +# https://tools.ietf.org/html/rfc7518#section-4.3 + +sub rsa_key_wrap { + my ($kek_public, $pt_data, $alg) = @_; + croak "rsa_key_wrap: no Crypt::PK::RSA" unless ref $kek_public eq 'Crypt::PK::RSA'; + my ($padding, $hash_name); + if ($alg eq 'RSA-OAEP') { ($padding, $hash_name) = ('oaep', 'SHA1') } + elsif ($alg eq 'RSA-OAEP-256') { ($padding, $hash_name) = ('oaep', 'SHA256') } + elsif ($alg eq 'RSA1_5') { $padding = 'v1.5' } + croak "rsa_key_wrap: invalid algorithm '$alg'" unless $padding; + my $ct_data = $kek_public->encrypt($pt_data, $padding, $hash_name); + return $ct_data; +} + +sub rsa_key_unwrap { + my ($kek_private, $ct_data, $alg) = @_; + croak "rsa_key_unwrap: no Crypt::PK::RSA" unless ref $kek_private eq 'Crypt::PK::RSA'; + croak "rsa_key_unwrap: no private key" unless $kek_private->is_private; + my ($padding, $hash_name); + if ($alg eq 'RSA-OAEP') { ($padding, $hash_name) = ('oaep', 'SHA1') } + elsif ($alg eq 'RSA-OAEP-256') { ($padding, $hash_name) = ('oaep', 'SHA256') } + elsif ($alg eq 'RSA1_5') { $padding = 'v1.5' } + croak "rsa_key_unwrap: invalid algorithm '$alg'" unless $padding; + my $pt_data = $kek_private->decrypt($ct_data, $padding, $hash_name); + return $pt_data; +} + +# ConcatKDF - http://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Ar2.pdf +# ECDH KW - https://tools.ietf.org/html/rfc7518#section-4.6 + +sub _concat_kdf { + my ($hash_name, $key_size, $shared_secret, $algorithm, $apu, $apv) = @_; + $apu = '' unless defined $apu; + $apv = '' unless defined $apv; + my $hsize = Crypt::Digest->hashsize($hash_name); + my $count = int($key_size / $hsize); + $count++ if ($key_size % $hsize) > 0; + my $data = ''; + for my $i (1..$count) { + $data .= digest_data('SHA256', pack("N", 1) . + $shared_secret . + pack("N", length($algorithm)) . $algorithm . + pack("N", length($apu)) . $apu . + pack("N", length($apv)) . $apv . + pack("N", 8 *$key_size)); + } + return substr($data, 0, $key_size); +} + +sub ecdh_key_wrap { + my ($kek_public, $enc, $apu, $apv) = @_; + croak "ecdh_key_wrap: no Crypt::PK::ECC" unless ref $kek_public eq 'Crypt::PK::ECC'; + my $encryption_key_size = 256; + if ($enc =~ /^A(128|192|256)CBC-HS/) { + $encryption_key_size = $1*2; + } + if ($enc =~ /^A(128|192|256)GCM/) { + $encryption_key_size = $1; + } + my $ephemeral = Crypt::PK::ECC->new()->generate_key($kek_public->curve2hash); + my $shared_secret = $ephemeral->shared_secret($kek_public); + my $ct_data = _concat_kdf('SHA256', $encryption_key_size/8, $shared_secret, $enc, $apu, $apv); + return ($ct_data, $ephemeral->export_key_jwk('public')); +} + +sub ecdh_key_unwrap { + my ($kek_private, $enc, $epk, $apu, $apv) = @_; + croak "ecdh_key_unwrap: no Crypt::PK::ECC" unless ref $kek_private eq 'Crypt::PK::ECC'; + croak "ecdh_key_unwrap: no private key" unless $kek_private->is_private; + my $encryption_key_size = 256; + if ($enc =~ /^A(128|192|256)CBC-HS/) { + $encryption_key_size = $1*2; + } + if ($enc =~ /^A(128|192|256)GCM/) { + $encryption_key_size = $1; + } + my $ephemeral = ref($epk) eq 'Crypt::PK::ECC' ? $epk : Crypt::PK::ECC->new(ref $epk ? $epk : \$epk); + my $shared_secret = $kek_private->shared_secret($ephemeral); + my $pt_data = _concat_kdf('SHA256', $encryption_key_size/8, $shared_secret, $enc, $apu, $apv); + return $pt_data; +} + +sub ecdhaes_key_wrap { + my ($kek_public, $pt_data, $alg, $apu, $apv) = @_; + croak "ecdhaes_key_wrap: no Crypt::PK::ECC" unless ref $kek_public eq 'Crypt::PK::ECC'; + my $encryption_key_size = 256; + if ($alg =~ /^ECDH-ES\+A(128|192|256)KW$/) { + $encryption_key_size = $1; + } + my $ephemeral = Crypt::PK::ECC->new()->generate_key($kek_public->curve2hash); + my $shared_secret = $ephemeral->shared_secret($kek_public); + my $kek = _concat_kdf('SHA256', $encryption_key_size/8, $shared_secret, $alg, $apu, $apv); + return (aes_key_wrap($kek, $pt_data), $ephemeral->export_key_jwk('public')); +} + +sub ecdhaes_key_unwrap { + my ($kek_private, $ct_data, $alg, $epk, $apu, $apv) = @_; + croak "ecdhaes_key_unwrap: no Crypt::PK::ECC" unless ref $kek_private eq 'Crypt::PK::ECC'; + croak "ecdhaes_key_unwrap: no private key" unless $kek_private->is_private; + my $encryption_key_size = 256; + if ($alg =~ /^ECDH-ES\+A(128|192|256)KW$/) { + $encryption_key_size = $1; + } + my $ephemeral = ref($epk) eq 'Crypt::PK::ECC' ? $epk : Crypt::PK::ECC->new(ref $epk ? $epk : \$epk); + my $shared_secret = $kek_private->shared_secret($ephemeral); + my $kek = _concat_kdf('SHA256', $encryption_key_size/8, $shared_secret, $alg, $apu, $apv); + my $pt_data = aes_key_unwrap($kek, $ct_data); + return $pt_data; +} + +1; + +=pod + +=head1 NAME + +Crypt::KeyWrap - Key management/wrapping algorithms defined in RFC7518 (JWA) + +=head1 SYNOPSIS + + # A192KW wrapping + use Crypt::KeyWrap qw(aes_key_wrap); + my $kek = pack("H*", "5840df6e29b02af1ab493b705bf16ea1ae8338f4dcc176a8"); # key encryption key + my $cek = pack("H*", "c37b7e6492584340bed12207808941155068f738"); # content encryption key + my $enc_cek = aes_key_wrap($kek, $pt_data); # encrypted content encryption key + + # A192KW unwrapping + use Crypt::KeyWrap qw(aes_key_unwrap); + my $kek = pack("H*", "5840df6e29b02af1ab493b705bf16ea1ae8338f4dcc176a8"); + my $enc_cek = pack("H*", "138bdeaa9b8fa7fc61f97742e72248ee5ae6ae5360d1ae6a5f54f373fa543b6a"); + my $cek = aes_key_unwrap($kek, $pt_data); + +=head1 DESCRIPTION + +Implements key management algorithms defined in L + +BEWARE: experimental, interface of this module might change! + +Supported algorithms (all defined in RFC7518): + + A128KW see: aes_key_wrap() + aes_key_unwrap() + A192KW see: aes_key_wrap() + aes_key_unwrap() + A256KW see: aes_key_wrap() + aes_key_unwrap() + A128GCMKW see: gcm_key_wrap() + gcm_key_unwrap() + A192GCMKW see: gcm_key_wrap() + gcm_key_unwrap() + A256GCMKW see: gcm_key_wrap() + gcm_key_unwrap() + PBES2-HS256+A128KW see: pbes2_key_wrap() + pbes2_key_unwrap() + PBES2-HS384+A192KW see: pbes2_key_wrap() + pbes2_key_unwrap() + PBES2-HS512+A256KW see: pbes2_key_wrap() + pbes2_key_unwrap() + RSA-OAEP see: rsa_key_wrap() + rsa_key_unwrap() + RSA-OAEP-256 see: rsa_key_wrap() + rsa_key_unwrap() + RSA1_5 see: rsa_key_wrap() + rsa_key_unwrap() + ECDH-ES+A128KW see: ecdhaes_key_wrap() + ecdhaes_key_unwrap() + ECDH-ES+A192KW see: ecdhaes_key_wrap() + ecdhaes_key_unwrap() + ECDH-ES+A256KW see: ecdhaes_key_wrap() + ecdhaes_key_unwrap() + ECDH-ES see: ecdh_key_wrap() + ecdh_key_unwrap() + +=head1 EXPORT + +Nothing is exported by default. + +You can export selected functions: + + use Crypt::KeyWrap qw(aes_key_wrap gcm_key_wrap pbes2_key_wrap); + +Or all of them at once: + + use Crypt::KeyWrap ':all'; + +=head1 FUNCTIONS + +=head2 aes_key_wrap + +AES key wrap algorithm as defined in L +(implements algorithms C, C, C). + +Implementation follows L and L. + +The implementation is also compatible with L +(it supports AES based KW, KWP + TDEA/DES_EDE based TKW). + +AES Key Wrap algorithm. + + $enc_cek = aes_key_wrap($kek, $cek); + # or + $enc_cek = aes_key_wrap($kek, $cek, $cipher, $padding, $inverse); + + # params: + # $kek .. key encryption key (16bytes for AES128, 24 for AES192, 32 for AES256) + # $cek .. content encryption key + # optional params: + # $cipher .. 'AES' (default) or 'DES_EDE' + # $padding .. 1 (default) or 0 handle $cek padding (relevant for AES only) + # $inverse .. 0 (default) or 1 use cipher in inverse mode as defined by SP.800-38F + +Values C<$enc_cek>, C<$cek> and C<$kek> are binary octets. If you disable padding you have to make sure that +C<$cek> length is multiply of 8 (for AES) or multiply of 4 (for DES_EDE); + +=head2 aes_key_unwrap + +AES key unwrap algorithm as defined in L +(implements algorithms C, C, C). + +AES Key Unwrap algorithm. + + $cek = aes_key_unwrap($kek, $enc_cek); + # or + $cek = aes_key_unwrap($kek, $enc_cek, $cipher, $padding, $inverse); + + # params: + # $kek .. key encryption key (16bytes for AES128, 24 for AES192, 32 for AES256) + # $enc_cek .. encrypted content encryption key + # optional params: + # $cipher .. 'AES' (default) or 'DES_EDE' + # $padding .. 1 (default) or 0 - use $cek padding (relevant for AES only) + # $inverse .. 0 (default) or 1 - use cipher in inverse mode as defined by SP.800-38F + +Values C<$enc_cek>, C<$cek> and C<$kek> are binary octets. + +=head2 gcm_key_wrap + +AES GCM key wrap algorithm as defined in L +(implements algorithms C, C, C). + + ($enc_cek, $tag, $iv) = gcm_key_wrap($kek, $cek); + #or + ($enc_cek, $tag, $iv) = gcm_key_wrap($kek, $cek, $aad); + #or + ($enc_cek, $tag, $iv) = gcm_key_wrap($kek, $cek, $aad, $cipher, $iv); + + # params: + # $kek .. key encryption key (16bytes for AES128, 24 for AES192, 32 for AES256) + # $cek .. content encryption key + # optional params: + # $aad .. additional authenticated data, DEFAULT is '' (empty string) + # $cipher .. cipher to be used by GCM, DEFAULT is 'AES' + # $iv .. initialization vector (if not defined a random IV is generated) + +Values C<$enc_cek>, C<$cek>, C<$aad>, C<$iv>, C<$tag> and C<$kek> are binary octets. + +=head2 gcm_key_unwrap + +AES GCM key unwrap algorithm as defined in L +(implements algorithms C, C, C). + + $cek = gcm_key_unwrap($kek, $enc_cek, $tag, $iv); + # or + $cek = gcm_key_unwrap($kek, $enc_cek, $tag, $iv, $aad); + # or + $cek = gcm_key_unwrap($kek, $enc_cek, $tag, $iv, $aad, $cipher); + + # params: + # $kek .. key encryption key (16bytes for AES128, 24 for AES192, 32 for AES256) + # $enc_cek .. encrypted content encryption key + # $tag .. GCM's tag + # $iv .. initialization vector + # optional params: + # $aad .. additional authenticated data, DEFAULT is '' (empty string) + # $cipher .. cipher to be used by GCM, DEFAULT is 'AES' + +Values C<$enc_cek>, C<$cek>, C<$aad>, C<$iv>, C<$tag> and C<$kek> are binary octets. + +=head2 pbes2_key_wrap + +PBES2 key wrap algorithm as defined in L +(implements algorithms C, C, C). + + $enc_cek = pbes2_key_wrap($kek, $cek, $alg, $salt, $iter); + + # params: + # $kek .. key encryption key (arbitrary length) + # $cek .. content encryption key + # $alg .. algorithm name e.g. 'PBES2-HS256+A128KW' (see rfc7518) + # $salt .. pbkdf2 salt + # $iter .. pbkdf2 iteration count + +Values C<$enc_cek>, C<$cek>, C<$salt> and C<$kek> are binary octets. + +=head2 pbes2_key_unwrap + +PBES2 key unwrap algorithm as defined in L +(implements algorithms C, C, C). + + $cek = pbes2_key_unwrap($kek, $enc_cek, $alg, $salt, $iter); + + # params: + # $kek .. key encryption key (arbitrary length) + # $enc_cek .. encrypted content encryption key + # $alg .. algorithm name e.g. 'PBES2-HS256+A128KW' (see rfc7518) + # $salt .. pbkdf2 salt + # $iter .. pbkdf2 iteration count + +Values C<$enc_cek>, C<$cek>, C<$salt> and C<$kek> are binary octets. + +=head2 rsa_key_wrap + +PBES2 key wrap algorithm as defined in L and +L (implements algorithms C, C, C). + + $enc_cek = rsa_key_wrap($kek, $cek, $alg); + + # params: + # $kek .. RSA public key - Crypt::PK::RSA instance + # $cek .. content encryption key + # $alg .. algorithm name e.g. 'RSA-OAEP' (see rfc7518) + +Values C<$enc_cek> and C<$cek> are binary octets. + +=head2 rsa_key_unwrap + +PBES2 key wrap algorithm as defined in L and +L (implements algorithms C, C, C). + + $cek = rsa_key_unwrap($kek, $enc_cek, $alg); + + # params: + # $kek .. RSA private key - Crypt::PK::RSA instance + # $enc_cek .. encrypted content encryption key + # $alg .. algorithm name e.g. 'RSA-OAEP' (see rfc7518) + +Values C<$enc_cek> and C<$cek> are binary octets. + +=head2 ecdhaes_key_wrap + +ECDH+AESKW key agreement/wrap algorithm as defined in L +(implements algorithms C, C, C). + + ($enc_cek, $epk) = ecdhaes_key_wrap($kek, $cek, $alg, $apu, $apv); + + # params: + # $kek .. ECC public key - Crypt::PK::ECC instance + # $cek .. content encryption key + # $alg .. algorithm name e.g. 'ECDH-ES+A256KW' (see rfc7518) + # optional params: + # $apu .. Agreement PartyUInfo Header Parameter + # $apv .. Agreement PartyVInfo Header Parameter + +Values C<$enc_cek> and C<$cek> are binary octets. + +=head2 ecdhaes_key_unwrap + +ECDH+AESKW key agreement/unwrap algorithm as defined in L +(implements algorithms C, C, C). + + $cek = ecdhaes_key_unwrap($kek, $enc_cek, $alg, $epk, $apu, $apv); + + # params: + # $kek .. ECC private key - Crypt::PK::ECC instance + # $enc_cek .. encrypted content encryption key + # $alg .. algorithm name e.g. 'ECDH-ES+A256KW' (see rfc7518) + # $epk .. ephemeral ECC public key (JWK/JSON or Crypt::PK::ECC) + # optional params: + # $apu .. Agreement PartyUInfo Header Parameter + # $apv .. Agreement PartyVInfo Header Parameter + +Values C<$enc_cek> and C<$cek> are binary octets. + +=head2 ecdh_key_wrap + +ECDH (Ephememeral Static) key agreement/wrap algorithm as defined in L +(implements algorithm C). + + ($cek, $epk) = ecdh_key_wrap($kek, $enc, $apu, $apv); + + # params: + # $kek .. ECC public key - Crypt::PK::ECC instance + # $enc .. encryption algorithm name e.g. 'A256GCM' (see rfc7518) + # optional params: + # $apu .. Agreement PartyUInfo Header Parameter + # $apv .. Agreement PartyVInfo Header Parameter + +Value C<$cek> - binary octets, C<$epk> JWK/JSON string with ephemeral ECC public key. + +=head2 ecdh_key_unwrap + +ECDH (Ephememeral Static) key agreement/unwrap algorithm as defined in L +(implements algorithm C). + + $cek = ecdh_key_unwrap($kek, $enc, $epk, $apu, $apv); + + # params: + # $kek .. ECC private key - Crypt::PK::ECC instance + # $enc .. encryption algorithm name e.g. 'A256GCM' (see rfc7518) + # $epk .. ephemeral ECC public key (JWK/JSON or Crypt::PK::ECC) + # optional params: + # $apu .. Agreement PartyUInfo Header Parameter + # $apv .. Agreement PartyVInfo Header Parameter + +Value C<$cek> - binary octets. + +=head1 SEE ALSO + +L, L, L, L + +=head1 LICENSE + +This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. + +=head1 COPYRIGHT + +Copyright (c) 2015 DCIT, a.s. L / Karel Miko diff --git a/t/compile.t b/t/compile.t new file mode 100644 index 0000000..c321383 --- /dev/null +++ b/t/compile.t @@ -0,0 +1,7 @@ +use strict; +use warnings; + +use Test::More tests => 2; + +use_ok('Crypt::KeyWrap'); +use_ok('Crypt::JWT'); diff --git a/t/flattened.t b/t/flattened.t new file mode 100644 index 0000000..4951126 --- /dev/null +++ b/t/flattened.t @@ -0,0 +1,65 @@ +use strict; +use warnings; +use Test::More tests => 4; + +use Crypt::JWT qw(decode_jwt encode_jwt); + +### JWS - test case from https://github.com/Spomky-Labs/jose + +my $key1 = { + 'kid' => 'e9bc097a-ce51-4036-9562-d2ade882db0d', + 'kty' => 'EC', + 'crv' => 'P-256', + 'x' => 'f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU', + 'y' => 'x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0', + 'd' => 'jpsQnnGQmL-YBIffH1136cspYG6-0iY7X1fCE9-E9LI', +}; + +my $jws = '{"payload":"eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ","protected":"eyJhbGciOiJFUzI1NiJ9","header":{"kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"},"signature":"DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q"}'; + +my ($header, $data) = decode_jwt(token=>$jws, key=>$key1, verify_exp=>0, decode_header=>1); +is($data->{iss}, "joe"); +is($data->{exp}, 1300819380); +is($header->{alg}, "ES256"); +is($header->{kid}, "e9bc097a-ce51-4036-9562-d2ade882db0d"); + +#------------------------------------------------------------------------------- +#Example from RFC 7516 (JWE) + +{ + require JSON::MaybeXS; + + #https://tools.ietf.org/html/rfc7516#appendix-A.2.3 + my $jwk_hr = { + kty => "RSA", + n => "sXchDaQebHnPiGvyDOAT4saGEUetSyo9MKLOoWFsueri23bOdgWp4Dy1WlUzewbgBHod5pcM9H95GQRV3JDXboIRROSBigeC5yjU1hGzHHyXss8UDprecbAYxknTcQkhslANGRUZmdTOQ5qTRsLAt6BTYuyvVRdhS8exSZEy_c4gs_7svlJJQ4H9_NxsiIoLwAEk7-Q3UXERGYw_75IDrGA84-lA_-Ct4eTlXHBIY2EaV7t7LjJaynVJCpkv4LKjTTAumiGUIuQhrNhZLuF_RJLqHpM2kgWFLU7-VTdL1VbC2tejvcI2BlMkEpk1BzBZI0KQB0GaDWFLN-aEAw3vRw", + e => "AQAB", + d => "VFCWOqXr8nvZNyaaJLXdnNPXZKRaWCjkU5Q2egQQpTBMwhprMzWzpR8Sxq1OPThh_J6MUD8Z35wky9b8eEO0pwNS8xlh1lOFRRBoNqDIKVOku0aZb-rynq8cxjDTLZQ6Fz7jSjR1Klop-YKaUHc9GsEofQqYruPhzSA-QgajZGPbE_0ZaVDJHfyd7UUBUKunFMScbflYAAOYJqVIVwaYR5zWEEceUjNnTNo_CVSj-VvXLO5VZfCUAVLgW4dpf1SrtZjSt34YLsRarSb127reG_DUwg9Ch-KyvjT1SkHgUWRVGcyly7uvVGRSDwsXypdrNinPA4jlhoNdizK2zF2CWQ", + p => "9gY2w6I6S6L0juEKsbeDAwpd9WMfgqFoeA9vEyEUuk4kLwBKcoe1x4HG68ik918hdDSE9vDQSccA3xXHOAFOPJ8R9EeIAbTi1VwBYnbTp87X-xcPWlEPkrdoUKW60tgs1aNd_Nnc9LEVVPMS390zbFxt8TN_biaBgelNgbC95sM", + q => "uKlCKvKv_ZJMVcdIs5vVSU_6cPtYI1ljWytExV_skstvRSNi9r66jdd9-yBhVfuG4shsp2j7rGnIio901RBeHo6TPKWVVykPu1iYhQXw1jIABfw-MVsN-3bQ76WLdt2SDxsHs7q7zPyUyHXmps7ycZ5c72wGkUwNOjYelmkiNS0", + dp => "w0kZbV63cVRvVX6yk3C8cMxo2qCM4Y8nsq1lmMSYhG4EcL6FWbX5h9yuvngs4iLEFk6eALoUS4vIWEwcL4txw9LsWH_zKI-hwoReoP77cOdSL4AVcraHawlkpyd2TWjE5evgbhWtOxnZee3cXJBkAi64Ik6jZxbvk-RR3pEhnCs", + dq => "o_8V14SezckO6CNLKs_btPdFiO9_kC1DsuUTd2LAfIIVeMZ7jn1Gus_Ff7B7IVx3p5KuBGOVF8L-qifLb6nQnLysgHDh132NDioZkhH7mI7hPG-PYE_odApKdnqECHWw0J-F0JWnUd6D2B_1TvF9mXA2Qx-iGYn8OVV1Bsmp6qU", + qi => "eNho5yRBEBxhGBtQRww9QirZsB66TrfFReG_CcteI1aCneT0ELGhYlRlCtUkTRclIfuEPmNsNDPbLoLqqCVznFbvdB7x-Tl-m0l_eFTj2KiqwGqE9PZB9nNTwMVvH3VRRSLWACvPnSiwP8N5Usy-WRXS-V7TbpxIhvepTfE0NNo", + }; + + #https://tools.ietf.org/html/rfc7516#appendix-A.3.3 + my $key = { "kty" => "oct", "k" => "GawgguFyGrWKav7AX4VKUg" }; + + #https://tools.ietf.org/html/rfc7516#appendix-A.5 + my $jwe_flattened = JSON::MaybeXS::encode_json( + { + protected => "eyJlbmMiOiJBMTI4Q0JDLUhTMjU2In0", + unprotected => { "jku" => "https://server.example.com/keys.jwks" }, + header => { "alg" => "A128KW", "kid" => "7" }, + encrypted_key => "6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ", + iv => "AxY8DCtDaGlsbGljb3RoZQ", + ciphertext => "KDlTtXchhZTGufMYmOYGS4HffxPSUrfmqCHXaI9wOGY", + tag => "Mz-VPPyU4RlcuYv1IwIvzw", + }, + ); + + my ($header, $data) = decode_jwt(token=>$jwe_flattened, key=>$key, verify_exp=>0, decode_header=>1); + + #diag explain $header; + #diag explain $data; +} diff --git a/t/jws_no_key.t b/t/jws_no_key.t new file mode 100644 index 0000000..fab1382 --- /dev/null +++ b/t/jws_no_key.t @@ -0,0 +1,34 @@ +use strict; +use warnings; +use Test::More; + +plan tests => 2; + +use Crypt::JWT qw(encode_jwt decode_jwt); + +my $jws = + 'eyJqd2siOnsiZSI6IkFRQUIiLCJuIjoieVFJdnpEU3h2a2EzQTNhVFYzS2Yza29PeElWMjNqZGlaa1BkOU8xb3RsN0JYLWZJS2dEYk00QnBHSkxZLUhrTG5aZUxpcXgwSFpKaF94U09IVXhWNnVpLUpIU00yZkFrTnEzMHd4QzMycDZmVDk2b3RuT3ZsTEhPTVVpNEZwUFR0NDVFQmcyemlqRXRfRWNFM3g0OFJjT2ZQVGk3SDBmWnhBdXVYcmJrYmU1SHFqczVxVWp2bDFKWUdKdTA1TlItdnE2NUwyUC1oOFA5eUJBT1pRZjhRMVhBSGg1RlFQd08tQjZ3T1p6aTNjeTEtRUhXZkhpWXpxeTMxWU01ZmxIaFZ4QndWRmUyMUlINEh3WWp2SE5KMURFaEl2R2FSQTBWc09ZNlFqVUxPS19XTVlQVnExc211TmdEZThlZ1V4RnV2R2N4aWJ4NTUydHJkSHVBaWFUVGlRIiwia3R5IjoiUlNBIn0sImFsZyI6IlJTMjU2Iiwibm9uY2UiOm51bGx9.eyJjb250YWN0IjpbIm1haWx0bzpmQGcudGxkIl0sInJlc291cmNlIjoibmV3LXJlZyJ9.wrY6y0kvA3qgR38ZuAA471ygN9fmSHdfWDIayjkBKGmeGbn0f30_LQBC9FiFDFgFJ8Owyy3bOkPWvHx7yhRnP5XnEYdzNtKy4t2LyKq_JSEVQf6p1zycsVaxVLCmZ6ZbRidxIFLhbkcmu2uc4BEVGQQEj3UesccIv-AS2JCQFqK5ca-HQeaLEMntXOz5p7DYZtauYjHuXQ60i25mClm51jScJfP-wk7yYnnohGYKDinwiYlH4Nw8p4yElzWL1dI-U8fiFoxnduGaflPIZ80hkk0p7delrZt3RvmaDdu4cLJ16TgrMw_nMZfbAK0IJXByAsbej78HwIAchdzHyRPmgA'; + +my ( $header, $payload ) = decode_jwt( token => $jws, decode_header => 1 ); + +is_deeply( + $header, + { + 'alg' => 'RS256', + 'jwk' => { + 'e' => 'AQAB', + 'kty' => 'RSA', + 'n' => + 'yQIvzDSxvka3A3aTV3Kf3koOxIV23jdiZkPd9O1otl7BX-fIKgDbM4BpGJLY-HkLnZeLiqx0HZJh_xSOHUxV6ui-JHSM2fAkNq30wxC32p6fT96otnOvlLHOMUi4FpPTt45EBg2zijEt_EcE3x48RcOfPTi7H0fZxAuuXrbkbe5Hqjs5qUjvl1JYGJu05NR-vq65L2P-h8P9yBAOZQf8Q1XAHh5FQPwO-B6wOZzi3cy1-EHWfHiYzqy31YM5flHhVxBwVFe21IH4HwYjvHNJ1DEhIvGaRA0VsOY6QjULOK_WMYPVq1smuNgDe8egUxFuvGcxibx552trdHuAiaTTiQ' + }, + 'nonce' => undef + }, +); + +is_deeply( + $payload, + { + 'contact' => ['mailto:f@g.tld'], + 'resource' => 'new-reg' + } +); diff --git a/t/jwt_decode_tv.t b/t/jwt_decode_tv.t new file mode 100644 index 0000000..6c993b6 --- /dev/null +++ b/t/jwt_decode_tv.t @@ -0,0 +1,505 @@ +use strict; +use warnings; +use Test::More; + +use Crypt::JWT qw(decode_jwt); +use Crypt::Misc qw(encode_b64u); + +my $key = "a0a2abd8-6162-41c3-83d6-1cf559b46afc"; + +my $aes128Key = join '', map { chr($_) } (194,164,235,6,138,248,171,239,24,216,11,22,137,199,215,133); +my $aes192Key = join '', map { chr($_) } (139, 156, 136, 148, 17, 147, 27, 233, 145, 80, 115, 197, 223, 11, 100, 221, 5, 50, 155, 226, 136, 222, 216, 14); +my $aes256Key = join '', map { chr($_) } (164,60,194,0,161,189,41,38,130,89,141,164,45,170,159,209,69,137,243,216,191,131,47,250,32,107,231,117,37,158,225,234); +my $aes384Key = join '', map { chr($_) } (185, 30, 233, 199, 32, 98, 209, 3, 114, 250, 30, 124, 207, 173, 227, 152, 243, 202, 238, 165, 227, 199, 202, 230, 218, 185, 216, 113, 13, 53, 40, 100, 100, 20, 59, 67, 88, 97, 191, 3, 161, 37, 147, 223, 149, 237, 190, 156); +my $aes512Key = join '', map { chr($_) } (238, 71, 183, 66, 57, 207, 194, 93, 82, 80, 80, 152, 92, 242, 84, 206, 194, 46, 67, 43, 231, 118, 208, 168, 156, 212, 33, 105, 27, 45, 60, 160, 232, 63, 61, 235, 68, 171, 206, 35, 152, 11, 142, 121, 174, 165, 140, 11, 172, 212, 13, 101, 13, 190, 82, 244, 109, 113, 70, 150, 251, 82, 215, 226); + +my $Ecc256Public_PEM = <<'EOF'; +t/jwt_decode_tv.t ...... 46/? -----BEGIN PUBLIC KEY----- +MIIBMzCB7AYHKoZIzj0CATCB4AIBATAsBgcqhkjOPQEBAiEA/////wAAAAEAAAAA +AAAAAAAAAAD///////////////8wRAQg/////wAAAAEAAAAAAAAAAAAAAAD///// +//////////wEIFrGNdiqOpPns+u9VXaYhrxlHQawzFOw9jvOPD4n0mBLBEEEaxfR +8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5RdiYwpZP40Li/hp/m47n60p8D54WK84z +V2sxXs7LtkBoN79R9QIhAP////8AAAAA//////////+85vqtpxeehPO5ysL8YyVR +AgEBA0IABARyHd86A7+qQ4DlIfKynZaFGdGLpkU3GlQwqaVD6GIJg3QIDhaWEksY +tZ9OWjNHn9a6+i/P9o5/NrdISP0VWDU= +-----END PUBLIC KEY----- +EOF + +my $Ecc256Public = { + kty => "EC", + crv => "P-256", + x => encode_b64u(join '', map { chr($_) } (4, 114, 29, 223, 58, 3, 191, 170, 67, 128, 229, 33, 242, 178, 157, 150, 133, 25, 209, 139, 166, 69, 55, 26, 84, 48, 169, 165, 67, 232, 98, 9)), + y => encode_b64u(join '', map { chr($_) } (131, 116, 8, 14, 22, 150, 18, 75, 24, 181, 159, 78, 90, 51, 71, 159, 214, 186, 250, 47, 207, 246, 142, 127, 54, 183, 72, 72, 253, 21, 88, 53)), +}; +my $Ecc256Private = { + kty => "EC", + crv => "P-256", + x => encode_b64u(join '', map { chr($_) } (4, 114, 29, 223, 58, 3, 191, 170, 67, 128, 229, 33, 242, 178, 157, 150, 133, 25, 209, 139, 166, 69, 55, 26, 84, 48, 169, 165, 67, 232, 98, 9)), + y => encode_b64u(join '', map { chr($_) } (131, 116, 8, 14, 22, 150, 18, 75, 24, 181, 159, 78, 90, 51, 71, 159, 214, 186, 250, 47, 207, 246, 142, 127, 54, 183, 72, 72, 253, 21, 88, 53)), + d => encode_b64u(join '', map { chr($_) } (42, 148, 231, 48, 225, 196, 166, 201, 23, 190, 229, 199, 20, 39, 226, 70, 209, 148, 29, 70, 125, 14, 174, 66, 9, 198, 80, 251, 95, 107, 98, 206)), +}; +my $Ecc384Public = { + kty => "EC", + crv => "P-384", + x => encode_b64u(join '', map { chr($_) } (70, 151, 220, 179, 62, 0, 79, 232, 114, 64, 58, 75, 91, 209, 232, 128, 7, 137, 151, 42, 13, 148, 15, 133, 93, 215, 7, 3, 136, 124, 14, 101, 242, 207, 192, 69, 212, 145, 88, 59, 222, 33, 127, 46, 30, 218, 175, 79)), + y => encode_b64u(join '', map { chr($_) } (189, 202, 196, 30, 153, 53, 22, 122, 171, 4, 188, 42, 71, 2, 9, 193, 191, 17, 111, 180, 78, 6, 110, 153, 240, 147, 203, 45, 152, 236, 181, 156, 232, 223, 227, 148, 68, 148, 221, 176, 57, 149, 44, 203, 83, 85, 75, 55)), +}; +my $Ecc384Private = { + kty => "EC", + crv => "P-384", + x => encode_b64u(join '', map { chr($_) } (70, 151, 220, 179, 62, 0, 79, 232, 114, 64, 58, 75, 91, 209, 232, 128, 7, 137, 151, 42, 13, 148, 15, 133, 93, 215, 7, 3, 136, 124, 14, 101, 242, 207, 192, 69, 212, 145, 88, 59, 222, 33, 127, 46, 30, 218, 175, 79)), + y => encode_b64u(join '', map { chr($_) } (189, 202, 196, 30, 153, 53, 22, 122, 171, 4, 188, 42, 71, 2, 9, 193, 191, 17, 111, 180, 78, 6, 110, 153, 240, 147, 203, 45, 152, 236, 181, 156, 232, 223, 227, 148, 68, 148, 221, 176, 57, 149, 44, 203, 83, 85, 75, 55)), + d => encode_b64u(join '', map { chr($_) } (137, 199, 183, 105, 188, 90, 128, 82, 116, 47, 161, 100, 221, 97, 208, 64, 173, 247, 9, 42, 186, 189, 181, 110, 24, 225, 254, 136, 75, 156, 242, 209, 94, 218, 58, 14, 33, 190, 15, 82, 141, 238, 207, 214, 159, 140, 247, 139)), +}; +my $Ecc512Public = { + kty => "EC", + crv => "P-521", + x => encode_b64u(join '', map { chr($_) } (0, 248, 73, 203, 53, 184, 34, 69, 111, 217, 230, 255, 108, 212, 241, 229, 95, 239, 93, 131, 100, 37, 86, 152, 87, 98, 170, 43, 25, 35, 80, 137, 62, 112, 197, 113, 138, 116, 114, 55, 165, 128, 8, 139, 148, 237, 109, 121, 40, 205, 3, 61, 127, 28, 195, 58, 43, 228, 224, 228, 82, 224, 219, 148, 204, 96)), + y => encode_b64u(join '', map { chr($_) } (0, 60, 71, 97, 112, 106, 35, 121, 80, 182, 20, 167, 143, 8, 246, 108, 234, 160, 193, 10, 3, 148, 45, 11, 58, 177, 190, 172, 26, 178, 188, 240, 91, 25, 67, 79, 64, 241, 203, 65, 223, 218, 12, 227, 82, 178, 66, 160, 19, 194, 217, 172, 61, 250, 23, 78, 218, 130, 160, 105, 216, 208, 235, 124, 46, 32)), +}; +my $Ecc512Private = { + kty => "EC", + crv => "P-521", + x => encode_b64u(join '', map { chr($_) } (0, 248, 73, 203, 53, 184, 34, 69, 111, 217, 230, 255, 108, 212, 241, 229, 95, 239, 93, 131, 100, 37, 86, 152, 87, 98, 170, 43, 25, 35, 80, 137, 62, 112, 197, 113, 138, 116, 114, 55, 165, 128, 8, 139, 148, 237, 109, 121, 40, 205, 3, 61, 127, 28, 195, 58, 43, 228, 224, 228, 82, 224, 219, 148, 204, 96)), + y => encode_b64u(join '', map { chr($_) } (0, 60, 71, 97, 112, 106, 35, 121, 80, 182, 20, 167, 143, 8, 246, 108, 234, 160, 193, 10, 3, 148, 45, 11, 58, 177, 190, 172, 26, 178, 188, 240, 91, 25, 67, 79, 64, 241, 203, 65, 223, 218, 12, 227, 82, 178, 66, 160, 19, 194, 217, 172, 61, 250, 23, 78, 218, 130, 160, 105, 216, 208, 235, 124, 46, 32)), + d => encode_b64u(join '', map { chr($_) } (0, 222, 129, 9, 133, 207, 123, 116, 176, 83, 95, 169, 29, 121, 160, 137, 22, 21, 176, 59, 203, 129, 62, 111, 19, 78, 14, 174, 20, 211, 56, 160, 83, 42, 74, 219, 208, 39, 231, 33, 84, 114, 71, 106, 109, 161, 116, 243, 166, 146, 252, 231, 137, 228, 99, 149, 152, 123, 201, 157, 155, 131, 181, 106, 179, 112)), +}; + +my $rsaPub = <<'EOF'; +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqFZv0pea/jn5Mo4qEUmS +tuhlulso8n1inXbEotd/zTrQp9K0RK0hf7t0K4BjKVhaiqIam4tVVQvkmYeBeYr1 +MmnO/0N97dMBz/7fmvyv0hgHaBdQ5mR5u3LTlHo8tjRE7+GzZmGs6jMcyj7HbXob +DPQJZpqNy6JjliDVXxW8nWJDetxGBlqmTj1E1fr2RCsZLreDOPSDIedG1upz9Rra +ShsIDzeefOcKibcAaKeeVI3rkAU8/mOauLSXv37hlk0h6sStJb3qZQXyOUkVkjXI +khvNu/ve0v7LiLT4G/OxYGzpOQcCnimKdojzNP6GtVDaMPh+QkSJE32UCos9R3wI +2QIDAQAB +-----END PUBLIC KEY----- +EOF + +my $rsaPriv = <<'EOF'; +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCoVm/Sl5r+Ofky +jioRSZK26GW6WyjyfWKddsSi13/NOtCn0rRErSF/u3QrgGMpWFqKohqbi1VVC+SZ +h4F5ivUyac7/Q33t0wHP/t+a/K/SGAdoF1DmZHm7ctOUejy2NETv4bNmYazqMxzK +PsdtehsM9Almmo3LomOWINVfFbydYkN63EYGWqZOPUTV+vZEKxkut4M49IMh50bW +6nP1GtpKGwgPN5585wqJtwBop55UjeuQBTz+Y5q4tJe/fuGWTSHqxK0lveplBfI5 +SRWSNciSG827+97S/suItPgb87FgbOk5BwKeKYp2iPM0/oa1UNow+H5CRIkTfZQK +iz1HfAjZAgMBAAECggEBAJSYcG9KSpQdor8gxTurYWo6LQpazAN58SIkpCFG71a/ +k06BbYWt+oMhesOnumDV0F7OB4TEctf2/p0UA5PBuP3+bq3f6vqTp+buCn5qjd18 +PpWA93XYvahdDS3k1VDVRQEnj9BRamz2H3TcA/i8r8I4bU/4IDDgMN5mL1OXAX8+ +vt7j3YZdwsEBQk4MDrnfwQPadjDzFBxvNsDCv7DTtSNE2KY5u058DQcIimzH/ouQ +ip7qIYKGKxA2C3jIN399ngZY5QhTWGqArU/pq9WXtDkyTQ9OL23y6LVfgQSrpSKW +zjknlaShu4CcWR5r+4p+zxOf1s2sShVaB1t8Eer/xs0CgYEA0qaOkT174vRG3E/6 +7gU3lgOgoT6L3pVHuu7wfrIEoxycPa5/mZVG54SgvQUofGUYEGjR0lavUAjClw9t +OzcODHX8RAxkuDntAFntBxgRM+IzAy8QzeRl/cbhgVjBTAhBcxg+3VySv5GdxFyr +QaIo8Oy/PPI1L4EFKZHmicBd3tsCgYEAzJPqCDKqaJH9TAGfzt6b4aNt9fpirEcd +pAF1bCedFfQmUZM0LG3rMtOAIhjEXgADt5GB8ZNK3BQl8BJyMmKs57oKmbVcODER +CtPqjECXXsxH+az9nzxatPvcb7imFW8OlWslwr4IIRKdEjzEYs4syQJz7k2ktqOp +YI5/UfYnw1sCgYApNaZMaZ/T3YADV646ZFDkix8gjFDmoYOf4WCxGHhpxI4YTwvt +atOtNTgQ4nJyK4DSrP7nTEgNuzj+PmlbHUElVOueEGKf280utWj2a1HqOYVLSSjb +bqQ5SnARUuC11COhtYuO2K5oxb78jDiApY2m3FnpPWUEPxRYdo+IQVbb4wKBgCZ9 +JajJL3phDRDBtXlMNHOtNcDzjKDw+Eik5Zylj05UEumCEmzReVCkrhS8KCWvRwPA +Ynw6w/jH6aNTNRz5p6IpRFlK38DKqnQpDpW4iUISmPAGdekBh+dJA14ZlVWvAUVn +VUFgU1M1l0uZFzGnrJFc3sbU4Mpj3DgIVzfqYezFAoGBALEQD4oCaZfEv77H9c4S +U6xzPe8UcLgdukek5vifLCkT2+6eccTZZjgQRb1plsXbaPHQRJTZcnUmWp9+98gS +8c1vm2YFafgdkSk9Qd1oU2Fv1aOQy4VovOFzJ3CcR+2r7cbRfcpLGnintHtp9yek +02p+d5g4OChfFNDhDtnIqjvY +-----END PRIVATE KEY----- +EOF + +### Plaintext +{ + my $token = "eyJhbGciOiJub25lIn0.eyJoZWxsbyI6ICJ3b3JsZCJ9."; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>$key, allow_none=>1) } or warn $@; + my $expected = '{"hello": "world"}'; + is($json, $expected, 'Plaintext'); + is($json, $expected, 'Plaintext'); +} +### HS256 +{ + my $token = "eyJhbGciOiJIUzI1NiIsImN0eSI6InRleHRcL3BsYWluIn0.eyJoZWxsbyI6ICJ3b3JsZCJ9.chIoYWrQMA8XL5nFz6oLDJyvgHk2KA4BrFGrKymjC8E"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>$key) } or warn $@; + my $expected = '{"hello": "world"}'; + is($json, $expected, 'HS256'); +} +### HS384 +{ + my $token = "eyJhbGciOiJIUzM4NCIsImN0eSI6InRleHRcL3BsYWluIn0.eyJoZWxsbyI6ICJ3b3JsZCJ9.McDgk0h4mRdhPM0yDUtFG_omRUwwqVS2_679Yeivj-a7l6bHs_ahWiKl1KoX_hU_"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>$key) } or warn $@; + my $expected = '{"hello": "world"}'; + is($json, $expected, 'HS384'); +} +### HS512 +{ + my $token = "eyJhbGciOiJIUzUxMiIsImN0eSI6InRleHRcL3BsYWluIn0.eyJoZWxsbyI6ICJ3b3JsZCJ9.9KirTNe8IRwFCBLjO8BZuXf3U2ZVagdsg7F9ZsvMwG3FuqY9W0vqwjzPOjLqPN-GkjPm6C3qWPnINhpr5bEDJQ"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>$key) } or warn $@; + my $expected = '{"hello": "world"}'; + is($json, $expected, 'HS512'); +} +### RS256 +{ + my $token = "eyJhbGciOiJSUzI1NiIsImN0eSI6InRleHRcL3BsYWluIn0.eyJoZWxsbyI6ICJ3b3JsZCJ9.NL_dfVpZkhNn4bZpCyMq5TmnXbT4yiyecuB6Kax_lV8Yq2dG8wLfea-T4UKnrjLOwxlbwLwuKzffWcnWv3LVAWfeBxhGTa0c4_0TX_wzLnsgLuU6s9M2GBkAIuSMHY6UTFumJlEeRBeiqZNrlqvmAzQ9ppJHfWWkW4stcgLCLMAZbTqvRSppC1SMxnvPXnZSWn_Fk_q3oGKWw6Nf0-j-aOhK0S0Lcr0PV69ZE4xBYM9PUS1MpMe2zF5J3Tqlc1VBcJ94fjDj1F7y8twmMT3H1PI9RozO-21R0SiXZ_a93fxhE_l_dj5drgOek7jUN9uBDjkXUwJPAyp9YPehrjyLdw"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>\$rsaPub) } or warn $@; + my $expected = '{"hello": "world"}'; + is($json, $expected, 'RS256'); +} +### RS384 +{ + my $token = "eyJhbGciOiJSUzM4NCIsImN0eSI6InRleHRcL3BsYWluIn0.eyJoZWxsbyI6ICJ3b3JsZCJ9.cOPca7YEOxnXVdIi7cJqfgRMmDFPCrZG1M7WCJ23U57rAWvCTaQgEFdLjs7aeRAPY5Su_MVWV7YixcawKKYOGVG9eMmjdGiKHVoRcfjwVywGIb-nuD1IBzGesrQe7mFQrcWKtYD9FurjCY1WuI2FzGPp5YhW5Zf4TwmBvOKz6j2D1vOFfGsogzAyH4lqaMpkHpUAXddQxzu8rmFhZ54Rg4T-jMGVlsdrlAAlGA-fdRZ-V3F2PJjHQYUcyS6n1ULcy6ljEOgT5fY-_8DDLLpI8jAIdIhcHUAynuwvvnDr9bJ4xIy4olFRqcUQIHbcb5-WDeWul_cSGzTJdxDZsnDuvg"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>\$rsaPub) } or warn $@; + my $expected = '{"hello": "world"}'; + is($json, $expected, 'RS384'); +} +### RS512 +{ + my $token = "eyJhbGciOiJSUzUxMiIsImN0eSI6InRleHRcL3BsYWluIn0.eyJoZWxsbyI6ICJ3b3JsZCJ9.KP_mwCVRIxcF6ErdrzNcXZQDFGcL-Hlyocc4tIl3tJfzSfc7rz7qOLPjHpZ6UFH1ncd5TlpRc1B_pgvY-l0BNtx_s7n_QA55X4c1oeD8csrIoXQ6A6mtvdVGoSlGu2JnP6N2aqlDmlcefKqjl_Z-8nwDMGTMkDNhHKfHlIb2_Dliwxeq8LmNMREEdvNH2XVp_ffxBjiaKv2Eqbwc6I17241GCEmjDCvnagSgjX_5uu-da2H7TK2gtPJYUo8r9nzC7uzZJ5SB8suZH0COSofsP-9wvH0FESO40evCyEBylqg3bh9M9dIzeq8_bdTiC5kG93Fal44OEY8_Zm88wB_VjQ"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>\$rsaPub) } or warn $@; + my $expected = '{"hello": "world"}'; + is($json, $expected, 'RS512'); +} +### ES256 +{ + my $token = "eyJhbGciOiJFUzI1NiIsImN0eSI6InRleHRcL3BsYWluIn0.eyJoZWxsbyI6ICJ3b3JsZCJ9.EVnmDMlz-oi05AQzts-R3aqWvaBlwVZddWkmaaHyMx5Phb2NSLgyI0kccpgjjAyo1S5KCB3LIMPfmxCX_obMKA"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>$Ecc256Public) } or warn $@; + my $expected = '{"hello": "world"}'; + is($json, $expected, 'ES256'); +} +### ES384 +{ + my $token = "eyJhbGciOiJFUzM4NCIsImN0eSI6InRleHRcL3BsYWluIn0.eyJoZWxsbyI6ICJ3b3JsZCJ9.jVTHd9T0fIQDJLNvAq3LPpgj_npXtWb64FfEK8Sm65Nr9q2goUWASrM9jv3h-71UrP4cBpM3on3yN--o6B-Tl6bscVUfpm1swPp94f7XD9VYLEjGMjQOaozr13iBZJCY"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>$Ecc384Public) } or warn $@; + my $expected = '{"hello": "world"}'; + is($json, $expected, 'ES384'); +} +### ES512 +{ + my $token = "eyJhbGciOiJFUzUxMiIsImN0eSI6InRleHRcL3BsYWluIn0.eyJoZWxsbyI6ICJ3b3JsZCJ9.AHxJYFeTVpZmrfZsltpQKkkplmbkycQKFOFucD7hE4Sm3rCswUDi8hlSCfeYByugySYLFzogTQGk79PHP6vdl39sAUc9k2bhnv-NxRmJsN8ZxEx09qYKbc14qiNWZztLweQg0U-pU0DQ66rwJ0HikzSqgmyD1bJ6RxitJwceYLAovv0v"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>$Ecc512Public) } or warn $@; + my $expected = '{"hello": "world"}'; + is($json, $expected, 'ES512'); +} +### PS256 +{ + my $token = "eyJhbGciOiJQUzI1NiIsImN0eSI6InRleHRcL3BsYWluIn0.eyJoZWxsbyI6ICJ3b3JsZCJ9.S9xuR-IGfXEj5qsHcMtK-jcj1lezvVstw1AISp8dEQVRNgwOMZhUQnSCx9i1CA-pMucxR-lv4e7zd6h3cYCfMnyv7iuxraxNiNAgREhOT-bkBCZMNgb5t15xEtDSJ3MuBlK3YBtXyVcDDIdKH_Bwj-u363y6LuvZ8FEOGmIK5WSFi18Xjg-ihhvH1C6UzH1G82wrRbX6DyJKqrUnHAg8yzUJVP1AdgjWRt5BKpuYbXSib-MKZZkaE4q_hCb-j25xCzn8Ez8a7PO7p0fDGvZuOk_yzSfvXSavg7iE0GLuUTNv3nQ_xW-rfbrpYeyXNtstoK3JPFpdtORTyH1iIh7VVA"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>\$rsaPub) } or warn $@; + my $expected = '{"hello": "world"}'; + is($json, $expected, 'PS256'); +} +### PS384 +{ + my $token = "eyJhbGciOiJQUzM4NCIsImN0eSI6InRleHRcL3BsYWluIn0.eyJoZWxsbyI6ICJ3b3JsZCJ9.EKqVLw6nLGNt1h7KNFZbzkKhf788VBYCfnigYc0dBZBa64MrfbIFHtJuFgIGkCVSDYH-qs-i4w9ke6mD8mxTZFniMgzFXXaCFIrv6QZeMbKh6VYtSEPp7l0B1zMZiQw6egZbZ6a8VBkCRipuZggSlUTg5tHMMTj_jNVxxlY4uUwXlz7vakpbqgXe19pCDJrzEoXE0cNKV13eRCNA1tXOHx0dFL7Jm9NUq7blvhJ8iTw1jMFzK8bV6g6L7GclHBMoJ3MIvRp71m6idir-QeW1KCUfVtBs3HRn3a822LW02vGqopSkaGdRzQZOI28136AMeW4679UXE852srA2v3mWHQ"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>\$rsaPub) } or warn $@; + my $expected = '{"hello": "world"}'; + is($json, $expected, 'PS384'); +} +### PS512 +{ + my $token = "eyJhbGciOiJQUzUxMiIsImN0eSI6InRleHRcL3BsYWluIn0.eyJoZWxsbyI6ICJ3b3JsZCJ9.IvbnmxhKvM70C0n0grkF807wOQLyPOBwJOee-p7JHCQcSstNeml3Owdyw9C3HGHzOdK9db51yAkjJ2TCojxqHW4OR5Apna8tvafYgD2femn1V3GdkGj6ZvYdV3q4ldnmahVeO36vHYy5P0zFcEGU1_j3S3DwGmhw2ktZ4p5fLZ2up2qwhzlOjbtsQpWywHj7cLdeA32MLId9MTAPVGUHIZHw_W0xwjJRS6TgxD9vPQQnP70MY-q_2pVAhfRCM_pauPYO1XH5ldizrTvVr27q_-Uqtw-wV-UDUnyWYQUDDiMTpLBoX1EEXmsbvUGx0OH3yWEaNINoCsepgZvTKbiEQQ"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>\$rsaPub) } or warn $@; + my $expected = '{"hello": "world"}'; + is($json, $expected, 'PS512'); +} +### RSA_OAEP_A128CBC_HS256 +{ + my $token = "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhDQkMtSFMyNTYifQ.dgIoddBRTBLi8b6fwjaIU5uUP_J-6jL5AtIvoNZDwN0JSmsXkm9SIFz7kQfwavBz_PPG6h0yId55YVFnCqrB5qCIbifmBQPEcB5acKCybHuoHhEBCnQpqxVtHLXZ0dUyd6Xs5h9ymgbbZMjpAoCUK7si90m4O5BCSdedZNQvdXWQW599CRftFVVe_mZOcgABuNIDMfIwyxmi2DVR5c2bSA0ji2Sy27SE_X0lCVHqrAwI-8Rlz1WTWLI6bhRh2jsUPK-6958E4fsXOWsTOp9fW97eW85InZPniv8B5HSG_D0NALhu5AIMsNt-ENeR0sefcphZGUzfyFoxK7EMpY7gAQ.jNw5xfYCvwHvviSuUFYpfw.0_Rvs5cA_QKSVMGbPr5ntFrd_BQhTql-hB9fzLhndAy9vLeHBLtv-bXeZatw4QJIufnpsSnXmRYjKqvWVCp-x-AKpPWzkaj6fvsQ8Mns1kWw5XZr-8SJrbT72LOnRBcTd4qjOYXEJZad8uIwQHDFkkmpm4d7FQ6PhW0-1gOS8FGuYjUupYDQX2ia-4jzqWisv2bE-mKn65q5wy_dT0w04rF-Mk_USyOG5d09kne3ZBv42stpS_xyDS3euVtPuxhQT5TzfPpBkG3CNwwm_HvTTg.E2opVK9nQXPXJbDKb06FBg"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>\$rsaPriv) } or warn $@; + my $expected = '{"exp":1391196668,"sub":"alice","nbf":1391196068,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"03ac026e-55aa-4475-a806-f09e83048922","iat":1391196068}'; + is($json, $expected, 'RSA_OAEP_A128CBC_HS256'); +} +### RSA_OAEP_A128CBC_HS256_Compressed +{ + my $token = "eyJhbGciOiJSU0EtT0FFUCIsInppcCI6IkRFRiIsImVuYyI6IkExMjhDQkMtSFMyNTYifQ.nXSS9jDwE0dXkcGI7UquZBhn2nsB2P8u-YSWEuTAgEeuV54qNU4SlE76bToI1z4LUuABHmZOv9S24xkF45b7Mrap_Fu4JXH8euXrQgKQb9o_HL5FvE8m4zk5Ow13MKGPvHvWKOaNEBFriwYIfPi6QBYrpuqn0BaANc_aMyInV0Fn7e8EAgVmvoagmy7Hxic2sPUeLEIlRCDSGa82mpiGusjo7VMJxymkhnMdKufpGPh4wod7pvgb-jDWasUHpsUkHqSKZxlrDQxcy1-Pu1G37TAnImlWPa9NU7500IXc-W07IJccXhR3qhA5QaIyBbmHY0j1Dn3808oSFOYSF85A9w.uwbZhK-8iNzcjvKRb1a2Ig.jxj1GfH9Ndu1y0b7NRz_yfmjrvX2rXQczyK9ZJGWTWfeNPGR_PZdJmddiam15Qtz7R-pzIeyR4_qQoMzOISkq6fDEvEWVZdHnnTUHQzCoGX1dZoG9jXEwfAk2G1vXYT2vynEQZ72xk0V_OBtKhpIAUEFsXwCUeLAAgjFNY4OGWZl_Kmv9RTGhnePZfVbrbwg.WuV64jlV03OZm99qHMP9wQ"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>\$rsaPriv) } or warn $@; + my $expected = '{"exp":1392963710,"sub":"alice","nbf":1392963110,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"9fa7a38a-28fd-421c-825c-8fab3bbf3fb4","iat":1392963110}'; + is($json, $expected, 'RSA_OAEP_A128CBC_HS256_Compressed'); +} +### RSA_OAEP_A256CBC_HS512 +{ + my $token = "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZDQkMtSFM1MTIifQ.gCLatpLEIHdwqXGNQ6pI2azteXmksiGvHZoXaFmGjvN6T71ky5Ov8DHmXyaFdxWVPxiPAf6RDpJlokSR34e1W9ey0m9xWELJ_hH_bEoH4s7-wI74edS06i35let0YvCubl3eIemuQNkaJEqoEaHx8sLZ-SsoRxi7tRAIABl4f_THC8CDLw7SXrVcp_b6xRtB9oSI2_y_vSAZXOTZPJBw_t4jwZLnsOUbBXXGKAGIpG0rrL8qMt1KwRW_79qQie2U1kDclD7EVMQ-ji5upayxaXOMLkPqfISgvyGKyaLs-8e_aRyVKbMpkCZNWaLnSAA6aJHynNsnuM8O4iEN-wRXmw.r2SOQ2k_YqZRpoIB6wSbqA.DeYxdBzfRiiJeAm8H58SO8NJCa4yg3beciqZRGiAqQDrFYdp9q1RHuNrd0UY7DfzBChW5Gp37FqMA3eRpZ_ERbMiYMSgBtqJgUTKWyXGYItThpg92-1Nm7LN_Sp16UOSBHMJmbXeS30NMEfudgk3qUzE2Qmec7msk3X3ylbgn8EIwSIeVpGcEi6OWFCX1lTIRm1bqV2JDxY3gbWUB2H2YVtgL7AaioMttBM8Mm5plDY1pTHXZzgSBrTCtqtmysCothzGkRRzuHDzeaWYbznkVg.Hpk41zlPhLX4UQvb_lbCLZ0zAhOI8A0dA-V31KFGs6A"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>\$rsaPriv) } or warn $@; + my $expected = '{"exp":1391196668,"sub":"alice","nbf":1391196068,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"19539b79-e5cf-4f99-a66e-00a980e1b0a9","iat":1391196068}'; + is($json, $expected, 'RSA_OAEP_A256CBC_HS512'); +} +### RSA_OAEP_A192CBC_HS384 +{ + my $token = "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExOTJDQkMtSFMzODQifQ.BZ8MgMgby05auOw-Gb4ii-fgcRWAlCHd6pMZNFafle6BAT1accRGUsMGRzJRETUFFqoy3rzfdSdFcqgc7lmUQUXrVei6XCRei5VZJo1YlzIPN9rEig3sSJ99hg1mrXh3ezFX_JczTn7xEaRRzdatnkSvWBMMmbMWVjqlpkXSOr7P7x2Ctf-GQwXOKEVUrRFwe2D0qXC0ynWKrm7mkV-tlRHJf5NRdWLT5Tmxka8OJZ0W1MyJKNEemEMt1dThcnedPMBjb8y0IwPZ8Aiam87fWdqk20MDknNyxRoC_epBFZFaWFpZ383mKI2Ev-EqO2lCnFOkSvwcNmhnlOPXHJ40qQ.1aAvdZ8g580VUE55RqRBVw.IkoVJF73DSzi-ebiErrCAtpWPepbFZS6DX0S9Ka85aRfgmLQRQxBucxm48MixkRJ5QYCPGmtXRPyiQQE9zT1aA5Js6BoV8U2JK44HWga4cNkyUUr0Wpu0uz6GEBU620i9DmJasTb4iA3iTMboCpdrCTlzhJrYhSYc09Jo0WJRM83LjorxRjpUmLGqR4SgV1WYFKaben4iSqOVPThzQc7HEGrkbycZRNKj-BAkll7qRtN_1e5k83W9Wlf5taAWwSXMF2VL6XqR0bZXpPcpLi_vw.kePqK6KpRWohWEpSg8vfeCd0PQAqBmjW"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>\$rsaPriv) } or warn $@; + my $expected = '{"exp":1391196668,"sub":"alice","nbf":1391196068,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"59f54c91-5224-4484-9c3a-e57b87b6f212","iat":1391196068}'; + is($json, $expected, 'RSA_OAEP_A192CBC_HS384'); +} +### RSA_OAEP_256_A128CBC_HS256 +{ + my $token = "eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.bje66yTjMUpyGzbt3QvPNOmCmUPowgEmoBHXw-pByhST2VBSs0_67JKDymKW0VpmQC5Qb7ZLC6nNG8YW5pxTZDOeTQLodhAvzoNAsrx4M2R_N58ZVqBPLKTq7FKi1NNd8oJ80dwWbOJ13dkLH68SlhOK5bhqKFgtbzalnglL2kq8Fki1GkN4YyFnS8-chC-mlrS5bJrPSHUF7oAsG_flL_e9-KzYqYTQgGCB3GYSo_pgalsp2rUO3Oz2Pfe9IEJNlX7R9wOT1nTT0UUg-lSzQ2oOaXNvNyaPgEa76mJ1nk7ZQq7ZNix1m8snjk0Vizd8EOFCSRyOGcp4mHMn7-s00Q.tMFMCdFNQXbhEnwE6mP_XQ.E_O_ZBtJ8P0FvhKOV_W98oxIySDgdd0up0c8FAjo-3OVZ_6XMEQYFDKVG_Zc3zkbaz1Z2hmc7D7M28RbhRdya3yJN6Hcv1KuXeZ9ociI7o739Ni_bPvv8xCmGxlASS5AF7N4JR7XjrWL-SYKGNL1p0XNTlPo3B3qYqgAY6jFNvlcjWupim-pQbWKNqPbO2KmSCtUzyKE5oHjsomH0hnQs0_DXv3cgQ_ZFLFZBc1tC4AjQ8QZex5kWg5BmlJDM5F_jD7QRhb7B1u4Mi563-AKVA.0lraw3IXMM6wPqUZVYA8pg"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>\$rsaPriv) } or warn $@; + my $expected = '{"exp":1392553211,"sub":"alice","nbf":1392552611,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"586dd129-a29f-49c8-9de7-454af1155e27","iat":1392552611}'; + is($json, $expected, 'RSA_OAEP_256_A128CBC_HS256'); +} +### RSA_OAEP_256_A192CBC_HS384 +{ + my $token = "eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMTkyQ0JDLUhTMzg0In0.COuKvozBVi2vkEPpFdx0HTMpU9tmpP1lLngbmGn8RVphY-vjhVaduv8D_Ay_1j8LuMz4tgP98xWtbJkTyhxY1kBwXe0CgqFUOSJ1mTEPRkKSXpdFR7rT1Pv68qug2yKaXT_qcviyBerIcUVFbXBmtiYAosYO4kaPSOE1IvLadFOrMkxdZv6QiiCROzWgJNCCMgNQZGRoPhqLe3wrcxi86DhNO7Bpqq_yeNVyHdU_qObMuMVZIWWEQIDhiU4nE8WGJLG_NtKElc_nQwbmclL_YYgTiHsIAKWZCdj0nwfLe5mwJQN4r7pjakiUVzCbNNgI1-iBH1vJD5VCPxgWldzfYA.7cDs4wzbNDt1Kq40Q5ae4w.u1bR6ChVd90QkFIp3H6IkOCIMwf5aIKsQOvqgFangRLrDjctl5qO5jTHr1o1GwBQvAkRmaGSE7fRIwWB_l-Ayx2c2WDFOkVXFSR_D23GrWaLMLbugPItQd2Mny6H4QOzO3O0EK_Qm7frqwKQI3og72SB8DUqzEaKsrz7HR2z_qMa2CEEApxai_R6NIlAdMUbYvOfZx262MWFGrITBDmma-Mnqiz9WJUv2wexfwjROaaS4wXfkGy5B6ltESifpZZk5NerExR3GA6yX7cFqJc4pQ.FKcbLyB9eP1UXmxyliTu1_GQrnS-JtAB"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>\$rsaPriv) } or warn $@; + my $expected = '{"exp":1392553211,"sub":"alice","nbf":1392552611,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"586dd129-a29f-49c8-9de7-454af1155e27","iat":1392552611}'; + is($json, $expected, 'RSA_OAEP_256_A192CBC_HS384'); +} +### RSA_OAEP_256_A256CBC_HS512 +{ + my $token = "eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIn0.Pt1q6MNdaiVWhMnY7r6DVpkYQmzyIjhb0cj10LowP_FgMu1dOQVuNwhK14MO1ki1y1Pvxouct9wwmb5gE7jNJBy6vU-FrrY62WNr_hKL3Cq2030LlJwauv1XQrEE-GCw1srxOAsw6LNT14v4f0qjeW46mIHNX4CZMEO9ntwojWsHTNsh4Qk6SU1QlS3WbbVl7gjjfqTP54j2ZwZM38s7Cs4pSAChP04UbW6Uhrm65JSi0lyg25OBXIxMEt1z9WY8lnjuh3iL_WttnFn9lf5fUuuR2N70HwANz2mxH3CxjO0ygXJtV-FhFzz3HqI2-ELrve4Igj_2f2_S6OrRTWRucA.er5K9Gk0wp3wF_sq7ib7BQ.L80B9FGSjUbEblpJ6tuiaq6NAsW89YQGD0awxtE-irKN65PT8nndBd0hlel8RRThXRF0kiYYor2GpgvVVaoOzSQcwL-aDgNO7BeRsaOL5ku2NlyT1erbg_8jEVG5BFMM0-jCb4kD0jBKWYCGoB7qs_QQxZ394H5GPwG68vlizKEa8PoaNIM0at5oFT7EHPdmGmwQyQCHR43e6uN4k28PWNxjN9Ndo5lvlYnxnAyDGVDu8lCjozaA_ZTrEPS-UBb6lOEW39CXdwVk1MgvyQfswQ.yuDMf_77Wr9Er3FG1_0FwHXJTOVQPjzBwGoKEg81mQo"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>\$rsaPriv) } or warn $@; + my $expected = '{"exp":1392553211,"sub":"alice","nbf":1392552611,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"586dd129-a29f-49c8-9de7-454af1155e27","iat":1392552611}'; + is($json, $expected, 'RSA_OAEP_256_A256CBC_HS512'); +} +### RSA_1_5_A128CBC_HS256 +{ + my $token = "eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.bx_4TL7gh14IeM3EClP3iVfY9pbT81pflXd1lEZOVPJR6PaewRFXWmiJcaqH9fcU9IjGGQ19BS-UPtpErenL5kw7KORFgIBm4hObCYxLoAadMy8A-qQeOWyjnxbE0mbQIdoFI4nGK5qWTEQUWZCMwosvyeHLqEZDzr9CNLAAFTujvsZJJ7NLTkA0cTUzz64b57uSvMTaOK6j7Ap9ZaAgF2uaqBdZ1NzqofLeU4XYCG8pWc5Qd-Ri_1KsksjaDHk12ZU4vKIJWJ-puEnpXBLoHuko92BnN8_LXx4sfDdK7wRiXk0LU_iwoT5zb1ro7KaM0hcfidWoz95vfhPhACIsXQ.YcVAPLJ061gvPpVB-zMm4A.PveUBLejLzMjA4tViHTRXbYnxMHFu8W2ECwj9b6sF2u2azi0TbxxMhs65j-t3qm-8EKBJM7LKIlkAtQ1XBeZl4zuTeMFxsQ0VShQfwlN2r8dPFgUzb4f_MzBuFFYfP5hBs-jugm89l2ZTj8oAOOSpAlC7uTmwha3dNaDOzlJniqAl_729q5EvSjaYXMtaET9wSTNSDfMUVFcMERbB50VOhc134JDUVPTuriD0rd4tQm8Do8obFKtFeZ5l3jT73-f1tPZwZ6CmFVxUMh6gSdY5A.tR8bNx9WErquthpWZBeMaw"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>\$rsaPriv) } or warn $@; + my $expected = '{"exp":1391196668,"sub":"alice","nbf":1391196068,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"3814fff3-db66-45d9-a29a-d2cc2407bdcf","iat":1391196068}'; + is($json, $expected, 'RSA_1_5_A128CBC_HS256'); +} +### RSA_1_5_A192CBC_HS384 +{ + my $token = "eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTkyQ0JDLUhTMzg0In0.ApUpt1SGilnXuqvFSHdTV0K9QKSf0P6wEEOTrAqWMwyEOLlyb6VR8o6fdd4wXMTkkL5Bp9BH1x0oibTrVwVa50rxbPDlRJQe0yvBm0w02nkzl3Tt4fE3sGjEXGgI8w8ZxSVAN0EkaXLqzsG1rQ631ptzqyNzg9BWfy53cHhuzh9w00ZOXZtNc7GFBQ1LRvhK1EyLS2_my8KD091KwsjvXC-_J0eOp2W8NkycP_jCIrUzAOSwz--NZyRXt9V2o609HGItKajHplbE1PJVShaXO84MdJl3X6ef8ZXz7mCP3dRlsYfK-tlnFVeEKwC1Oy_zdFsdiY4j41Mj3usvG2j7xQ.GY4Em2zkSGMZsDLNr9pnDw.GZYJSpeQHmOtx34dk4WxEPCnt7l8R5oLKd3IyoMYbjZrWRtomyTufOKfiOVT-nY9ad0Vs5w5Imr2ysy6DnkAFoOnINV_Bzq1hQU4oFfUd_9bFfHZvGuW9H-NTUVBLDhok6NHosSBaY8xLdwHL_GiztRsX_rU4I88bmWBIFiu8T_IRskrX_kSKQ_iGpIJiDy5psIxY4il9dPihLJhcI_JqysW0pIMHB9ij_JSrCnVPs4ngXBHrQoxeDv3HiHFTGXziZ8k79LZ9LywanzC0-ZC5Q.1cmUwl7MnFl__CS9Y__a8t5aVyI9IKOY"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>\$rsaPriv) } or warn $@; + my $expected = '{"exp":1391196668,"sub":"alice","nbf":1391196068,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"c9d44ff8-ff1e-4490-8454-941e45766152","iat":1391196068}'; + is($json, $expected, 'RSA_1_5_A192CBC_HS384'); +} +### RSA_1_5_A256CBC_HS512 +{ + my $token = "eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIn0.GVXwkd5rfqffr4ue26IGHXuiV6r-rQa9OQ4B1LtodsTpWfraOLyhyHYseEKpXV4aSMWWN0q2HS0myj73BuGsDMP-xiIM04QxWD7dbP2OticXzktcHHhMFUx0OK_IOmc21qshTqbb0yKWizMnCuVosQqw2tg_up2sgjqIyiwzpgvC5_l9ddxnTBV334LF_nXTnL22vqrUO92rH_3YmoJ6khHUYVSXhd0fXTKqwm9liULW43prDWkex0N8a8MfgdaFPq0rGw4gRA8HvS7aFn3xCeKAO9Q_q-g32DCDwbfqYhvGZCbS49ObwfPD-fKaFS94VFSMb_Cy-WalZwrIz-aWkQ.zh6hViRORvk4b-2io1vUSA.Us26-89QEOWb85TsOZJpH6QB5_GR3wZo49rR38X1daG_kmyfzIUQQ12wBwmxFwHluNvqStVj4YUIvPgC4oZEh1L-r3Tm81Q2PctdMrwl9fRDR6uH1Hqfx-K25vEhlk_A60s060wezUa5eSttjwEHGTY0FpoQvyOmdfmnOdtW_LLyRWoRzmGocD_N4z6BxK-cVTbbTvAYVbWaZNW_eEMLL4qAnKNAhXJzAtUTqJQIn0Fbh3EE3j827hKrtcRbrwqr1BmoOtaQdYUO4VZKIJ7SNw.Zkt6yXlSu9BdknCr32uyu7uH6HVwGFOV48xc4Z7wF9Y"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>\$rsaPriv) } or warn $@; + my $expected = '{"exp":1391196668,"sub":"alice","nbf":1391196068,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"7efcdbc6-b2b5-4480-985d-bdf741b376bb","iat":1391196068}'; + is($json, $expected, 'RSA_1_5_A256CBC_HS512'); +} +### RSA_OAEP_A128GCM +{ + my $token = "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.Izae78a1L2Z0ai_aYbvVbWjiZwz3DTlD27c4Jh44SZAz7T_w7GHiWGuxa4CYPq4Ul_9i5qpdUK1WJOTxlL8C-TXbWzxgwhs-DdmkRBmI5JWozc6RIYz2ddYBIPDTpOSbg_nwVzCUkqId6PwATSPiYjLY0ZwsSung1JGuSKU5WHzdCLh8cXKFdSNo4PA6xxuIFqDWNeshSvbUhK-xPL_ySPSLGtMfzUocPi--SDnc867a92WZpnCwLbpAqlGcj1u-nrpXjlTdECbZbPH5mggnIU8Xrzi6OIRTf2RPOxk2nYcW-KkzsERSUUmoIStaTnnq6MzRLKdF-eOolVaPEB94tQ.dBju23LfGAmbhKQl.l-hxA-_Jj9X-Kbq6W_7XNSxeeDaZc_YFoHRIBclWn2ebd_1qbZ3Td8aPsxBwe4Mc0KP7JdTnDXH53ajtdo2CQaPIaxNh-ffZkUZCi7o-tM_SRyt1MkUnoxQ5ib4i5lzJNEJyklf7lHQhjUhUa2FKTS1KJvLo0uChw5Gb-Y_7S_BUfOzTDCFQR4XFbpd7ngCWww4skpHEulhBhSr66RGog4wwac_ucfSTKeKxZw0UhHBIZFIAju4zcoN8Abh23JHh0VETiA.FFFvIyv5vq_cE1xIPYn6Wg"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>\$rsaPriv) } or warn $@; + my $expected = '{"exp":1391705293,"sub":"alice","nbf":1391704693,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"2f3b5379-a851-4202-ac9a-85baae41459e","iat":1391704693}'; + is($json, $expected, 'RSA_OAEP_A128GCM'); +} +### RSA_OAEP_A192GCM +{ + my $token = "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExOTJHQ00ifQ.QUBq_S563qAz9KA1oQDPLQ8Upfaf5XWeLLo6w_BpZRPUUshnSsf5GYmxeakuVCGywv2mKR7pEd0EzzA4vL1l3tc8woftrA_jDSU9lQp_Icuwqg9pzBswv7ofKegK7ch7KhOuWGaeFz6mdoUESHhdEPmhVZwz7ryrKWj_TlL-Cr7UG8MpHWV_bvohdtLReTSqfbUbcT_iSY4Nid7RHca__0dmSWgEM2Sydmesv8KJzuoyI6xgGLCaw_p46GuZ4XhM88scV7doV7f3mEv7AYTDMJz4Q5_8lz_gIDDyloTx3-tC9a9KlDVSC3XkPppfQwwjSt-yWhh9SZmsPIpC_K6ubA.pUr3CK_0cTGIODJx.TBD3RZ2nJGNSns_iOOvruxC2Dr-4SsClKIwPXIt8zIjKtKub8o1lFqaRwlBfyciPNMiCqqocWR8zwyNNDFBIAUYJMBW6SPuFzJv8mrjlV_aRsfFmYjpw9U3-n0u-noYHT5U1FXy6feUY907AIqbcEKkHF0TfjEXLfuKVvJHNjaqS84-UFZqmxN0U2szRCo8-k6omS32pRDwTGgl1Co9yXSBUAXtGoi02uqOKpAFbtxfD8_6P41N7-HK86l94m5x1uNCViQ.9xj6WjYwh4OCUr-GKl7_yQ"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>\$rsaPriv) } or warn $@; + my $expected = '{"exp":1391710436,"sub":"alice","nbf":1391709836,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"6b646d08-2871-4e0d-bfa8-48a9e1bd6de5","iat":1391709836}'; + is($json, $expected, 'RSA_OAEP_A192GCM'); +} +### RSA_OAEP_A256GCM +{ + my $token = "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.M7PuAabTBMnrudthuZNYWfwkXlv1KVxsSYjpaRmuStqybglofWHK37wWWcF5JMYmJfRjswXlGf-iHjw2aSfGpmTJBdUYYbchMtn2TKnjU03piFaqWN3D9384nq_4NJeRkwwe7uYD3iGDxeemJpJLjqpXj5cXgK5Xd93TtJ-QB9hIpXtyDqOlLdoooMWKG8Y9cIBdbwCza57KzOm1S5and_3E4IgijvtRlqENzeLesH3jT3P2310nDEn60j7eqHCeXWR8lUKMZudVCY7f9lkGpotKQeJpxDG1Sd2EG_GiOK5DwpR_1CimkE3c4y1qUoFM10Pjzf7IqZJL1HBAMHcXxQ.9a8lpyZcMoPi2qJb.TKWSdvz395ZfzsjDV6r9mhMdU5XZ14pCcna5EkoA1wmolDAth9qqYAPJErbfZfUAptbUFDitLlsnnnIIhej-N_42XIQnu14Wz0G-sizAn78jKjf145ckDYt63qaX4SxBW6-SQqSCYV4Nz6t0DUBMLK9UcYsVQ2e3Ur5YvxcnTeFM9FqgUiEz9IiNlsJXwZ1HN-LTp0412YCELxoxUu3Bg7R_GWHx2iUliBnRN4WcvRhYMApI_o3qAoK4StTgCQJu-laPdg.Rztnz6rBQ2aSlDHKORI5AA"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>\$rsaPriv) } or warn $@; + my $expected = '{"exp":1391710647,"sub":"alice","nbf":1391710047,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"0b0f3b1b-8f36-4ee2-b463-54263b4af8b7","iat":1391710047}'; + is($json, $expected, 'RSA_OAEP_A256GCM'); +} +### RSA1_5_A128GCM +{ + my $token = "eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4R0NNIn0.FojyyzygtFOyNBjzqTRfr9HVHPrvtqbVUt9sXSuU59ZhLlzk7FrirryFnFGtj8YC9lx-IX156Ro9rBaJTCU_dfERd05DhPMffT40rdcDiLxfCLOY0E2PfsMyGQPhI6YtNBtf_sQjXWEBC59zH_VoswFAUstkvXY9eVVecoM-W9HFlIxwUXMVpEPtS96xZX5LMksDgJ9sYDTNa6EQOA0hfzw07fD_FFJShcueqJuoJjILYbad-AHbpnLTV4oTbFTYjskRxpEYQr9plFZsT4_xKiCU89slT9EFhmuaiUI_-NGdX-kNDyQZj2Vtid4LSOVv5kGxyygThuQb6wjr1AGe1g.O92pf8iqwlBIQmXA.YdGjkN7lzeKYIv743XlPRYTd3x4VA0xwa5WVoGf1hiHlhQuXGEg4Jv3elk4JoFJzgVuMMQMex8fpFFL3t5I4H9bH18pbrEo7wLXvGOsP971cuOOaXPxhX6qClkwx5qkWhcTbO_2AuJxzIaU9qBwtwWaxJm9axofAPYgYbdaMZkU4F5sFdaFY8IOe94wUA1Ocn_gxC_DYp9IEAyZut0j5RImmthPgiRO_0pK9OvusE_Xg3iGfdxu70x0KpoItuNwlEf0LUA.uP5jOGMxtDUiT6E3ubucBw"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>\$rsaPriv) } or warn $@; + my $expected = '{"exp":1391711317,"sub":"alice","nbf":1391710717,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"cadf3d33-a109-4829-a869-94a4bfbb4cbf","iat":1391710717}'; + is($json, $expected, 'RSA1_5_A128GCM'); +} +### RSA1_5_A192GCM +{ + my $token = "eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTkyR0NNIn0.f-g3EVuoVHRWRnhuQTa0s4V6kKWdCZrQadK27AcmjYNuBAQL26GpiZe1fBggGdREMjXejQjykDd0GGGzdk3avSLlfOuAHb6D5TJE6DB67v_Fjn29_ky-9L6fZmLUKlHOYbE5H2cnkUk9eI3mT0_VJLfhkh3uOAvs1h31NGBmVcQJOLoyH9fa6nTt1kddubsnkHLMfjS_fm4lKOBv2e1S4fosWYEVM9ylpfL-wSYJYrDtEsy_r9lTv19OmrcjtQoqoF9kZ9bMm7jOcZWiG00nw5Zbo2nze_y-bSngJcA7jIutf0zmFxa9GsIVvseQbqcLYZoiACMqEp0HgPg2xfBPmw.mzEicVwcsRKNKrHs.jW2bcx2pxSK2a8NNZSydArUd3JgRQl_dX5N8i5REBeR7WmtgL9aHXyrGqy9rl-iUb6LZMjMFG4tDqOetJjS48OUzMgvLZH3tui_oL8m9ZHWG-yl079uJZSIWU-icHuWzSjbc4ExPu1IXCcTnBGIjid5PM3HAfmWtVP5Pv0q6qeuvzMXvLG7YcZtuS5dTSu1pZTW7O5BEaxy9AvC0-xr0SlTdEEVCT_kZIprhIT7XiGnuMUztx83AxuO-FYXZeL5iXMW8hQ.H9qkfReSyqgVkiVt53fh-Q"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>\$rsaPriv) } or warn $@; + my $expected = '{"exp":1391711482,"sub":"alice","nbf":1391710882,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"7488a8fc-345a-42c2-971e-a286c14fc5af","iat":1391710882}'; + is($json, $expected, 'RSA1_5_A192GCM'); +} +### RSA1_5_A256GCM +{ + my $token = "eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMjU2R0NNIn0.arMHtbGJv2mi7COD9WTz_FzUPJ8Jq1qUTMc5C3IKGD7RNeV1oiv1AgCPiChTuu-UGA56iGJXbFAE7x2jSFK_foKvRvZxyCKz6Siy18seHoz1iw8gU2A_mMG7IEPVcA8MmbMVawFTXoMdLBeW9CsV_102wmZFeh2S74f80XogE63Nd3VjE3LaSbatnXQxIaD0Meq9ZqrKUFZS5SY-FyKqWrdjH82MZP8lrBLDTkTXx4bkfoZForimE1oIEykanpv-tnAlQNFlqRPJsGy-HtcEoHQ7E1Xkqxg9kULmF4TeqiyQ0HBfXXBbm3pQ43GUPmbFJW-l7W6vDAc9-41BCNChQw.kryfKm1U6NobSiyI.kMQXbCKGdeh_vqj6J7wQ1qP48q4VQv5zGZIJp0FgIlk0Lrv9XP4ExlgYlPb24mr1W43d2rY0OJ9fDgPnoTk_cQ6kpXL3nSBo82yBTBA_g6UyIJ4b1PIOpJv_RANA-b8TwQwGtg0eMr_5il4QQWfB_AxnvCe9CDyTkNo7befER3706xilqm6aHdryZx3Hk6C9hbrSe0xW96uor1Js2b-UWRcCJDFQK5Ux9IAHy2Utqsqv7qDq0Ai5pVQOMjyq3iKmUuOOEg.rqSGPBypVniu58fdHswm7g"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>\$rsaPriv) } or warn $@; + my $expected = '{"exp":1391711482,"sub":"alice","nbf":1391710882,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"77a31aed-f546-4b1d-ba77-9455a2e0a3d5","iat":1391710882}'; + is($json, $expected, 'RSA1_5_A256GCM'); +} +### RSA1_5_A256GCM_DEFLATE +{ + my $token = "eyJhbGciOiJSU0ExXzUiLCJ6aXAiOiJERUYiLCJlbmMiOiJBMjU2R0NNIn0.imMUAOkYe54TzNknmrUkWOtjgGlbSivDyFRbvebC1rT9ixxQOTN-bCGiLwyEoPLdkroEvvR1cf_abR_afZIfWsk6Om09aar9JQkA7KMNoTRBQnn7X7BX_agpZuhRzPo_gQDXA0fll10j9OdUTcXd7oSw6FVb4non2qyO2ZvwT1UANY3SbQchlQrXnQpjQluR1tkxWXo-5p3o9MQEIqyypOQyGKIIXJlBtcUkWz0PHHsqJ3OdZus7dbwajv5GpHmLfT8Q2aPZN5QX1zv4h2y8vD6RYn6evLCc7e7Gp1z7C5WOZXDA6hyYQiL3Y92zzxVVD5E7nt94WSktxjM-y65TQw.g3FCuDmLISjam69Q.PrMnFDnuYNkLvmR8QmmEu6NB9N6ecJy6gMSR1fYEkZLz2jMtxN-OTaudX901_SWCX_dDFgpmOPziQRJ1IYOiySZ3N0FFyWxemJgHjVOZaPpu5ZSTH7JYoH5CLBpD1H9VMX5vC5SUH7hWgLZ_NCgVs0eZt_3_AyUObVAInNNTH_pNjhdjV8xuCCE.rDEvIPtM1fNjpDvD62x2PA"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>\$rsaPriv) } or warn $@; + my $expected = '{"exp":1392994388,"sub":"alice","nbf":1392993788,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"81b338eb-346e-4b04-a618-d3cbb2d64ec6","iat":1392993788}'; + is($json, $expected, 'RSA1_5_A256GCM_DEFLATE'); +} +### DIR_A128GCM +{ + my $token = "eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4R0NNIn0..yVi-LdQQngN0C5WS.1McwSmhZzAtmmLp9y-OdnJwaJFo1nj_4ashmzl2LhubGf0Jl1OTEVJzsHZb7bkup7cGTkuxh6Vfv10ljHsjWf_URXoxP3stQqQeViVcuPV0y2Q_WHYzTNGZpmHGe-hM6gjDhyZyvu3yeXGFSvfPQmp9pWVOgDjI4RC0MQ83rzzn-rRdnZkznWjbmOPxwPrR72Qng0BISsEwbkPn4oO8-vlHkVmPpuDTaYzCT2ZR5K9JnIU8d8QdxEAGb7-s8GEJ1yqtd_w._umbK59DAKA3O89h15VoKQ"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>$aes128Key) } or warn $@; + my $expected = '{"exp":1392548520,"sub":"alice","nbf":1392547920,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"0e659a67-1cd3-438b-8888-217e72951ec9","iat":1392547920}'; + is($json, $expected, 'DIR_A128GCM'); +} +### DIR_A192GCM +{ + my $token = "eyJhbGciOiJkaXIiLCJlbmMiOiJBMTkyR0NNIn0..YW2WB0afVronbgSz.tfk1VADGjBnViYD7He5mbhxpbogoT1cmhKiDKzzoBV2AxfsgJ2Eq-vtEqPi9eY9H52FLLtht26rc5fPz9ZKOUH2hYeFdaRyKYXlpEnUR2cCT9_3TYcaFhpYBH4HCa59NruKlJHMBqM2ssWZLSEblFX9srUHFtu2OQz2ydMy1fr8ABDTdVYgaqyBoYRGykTkEsgayEyfAMz9u095N2J0JTCB5Q0IiXNdBzBSxZXG-i9f5HFEb6IliaTwFTNFnhDL66O4rsg._dh02z25W7HA6b1XiFVpUw"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>$aes192Key) } or warn $@; + my $expected = '{"exp":1392552631,"sub":"alice","nbf":1392552031,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"a3fea096-2e96-4d8b-b7cd-070e08b533fb","iat":1392552031}'; + is($json, $expected, 'DIR_A192GCM'); +} +### DIR_A256GCM +{ + my $token = "eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0..Fmz3PLVfv-ySl4IJ.LMZpXMDoBIll5yuEs81Bws2-iUUaBSpucJPL-GtDKXkPhFpJmES2T136Vd8xzvp-3JW-fvpRZtlhluqGHjywPctol71Zuz9uFQjuejIU4axA_XiAy-BadbRUm1-25FRT30WtrrxKltSkulmIS5N-Nsi_zmCz5xicB1ZnzneRXGaXY4B444_IHxGBIS_wdurPAN0OEGw4xIi2DAD1Ikc99a90L7rUZfbHNg_iTBr-OshZqDbR6C5KhmMgk5KqDJEN8Ik-Yw.Jbk8ZmO901fqECYVPKOAzg"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>$aes256Key) } or warn $@; + my $expected = '{"exp":1392552841,"sub":"alice","nbf":1392552241,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"efdfc02f-945e-4e1f-85a6-9f240f6cf153","iat":1392552241}'; + is($json, $expected, 'DIR_A256GCM'); +} +### DIR_A128CBC_HS256 +{ + my $token = "eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0..3lClLoerWhxIc811QXDLbg.iFd5MNk2eWDlW3hbq7vTFLPJlC0Od_MSyWGakEn5kfYbbPk7BM_SxUMptwcvDnZ5uBKwwPAYOsHIm5IjZ79LKZul9ZnOtJONRvxWLeS9WZiX4CghOLZL7dLypKn-mB22xsmSUbtizMuNSdgJwUCxEmms7vYOpL0Che-0_YrOu3NmBCLBiZzdWVtSSvYw6Ltzbch4OAaX2ye_IIemJoU1VnrdW0y-AjPgnAUA-GY7CAKJ70leS1LyjTW8H_ecB4sDCkLpxNOUsWZs3DN0vxxSQw.bxrZkcOeBgFAo3t0585ZdQ"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>$aes256Key) } or warn $@; + my $expected = '{"exp":1392553211,"sub":"alice","nbf":1392552611,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"586dd129-a29f-49c8-9de7-454af1155e27","iat":1392552611}'; + is($json, $expected, 'DIR_A128CBC_HS256'); +} +### ECDH_ES_A128CBC_HS256 +{ + my $token = "eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTEyOENCQy1IUzI1NiIsImVwayI6eyJrdHkiOiJFQyIsIngiOiItVk1LTG5NeW9IVHRGUlpGNnFXNndkRm5BN21KQkdiNzk4V3FVMFV3QVhZIiwieSI6ImhQQWNReTgzVS01Qjl1U21xbnNXcFZzbHVoZGJSZE1nbnZ0cGdmNVhXTjgiLCJjcnYiOiJQLTI1NiJ9fQ..UA3N2j-TbYKKD361AxlXUA.XxFur_nY1GauVp5W_KO2DEHfof5s7kUwvOgghiNNNmnB4Vxj5j8VRS8vMOb51nYy2wqmBb2gBf1IHDcKZdACkCOMqMIcpBvhyqbuKiZPLHiilwSgVV6ubIV88X0vK0C8ZPe5lEyRudbgFjdlTnf8TmsvuAsdtPn9dXwDjUR23bD2ocp8UGAV0lKqKzpAw528vTfD0gwMG8gt_op8yZAxqqLLljMuZdTnjofAfsW2Rq3Z6GyLUlxR51DAUlQKi6UpsKMJoXTrm1Jw8sXBHpsRqA.UHCYOtnqk4SfhAknCnymaQ"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>$Ecc256Private) } or warn $@; + my $expected = '{"exp":1392553211,"sub":"alice","nbf":1392552611,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"586dd129-a29f-49c8-9de7-454af1155e27","iat":1392552611}'; + is($json, $expected, 'ECDH_ES_A128CBC_HS256'); +} +### ECDH_ES_A128GCM +{ + my $token = "eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTEyOEdDTSIsImVwayI6eyJrdHkiOiJFQyIsIngiOiJPbDdqSWk4SDFpRTFrcnZRTmFQeGp5LXEtY3pQME40RVdPM1I3NTg0aEdVIiwieSI6Ik1kU2V1OVNudWtwOWxLZGU5clVuYmp4a3ozbV9kTWpqQXc5NFd3Q0xaa3MiLCJjcnYiOiJQLTI1NiJ9fQ..E4XwpWZ2kO-Vg0xb.lP5LWPlabtmzS-m2EPGhlPGgllLNhI5OF2nAbbV9tVvtCckKpt358IQNRk-W8-JNL9SsLdWmVUMplrw-GO-KA2qwxEeh_8-muYCw3qfdhVVhLnOF-kL4mW9a00Xls_6nIZponGrqpHCwRQM5aSr365kqTNpfOnXgJTKG2459nqv8n4oSfmwV2iRUBlXEgTO-1Tvrq9doDwZCCHj__JKvbuPfyRBp5T7d-QJio0XRF1TO4QY36GtKMXWR264lS7g-T1xxtA.vFevA9zsyOnNA5RZanKqHA"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>$Ecc256Private) } or warn $@; + my $expected = '{"exp":1392553211,"sub":"alice","nbf":1392552611,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"586dd129-a29f-49c8-9de7-454af1155e27","iat":1392552611}'; + is($json, $expected, 'ECDH_ES_A128GCM'); +} +### ECDH_ES_A192GCM +{ + my $token = "eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTE5MkdDTSIsImVwayI6eyJrdHkiOiJFQyIsIngiOiJQVHdUWWdjQ0s2aVBuNUQ4TmUwSGlERG16b0NpRWFpSnNIN0MycENFcHNjIiwieSI6IjdnVDJPVGstcTlFa2tqOE41OEd4LUo2X2NrcXRnWWVPMERyZ3E2SWFPWGMiLCJjcnYiOiJQLTI1NiJ9fQ..sK58aW_aYOIeXcd_.KCHYLUKgSpRSe01ACTS-C1dtc1vxSiqqw5GdWjTkdtdsrpG_GOLzDrPWv_W4C0GsI5yrfZNlsujAs6qCgeE9Ypk7Nh26pEAVFqYYHeGO8VIqB_KmA_Y00q6Ae0JrV9MhOx7Lk45iGZoVYHeTw8vXS_q8GIZMVPE8hiIwPZApCb11yAoupP6ZCCE7wDwGZUJebWagPssElcwe0bQDg-xhvDjCobGe-GxS-cSJD_pwATJDnwYnIkHhr8xQ5DG_A6hrKB1JJA.hYUguhKj7zVxpVAAO-mZ4Q"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>$Ecc256Private) } or warn $@; + my $expected = '{"exp":1392553211,"sub":"alice","nbf":1392552611,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"586dd129-a29f-49c8-9de7-454af1155e27","iat":1392552611}'; + is($json, $expected, 'ECDH_ES_A192GCM'); +} +### ECDH_ES_A256GCM +{ + my $token = "eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImVwayI6eyJrdHkiOiJFQyIsIngiOiJtRXhiTWVyTW14X28zZkdtQ3RNNEx3UlBOc0RsRzRNREw1NXdqYzd3cEw4IiwieSI6IkMtLXZ1VlR2OFhYUzlxT1ptX1pZcU54WG4tYkRXRkxDZUwxTTZRS2pJYlkiLCJjcnYiOiJQLTI1NiJ9fQ..SmI8J0ZwK1CXwamA.VnsYpxxR9-XbS7FAPSngPNkCslTBca2otiYzZVGbDrM4fJueODgMkRSkEKXzxeYRf2zU_0cwY1sUvgU00lou2SKwcoSgT8kON0sdoxxwn-atxyUoxISd75NW_WQdaAG2WysWweYMyB5eu7XuRDUwQ4iKCLmmtD2fdQ5w3RcNOxMIC_zyr3NwrQO7zarIbdcDg0iCgc7Szflbc1EYMadtiEmU_YN5veXOvJtASEOyjRbX-U9HyQnF-Z78dTf_j_gAe-TwjQ.H10mHRYClUt8j2LulRKAog"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>$Ecc256Private) } or warn $@; + my $expected = '{"exp":1392553211,"sub":"alice","nbf":1392552611,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"586dd129-a29f-49c8-9de7-454af1155e27","iat":1392552611}'; + is($json, $expected, 'ECDH_ES_A256GCM'); +} +### ECDH_ES_A128KW_A128GCM +{ + my $token = "eyJhbGciOiJFQ0RILUVTK0ExMjhLVyIsImVuYyI6IkExMjhHQ00iLCJlcGsiOnsia3R5IjoiRUMiLCJ4IjoiNnlzVWZVd09vVWxENUpGZG9qUHFXeFd3ZkJ3b2ttWmpOVmxJRFFrcG1PMCIsInkiOiJKZVpia19QazIybWowVFUwcG5uQjNVaUwySzJJcVl6Tk0xVVRPZS1KY3dZIiwiY3J2IjoiUC0yNTYifX0.e1n3YTorJJ-H7eWby-pfGWzVx0aDScCT.VQLnlbAD3N1O-k-S.mJzcAMoxUMQxXIHFGcVjuEVKw70lC6rNbcGqverZBkycPQ2EDgZCiqMgJenHuecvG_YqShi50uZYVyYS4TTrGh1Bj4jP6iFZ8Ksww3hW_jYzKQbp9CdbmOL1f0f25RKwUq61AraXGoJ1Lrs8IM96tvTjKTGpDkNMJ8xN4kVcRcrM5fjTIx973XKo2_nbuCpn-BlAhB6wzYuw_EFsqis8-8cssPENLuGA-n-xX66akqdhycfh5RiqrTPYUnk5ss1Fo_LWWA.l0-CNccSNLTgVdGW1CZr9w"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>$Ecc256Private) } or warn $@; + my $expected = '{"exp":1392553211,"sub":"alice","nbf":1392552611,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"586dd129-a29f-49c8-9de7-454af1155e27","iat":1392552611}'; + is($json, $expected, 'ECDH_ES_A128KW_A128GCM'); +} +### ECDH_ES_A192KW_A192GCM +{ + my $token = "eyJhbGciOiJFQ0RILUVTK0ExOTJLVyIsImVuYyI6IkExOTJHQ00iLCJlcGsiOnsia3R5IjoiRUMiLCJ4IjoiWWExQlYxSVl4RW9oVWNJclhDQU9PektlMFBPTXVCUElmMmRSNmtNZVN0cyIsInkiOiJTVXVqY3NsMHZmaUpuMXVfNFk1OU1NSjV1RkdjVVpFQlRXUHU1NEFSZ0VFIiwiY3J2IjoiUC0yNTYifX0.wpPrUGVTDsthaBTuToj5D51O-bbSJCBwmDq7lK4l8dE.23LmX0dUuB4bmjx8.At6v2XSn05ew5N_mW2q4nIcHmn3unnuJkceT-cADSfHS5TGHq5_dytb8OZRDvAA_6U__MDWONdpNAAucG_2UljX8LOfRkfDIncg-KcN_8UOyTNuCSwg3wHtPfDuVR4VPgyKysxGU0L6yIvXs8as8GzLQ4vA4YbCbMjsefQQLWjJbTELON5ASVj9cwTSTydO1N0xXDWjKiPXaiwHiBAnEE-ESeTvhqc1yfS6lel1PMuoZc0teV6XX21lZfFuVJtnKWQIcTQ.AoCKtceXULOU0y74O5qJFA"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>$Ecc256Private) } or warn $@; + my $expected = '{"exp":1392553211,"sub":"alice","nbf":1392552611,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"586dd129-a29f-49c8-9de7-454af1155e27","iat":1392552611}'; + is($json, $expected, 'ECDH_ES_A192KW_A192GCM'); +} +### ECDH_ES_A256KW_A256GCM +{ + my $token = "eyJhbGciOiJFQ0RILUVTK0EyNTZLVyIsImVuYyI6IkEyNTZHQ00iLCJlcGsiOnsia3R5IjoiRUMiLCJ4IjoiQU5UZy1LOFlBVXVPazBtUW1aTERQVWlPcVZFUFBrLVBmNmtSdG42Y0IycyIsInkiOiJsSmk3UExFRGU2WndxSjQ2alpyLUZtUHp5c3dGa3BkSVU3WlUzNHQ4RURzIiwiY3J2IjoiUC0yNTYifX0.Iqp3w3xo12wCqyNV_8wNk3m2tHKpBmv66XARscHeLtZS-2FslAbfDQ.UClH3759Eeo3V8xi.Y4UQpFk-MF5Xkec035WVmMI7O_eXw5V2gF3Ov4CnnV2cac6pul598NytO_rFI-hff4dOLwz2jgD_H6nQ_fL70STi0Wrsar2s7F8TMvolcaOhOfIbzX4O0vTdrNENiM9ug7044M-lvsOX8rK3Q3usfxSfOa4g9I_7r6b6SRMbjGqz3mtp8slMZhPZraBAxsxU97qfutBNA8ohCPGHasu7INHQnE_Cf0bZtE8mSpijq4AK3FGp91ekpoowH4627l7fBnupVg.hdEFZ6RBabaq7Xzb1SOaCg"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>$Ecc256Private) } or warn $@; + my $expected = '{"exp":1392553211,"sub":"alice","nbf":1392552611,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"586dd129-a29f-49c8-9de7-454af1155e27","iat":1392552611}'; + is($json, $expected, 'ECDH_ES_A256KW_A256GCM'); +} +### PBSE2_HS256_A128KW_A128CBC_HS256 +{ + my $token = "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwicDJjIjo4MTkyLCJwMnMiOiJiMFlFVmxMemtaNW9UUjBMIn0.dhPAhJ9kmaEbP-02VtEoPOF2QSEYM5085V6zYt1U1qIlVNRcHTGDgQ.4QAAq0dVQT41dQKDG7dhRA.H9MgJmesbU1ow6GCa0lEMwv8A_sHvgaWKkaMcdoj_z6O8LaMSgquxA-G85R_5hEILnHUnFllNJ48oJY7VmAJw0BQW73dMnn58u161S6Ftq7Mjxxq7bcksWvFTVtG5RsqqYSol5BZz5xm8Fcj-y5BMYMvrsCyQhYdeGEHkAvwzRdvZ8pGMsU2XPzl6GqxGjjuRh2vApAeNrj6MwKuD-k6AR0MH46EiNkVCmMkd2w8CNAXjJe9z97zky93xbxlOLozaC3NBRO2Q4bmdGdRg5y4Ew.xNqRi0ouQd7uo5UrPraedg"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>"top secret") } or warn $@; + my $expected = '{"exp":1392553211,"sub":"alice","nbf":1392552611,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"586dd129-a29f-49c8-9de7-454af1155e27","iat":1392552611}'; + is($json, $expected, 'PBSE2_HS256_A128KW_A128CBC_HS256'); +} +### PBSE2_HS256_A128KW_A256GCM +{ + my $token = "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMjU2R0NNIiwicDJjIjo4MTkyLCJwMnMiOiJqVVozY0NEX2hMZ3pQVHVfIn0.cgEZLTNOoGgDJXhRj0Ca0DL_HTY2xRKzVpoRnOf_Yuxm6IsQJgf0NA.7sAUk5_ryTMO_hLB.y7arc1aQP1--WUwlUsti4SiW6O2nrmGviTYznPjw9KD9Tu4E4QQO3RCU1uo59qNF3jJ5Mgku5OXV8bJHlouMouUfZbEb2cHgH9GLwY7hbCuYfGBIEyZw6qnHCgLGatO59akKaVDa8fqPo5--V_q0T5Z3xWm7UpK8RHaR8z3kuSBEXI1JH-dgj1EikG0yHSxVkFiInrlNLGzhI-cMTSD5xfLlmhmTzqbdpNp947AQ7pix2IvkQdvdgCo3bbSQVUsSJrLZSg.cO4fVMmdniwtEikHv55cqQ"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>"top secret") } or warn $@; + my $expected = '{"exp":1392553211,"sub":"alice","nbf":1392552611,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"586dd129-a29f-49c8-9de7-454af1155e27","iat":1392552611}'; + is($json, $expected, 'PBSE2_HS256_A128KW_A256GCM'); +} +### PBSE2_HS384_A192KW_A192CBC_HS384 +{ + my $token = "eyJhbGciOiJQQkVTMi1IUzM4NCtBMTkyS1ciLCJlbmMiOiJBMTkyQ0JDLUhTMzg0IiwicDJjIjo4MTkyLCJwMnMiOiIxZEdaODBpQTBqb3lGTzFqIn0.iElgf12HbQWt3enumKP_j3WDxGLfbwSePHYAbYEb_w3himk0swcdiTPo1Jm8MU7le7L_Z8rU2Uk.7LoW9-g7U8c3GNAYO3Z5Jw.guSjXuYN9deq6XIsbkbxAptU9Lp1jf9k11QdhsvjfUvaZRXKrWiE9vg3jEJRJnmF7lZq07cp2Ou8PztMg6R_ygT7gadmP_IYdgQwXD6HGQs__uzvFnqtjWALiwLWuL0V0INrKxBn3CivJ5Hg26nJwLACdVuO_k-fNTaphbox-nKefndS4UXaoe3hEuCzHFPgFivMlND4aZJb8pU8sQbGA29gx5U9qNBmWYOXwV2diYQ2q2SfUEbXoMV7uZyvfQ2juTcyqZBVnEfIYGf_8esALQ.QrgRr0TIlJDFkq2YWNXcoFoMpg4yMC6r"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>"top secret") } or warn $@; + my $expected = '{"exp":1392553211,"sub":"alice","nbf":1392552611,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"586dd129-a29f-49c8-9de7-454af1155e27","iat":1392552611}'; + is($json, $expected, 'PBSE2_HS384_A192KW_A192CBC_HS384'); +} +### PBSE2_HS512_A256KW_A256CBC_HS512 +{ + my $token = "eyJhbGciOiJQQkVTMi1IUzUxMitBMjU2S1ciLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwicDJjIjo4MTkyLCJwMnMiOiJCUlkxQ1M3VXNpaTZJNzhkIn0.ovjAL7yRnB_XdJbK8lAaUDRZ-CyVeio8f4pnqOt1FPj1PoQAdEX3S5x6DlzR8aqN_WR5LUwdqDSyUDYhSurnmq8VLfzd3AEe.YAjH6g_zekXJIlPN4Ooo5Q.tutaltxpeVyayXZ9pQovGXTWTf_GWWvtu25Jeg9jgoH0sUX9KCnL00A69e4GJR6EMxalmWsa45AItffbwjUBmwdyklC4ZbTgaovVRs-UwqsZFBO2fpEb7qLajjwra7o4OegzgXDD0jhrKrUusvRWGBvenvumb5euibUxmIfBUcVF1JbdfYxx7ztFeS-QKJpDkE00zyEkViq-QxfrMVl5p7LGmTz8hMrFL3LXLokypZSDgFBfsUzChJf3mlYzxiGaGUqhs7NksQJDoUYf6prPow.XwRVfVTTPogO74RnxZD_9Mse26fTSehna1pbWy4VHfY"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>"top secret") } or warn $@; + my $expected = '{"exp":1392553211,"sub":"alice","nbf":1392552611,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"586dd129-a29f-49c8-9de7-454af1155e27","iat":1392552611}'; + is($json, $expected, 'PBSE2_HS512_A256KW_A256CBC_HS512'); +} +### A128GCMKW_A128CBC_HS256 +{ + my $token = "eyJhbGciOiJBMTI4R0NNS1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiaXYiOiJ1SDVxVThlN2JVZXhGYWh3IiwidGFnIjoiamdxc2czdHoyUGo0QmhEWU1xTnBrdyJ9.peAzKiVO3_w2tAlSzRZdqqQpnUSpgPDHi_xgTd6VzP4.o8bhvYO_UTkrsxQmm__nIg.MSmgetpjXHWMs0TyuGgmWd-msfbQ7oVWC4WuCJcfAsbhLU9kLDLrd0naL5f_UkWBaM04bfcc31K4FRN20IiUxcHzLnMR-lY-HkvRFWYdur-kLWw1UXjIlPOb0nqCuyd2FRpxMdSfFnYr5Us9T45cF7DdK8p4iA7KqPToMHWBsvAcET_ycMIoERqJrBuiJzh-j7UtDzH6KtUfgD4tzZAm3iM6HWT2lq25Pqsu4qf19LYXxZaMIiFwFKboeexkJ5E0hc7P-wIeknzFJaZhkb5P4g.dTQAed1znLHX4cO-VDgxeA"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>$aes128Key) } or warn $@; + my $expected = '{"exp":1392553211,"sub":"alice","nbf":1392552611,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"586dd129-a29f-49c8-9de7-454af1155e27","iat":1392552611}'; + is($json, $expected, 'A128GCMKW_A128CBC_HS256'); +} +### A128GCMKW_A256CBC_HS512 +{ + my $token = "eyJhbGciOiJBMTI4R0NNS1ciLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwiaXYiOiI5bUwxR1YzUUZIWGtVbEdUIiwidGFnIjoiU0xrTDVpdmhncy1HNjRBTS01bTBxdyJ9.S3_MudWEzKWCp8RRxIG5p6H2YOtMDCkOXXKM9J8J4lMX5N2CcUqsKkDQ4TE1rG7gD5qYgHsb8AiQFLbhjgDeAA.WiOHBPlws9hImQr6bZ8h5Q.jN9UbuvhTiS6uJi1jc0TsvpheXqHs8vdJzKOUVgFmVHZ_OG4vSNRLx408vSoAgSeqsRmj8C8i9Yi2R6kpgtRXZ-Rw7EQEjZ65kg2uwZuve1ObqK-uBm3UzDmcT_Jh6myp9Df1m28ng8ojfrY_JUz6oE5yEcJdlm7H8ahipJyznWOjFigOqhaiXosjW0kbGGpYE-njD5OX22vR5k0RxHlMCDAH2ONR69kaWbLQvDg7y4yMFSxi3ILUFSVz4uXo6qlb8RVCqMUWzlGho-5Cy9OPA.XQ0UmHH5btv14_km6CIlIUwzOFj-rQUYyEzF9VY0r70"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>$aes128Key) } or warn $@; + my $expected = '{"exp":1392553211,"sub":"alice","nbf":1392552611,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"586dd129-a29f-49c8-9de7-454af1155e27","iat":1392552611}'; + is($json, $expected, 'A128GCMKW_A256CBC_HS512'); +} +### A192GCMKW_A192CBC_HS384 +{ + my $token = "eyJhbGciOiJBMTkyR0NNS1ciLCJlbmMiOiJBMTkyQ0JDLUhTMzg0IiwiaXYiOiJzRHRLdnRzZVk2UkFuU2twIiwidGFnIjoiZDFDS3dKWnlXSnlvcW5HTUFwbmR6dyJ9.2L9u7vV0P8bZddbkCKKe6_C5JTLf8wRZC8xzEe4gvmcGoF2K5AledhcqT6mIlaPx.1JY51r77jimrvKxts9EroQ.922BMD0HOscwZxn4pmYTRgV7oshegQ1dooU9njhonPcp46XbegdfsgeZAACVFpCc_CoY_XzOsM5trH1Z30QUDc7IGJmC0NKuPdK2KkrYQPXJAe6nuZMembGsyRkOHahtj7sew-ULZn9y0ztbntPqm5I9O716mv1Cu6_5_mBYu36c_VVd6jlzueUWun09yLDJLFuf5jRXDrqRrY4t6XIcqti8LF-QLowU_pa5DvRV_KzCtD_S8HvzJ217_TI9Y1qaApgvWr_BxDrfTXxO2xaZ2Q.0fnvCkg_ChWuf8F3KY8KUgbdIzifb_JT"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>$aes192Key) } or warn $@; + my $expected = '{"exp":1392553211,"sub":"alice","nbf":1392552611,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"586dd129-a29f-49c8-9de7-454af1155e27","iat":1392552611}'; + is($json, $expected, 'A192GCMKW_A192CBC_HS384'); +} +### A256GCMKW_A256CBC_HS512 +{ + my $token = "eyJhbGciOiJBMjU2R0NNS1ciLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIiwiaXYiOiJvUV9xbDNJUHNibEVPaDBXIiwidGFnIjoieTllMEFfY1hZMnNDZ24tamxsNl9TdyJ9.K5BxtxcV0simNM-69RvjZuNBjxaavDVnBzP7EFXSjbWZi3NjZFoTcFljcu2TuzR_F9zdjjBbohEgaf4kUMVZfg.881rEerOD33OLCHKdTWDjQ.LvrzsNicH2slBjwERYFu-Fr4Bus2lcLTdFazEpsHc_0QH4NJ2tGrJJjByli6OaFOwtdWONEu_3Ax8xvEXWHc0WMhYKxaVLZI1HQwE0NnWyqfF9mtOkUCCXn9ljvSGSDQY5VUcupVUT6WQxAkaNe6mJ6qkJOxE4pBpiMskO0luW5PkPexk2N3bJVz-GwzMp3xVT6wtFimThucZm2V71594NPCKIkA0HvtBkW0gW0M66pSTfQTHkU0Uvm7WfRvr6TXpiuKntJUe7RX5pXFXbfN2g.aW8OWGfHFI5zTGfFyKuqeLFT5o0tleSYbpCb7kAv1Bs"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>$aes256Key) } or warn $@; + my $expected = '{"exp":1392553211,"sub":"alice","nbf":1392552611,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"586dd129-a29f-49c8-9de7-454af1155e27","iat":1392552611}'; + is($json, $expected, 'A256GCMKW_A256CBC_HS512'); +} +### A128KW_A128CBC_HS256 +{ + my $token = "eyJhbGciOiJBMTI4S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.DPRoUHQ3Ac8duyD32nUNH3eNUKzUIMYgEdf5GwJ8rW4MYQdl2PCIHA.B1dR6t93aUPcFC1c1aUjeA.lHPKTK0ehgzq70_Ihdh-svI2icUa9usgqP8sF5j50fsQAGizITZpTTXKOKd9-GSEVmJo07551hq9xscZj4vXsDEx-z-akxg0nlL5fFE24km7l4T3LfAeG17gmrMcJuLP55mFUg-F98j9duV2UCyKJPXP6RwOQ5X17VNw29c4k-_AxYM0EjTv3Fww1o3AGuVa07PfpLWE-GdJeJF9RLgaP_6Pua_mdVJud77bYXOsVxsweVtKIaBeLswMUUSU6PoC5oYURP_ybW76GOCjmgXpjA.avU8f5LK_tbJOyKW6-fRnw"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>$aes128Key) } or warn $@; + my $expected = '{"exp":1392553211,"sub":"alice","nbf":1392552611,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"586dd129-a29f-49c8-9de7-454af1155e27","iat":1392552611}'; + is($json, $expected, 'A128KW_A128CBC_HS256'); +} +### A192KW_A192CBC_HS384 +{ + my $token = "eyJhbGciOiJBMTkyS1ciLCJlbmMiOiJBMTkyQ0JDLUhTMzg0In0.OLwgc7EaQdvsf54GfU69qH143C79H_eETvM_yGBgJzEB5367k9tbw6qW4TlQ56GMj__5QDJBvAg.BvYY_v4_dxxsK4M8A0T_TA.V0jBe7o-OahMkqGDgWW0Lxq1eTKPJYix7hjKmmqaKlhdVcnT0cdOU0ahdg82Ls-Vg_NaWKas8MhahHspz18Gx2abDSwLIKbU0jcaf0LxWZkEuMmFJs5dodq0ZqQeaEldDsHe9De_V_TQwPFkcMOPYqWhx2XEb13bmFTPtxNST18Cwm_j263Y_Ouz2YNyC4uZENZDWeOXfJLy7c8jt_ToOvXEVpXj7oZN7Ik1S9bGAenTcvUDORP-gdFdJ3stLe9FmKulOlb94Y-KvP_meyIZ7Q.XPPqS5YVJu2utJcAIRTUxlBHlECGRaM5"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>$aes192Key) } or warn $@; + my $expected = '{"exp":1392553211,"sub":"alice","nbf":1392552611,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"586dd129-a29f-49c8-9de7-454af1155e27","iat":1392552611}'; + is($json, $expected, 'A192KW_A192CBC_HS384'); +} +### A256KW_A256CBC_HS512 +{ + my $token = "eyJhbGciOiJBMjU2S1ciLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIn0.91z9VM1VLIA_qyTbqeInFoit7c4PWVuQ5mHcDyNsfofDGXS1qUDdPCWRdLC8ybvJflqHej7SCjEUMxuzOtPOUOgo-8rcdeHi.rsx7FYNTunzditC8XTMJXg.k88BLb0qs8g0UnKjSq9rs2PcrhpafEaUEX2kT-wMdmviZ9UEJrECoQY7MmJgCyQYO30hnnay2psJcr_yaDhV-NpctBZ793Xf9tztLZZndIjz5omV9HjcFgheQZj4g1tbNcRLwxod5uYz-OLrKORzeROEM-wkLgHVEqs90wN98NAiyhGyVMw7CXVX5NdU2KFUacbflkJc5AcaiAZYAts1t9bo2877XLYSO1qBoI5k5QKv6ijjM8I03Uyr3H0p0tdF6EB-cdYNcxq68GvA5CTkOw.DBtOuSJTFu5AAIdcgymUR-JflpwfcXJ2AnZU8LNB3UA"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>$aes256Key) } or warn $@; + my $expected = '{"exp":1392553211,"sub":"alice","nbf":1392552611,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"586dd129-a29f-49c8-9de7-454af1155e27","iat":1392552611}'; + is($json, $expected, 'A256KW_A256CBC_HS512'); +} +### DIR_A192CBC_HS384 +{ + my $token = "eyJhbGciOiJkaXIiLCJlbmMiOiJBMTkyQ0JDLUhTMzg0In0..fX42Nn8ABHClA0UfbpkX_g.ClZzxQIzg40GpTETaLejGNhCN0mqSM1BNCIU5NldeF-hGS7_u_5uFsJoWK8BLCoWRtQ3cWIeaHgOa5njCftEK1AoHvechgNCQgme-fuF3f2v5DOphU-tveYzN-uvrUthS0LIrAYrwQW0c0DKcJZ-9vQmC__EzesZgUHiDB8SnoEROPTvJcsBKI4zhFT7wOgqnFS7P7_BQZj_UnbJkzTAiE5MURBBpCYR-OS3zn--QftbdGVJ2CWmwH3HuDO9-IE2IQ5cKYHnzSwu1vyME_SpZA.qd8ZGKzmOzzPhFV-Po8KgJ5jZb5xUQtU"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>$aes384Key) } or warn $@; + my $expected = '{"exp":1392553372,"sub":"alice","nbf":1392552772,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"f81648e9-e9b3-4e37-a655-fcfacace0ef0","iat":1392552772}'; + is($json, $expected, 'DIR_A192CBC_HS384'); +} +### DIR_A256CBC_HS512 +{ + my $token = "eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIn0..ZD93XtD7TOa2WMbqSuaY9g.1J5BAuxNRMWaw43s7hR82gqLiaZOHBmfD3_B9k4I2VIDKzS9oEF_NS2o7UIBa6t_fWHU7vDm9lNAN4rqq7OvtCBHJpFk31dcruQHxwYKn5xNefG7YP-o6QtpyNioNWJpaSD5VRcRO5ufRrw2bu4_nOth00yJU5jjN3O3n9f-0ewrN2UXDJIbZM-NiSuEDEgOVHImQXoOtOQd0BuaDx6xTJydw_rW5-_wtiOH2k-3YGlibfOWNu51kApGarRsAhhqKIPetYf5Mgmpv1bkUo6HJw.nVpOmg3Sxri0rh6nQXaIx5X0fBtCt7Kscg6c66NugHY"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>$aes512Key) } or warn $@; + my $expected = '{"exp":1392553617,"sub":"alice","nbf":1392553017,"aud":["https:\/\/app-one.com","https:\/\/app-two.com"],"iss":"https:\/\/openid.net","jti":"029ea059-b8aa-44eb-a5ad-59458de678f8","iat":1392553017}'; + is($json, $expected, 'DIR_A256CBC_HS512'); +} + +### RS256 - BAD scalar key +{ + my $token = "eyJhbGciOiJSUzI1NiIsImN0eSI6InRleHRcL3BsYWluIn0.eyJoZWxsbyI6ICJ3b3JsZCJ9.NL_dfVpZkhNn4bZpCyMq5TmnXbT4yiyecuB6Kax_lV8Yq2dG8wLfea-T4UKnrjLOwxlbwLwuKzffWcnWv3LVAWfeBxhGTa0c4_0TX_wzLnsgLuU6s9M2GBkAIuSMHY6UTFumJlEeRBeiqZNrlqvmAzQ9ppJHfWWkW4stcgLCLMAZbTqvRSppC1SMxnvPXnZSWn_Fk_q3oGKWw6Nf0-j-aOhK0S0Lcr0PV69ZE4xBYM9PUS1MpMe2zF5J3Tqlc1VBcJ94fjDj1F7y8twmMT3H1PI9RozO-21R0SiXZ_a93fxhE_l_dj5drgOek7jUN9uBDjkXUwJPAyp9YPehrjyLdw"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>$rsaPub) }; + is($json, undef, 'RS256'); +} +### ES256 - BAD scalar key +{ + my $token = "eyJhbGciOiJFUzI1NiIsImN0eSI6InRleHRcL3BsYWluIn0.eyJoZWxsbyI6ICJ3b3JsZCJ9.EVnmDMlz-oi05AQzts-R3aqWvaBlwVZddWkmaaHyMx5Phb2NSLgyI0kccpgjjAyo1S5KCB3LIMPfmxCX_obMKA"; + my $json = eval { decode_jwt(token=>$token, decode_payload=>0, key=>$Ecc256Public_PEM) }; + is($json, undef, 'ES256'); +} + + +done_testing; \ No newline at end of file diff --git a/t/jwt_encode_decode.t b/t/jwt_encode_decode.t new file mode 100644 index 0000000..bb927f9 --- /dev/null +++ b/t/jwt_encode_decode.t @@ -0,0 +1,171 @@ +use strict; +use warnings; +use Test::More; +use utf8; + +use Crypt::JWT qw(encode_jwt decode_jwt); +use Crypt::PK::ECC; +use Crypt::PK::RSA; +use Crypt::Misc qw(encode_b64u); + +my $Ecc256Public = { + kty => "EC", + crv => "P-256", + x => encode_b64u(join '', map { chr($_) } (4, 114, 29, 223, 58, 3, 191, 170, 67, 128, 229, 33, 242, 178, 157, 150, 133, 25, 209, 139, 166, 69, 55, 26, 84, 48, 169, 165, 67, 232, 98, 9)), + y => encode_b64u(join '', map { chr($_) } (131, 116, 8, 14, 22, 150, 18, 75, 24, 181, 159, 78, 90, 51, 71, 159, 214, 186, 250, 47, 207, 246, 142, 127, 54, 183, 72, 72, 253, 21, 88, 53)), +}; +my $Ecc256Private = { + kty => "EC", + crv => "P-256", + x => encode_b64u(join '', map { chr($_) } (4, 114, 29, 223, 58, 3, 191, 170, 67, 128, 229, 33, 242, 178, 157, 150, 133, 25, 209, 139, 166, 69, 55, 26, 84, 48, 169, 165, 67, 232, 98, 9)), + y => encode_b64u(join '', map { chr($_) } (131, 116, 8, 14, 22, 150, 18, 75, 24, 181, 159, 78, 90, 51, 71, 159, 214, 186, 250, 47, 207, 246, 142, 127, 54, 183, 72, 72, 253, 21, 88, 53)), + d => encode_b64u(join '', map { chr($_) } (42, 148, 231, 48, 225, 196, 166, 201, 23, 190, 229, 199, 20, 39, 226, 70, 209, 148, 29, 70, 125, 14, 174, 66, 9, 198, 80, 251, 95, 107, 98, 206)), +}; +my $Ecc384Public = { + kty => "EC", + crv => "P-384", + x => encode_b64u(join '', map { chr($_) } (70, 151, 220, 179, 62, 0, 79, 232, 114, 64, 58, 75, 91, 209, 232, 128, 7, 137, 151, 42, 13, 148, 15, 133, 93, 215, 7, 3, 136, 124, 14, 101, 242, 207, 192, 69, 212, 145, 88, 59, 222, 33, 127, 46, 30, 218, 175, 79)), + y => encode_b64u(join '', map { chr($_) } (189, 202, 196, 30, 153, 53, 22, 122, 171, 4, 188, 42, 71, 2, 9, 193, 191, 17, 111, 180, 78, 6, 110, 153, 240, 147, 203, 45, 152, 236, 181, 156, 232, 223, 227, 148, 68, 148, 221, 176, 57, 149, 44, 203, 83, 85, 75, 55)), +}; +my $Ecc384Private = { + kty => "EC", + crv => "P-384", + x => encode_b64u(join '', map { chr($_) } (70, 151, 220, 179, 62, 0, 79, 232, 114, 64, 58, 75, 91, 209, 232, 128, 7, 137, 151, 42, 13, 148, 15, 133, 93, 215, 7, 3, 136, 124, 14, 101, 242, 207, 192, 69, 212, 145, 88, 59, 222, 33, 127, 46, 30, 218, 175, 79)), + y => encode_b64u(join '', map { chr($_) } (189, 202, 196, 30, 153, 53, 22, 122, 171, 4, 188, 42, 71, 2, 9, 193, 191, 17, 111, 180, 78, 6, 110, 153, 240, 147, 203, 45, 152, 236, 181, 156, 232, 223, 227, 148, 68, 148, 221, 176, 57, 149, 44, 203, 83, 85, 75, 55)), + d => encode_b64u(join '', map { chr($_) } (137, 199, 183, 105, 188, 90, 128, 82, 116, 47, 161, 100, 221, 97, 208, 64, 173, 247, 9, 42, 186, 189, 181, 110, 24, 225, 254, 136, 75, 156, 242, 209, 94, 218, 58, 14, 33, 190, 15, 82, 141, 238, 207, 214, 159, 140, 247, 139)), +}; +my $Ecc512Public = { + kty => "EC", + crv => "P-521", + x => encode_b64u(join '', map { chr($_) } (0, 248, 73, 203, 53, 184, 34, 69, 111, 217, 230, 255, 108, 212, 241, 229, 95, 239, 93, 131, 100, 37, 86, 152, 87, 98, 170, 43, 25, 35, 80, 137, 62, 112, 197, 113, 138, 116, 114, 55, 165, 128, 8, 139, 148, 237, 109, 121, 40, 205, 3, 61, 127, 28, 195, 58, 43, 228, 224, 228, 82, 224, 219, 148, 204, 96)), + y => encode_b64u(join '', map { chr($_) } (0, 60, 71, 97, 112, 106, 35, 121, 80, 182, 20, 167, 143, 8, 246, 108, 234, 160, 193, 10, 3, 148, 45, 11, 58, 177, 190, 172, 26, 178, 188, 240, 91, 25, 67, 79, 64, 241, 203, 65, 223, 218, 12, 227, 82, 178, 66, 160, 19, 194, 217, 172, 61, 250, 23, 78, 218, 130, 160, 105, 216, 208, 235, 124, 46, 32)), +}; +my $Ecc512Private = { + kty => "EC", + crv => "P-521", + x => encode_b64u(join '', map { chr($_) } (0, 248, 73, 203, 53, 184, 34, 69, 111, 217, 230, 255, 108, 212, 241, 229, 95, 239, 93, 131, 100, 37, 86, 152, 87, 98, 170, 43, 25, 35, 80, 137, 62, 112, 197, 113, 138, 116, 114, 55, 165, 128, 8, 139, 148, 237, 109, 121, 40, 205, 3, 61, 127, 28, 195, 58, 43, 228, 224, 228, 82, 224, 219, 148, 204, 96)), + y => encode_b64u(join '', map { chr($_) } (0, 60, 71, 97, 112, 106, 35, 121, 80, 182, 20, 167, 143, 8, 246, 108, 234, 160, 193, 10, 3, 148, 45, 11, 58, 177, 190, 172, 26, 178, 188, 240, 91, 25, 67, 79, 64, 241, 203, 65, 223, 218, 12, 227, 82, 178, 66, 160, 19, 194, 217, 172, 61, 250, 23, 78, 218, 130, 160, 105, 216, 208, 235, 124, 46, 32)), + d => encode_b64u(join '', map { chr($_) } (0, 222, 129, 9, 133, 207, 123, 116, 176, 83, 95, 169, 29, 121, 160, 137, 22, 21, 176, 59, 203, 129, 62, 111, 19, 78, 14, 174, 20, 211, 56, 160, 83, 42, 74, 219, 208, 39, 231, 33, 84, 114, 71, 106, 109, 161, 116, 243, 166, 146, 252, 231, 137, 228, 99, 149, 152, 123, 201, 157, 155, 131, 181, 106, 179, 112)), +}; + +my $rsaPub = <<'EOF'; +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqFZv0pea/jn5Mo4qEUmS +tuhlulso8n1inXbEotd/zTrQp9K0RK0hf7t0K4BjKVhaiqIam4tVVQvkmYeBeYr1 +MmnO/0N97dMBz/7fmvyv0hgHaBdQ5mR5u3LTlHo8tjRE7+GzZmGs6jMcyj7HbXob +DPQJZpqNy6JjliDVXxW8nWJDetxGBlqmTj1E1fr2RCsZLreDOPSDIedG1upz9Rra +ShsIDzeefOcKibcAaKeeVI3rkAU8/mOauLSXv37hlk0h6sStJb3qZQXyOUkVkjXI +khvNu/ve0v7LiLT4G/OxYGzpOQcCnimKdojzNP6GtVDaMPh+QkSJE32UCos9R3wI +2QIDAQAB +-----END PUBLIC KEY----- +EOF + +my $rsaPriv = <<'EOF'; +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCoVm/Sl5r+Ofky +jioRSZK26GW6WyjyfWKddsSi13/NOtCn0rRErSF/u3QrgGMpWFqKohqbi1VVC+SZ +h4F5ivUyac7/Q33t0wHP/t+a/K/SGAdoF1DmZHm7ctOUejy2NETv4bNmYazqMxzK +PsdtehsM9Almmo3LomOWINVfFbydYkN63EYGWqZOPUTV+vZEKxkut4M49IMh50bW +6nP1GtpKGwgPN5585wqJtwBop55UjeuQBTz+Y5q4tJe/fuGWTSHqxK0lveplBfI5 +SRWSNciSG827+97S/suItPgb87FgbOk5BwKeKYp2iPM0/oa1UNow+H5CRIkTfZQK +iz1HfAjZAgMBAAECggEBAJSYcG9KSpQdor8gxTurYWo6LQpazAN58SIkpCFG71a/ +k06BbYWt+oMhesOnumDV0F7OB4TEctf2/p0UA5PBuP3+bq3f6vqTp+buCn5qjd18 +PpWA93XYvahdDS3k1VDVRQEnj9BRamz2H3TcA/i8r8I4bU/4IDDgMN5mL1OXAX8+ +vt7j3YZdwsEBQk4MDrnfwQPadjDzFBxvNsDCv7DTtSNE2KY5u058DQcIimzH/ouQ +ip7qIYKGKxA2C3jIN399ngZY5QhTWGqArU/pq9WXtDkyTQ9OL23y6LVfgQSrpSKW +zjknlaShu4CcWR5r+4p+zxOf1s2sShVaB1t8Eer/xs0CgYEA0qaOkT174vRG3E/6 +7gU3lgOgoT6L3pVHuu7wfrIEoxycPa5/mZVG54SgvQUofGUYEGjR0lavUAjClw9t +OzcODHX8RAxkuDntAFntBxgRM+IzAy8QzeRl/cbhgVjBTAhBcxg+3VySv5GdxFyr +QaIo8Oy/PPI1L4EFKZHmicBd3tsCgYEAzJPqCDKqaJH9TAGfzt6b4aNt9fpirEcd +pAF1bCedFfQmUZM0LG3rMtOAIhjEXgADt5GB8ZNK3BQl8BJyMmKs57oKmbVcODER +CtPqjECXXsxH+az9nzxatPvcb7imFW8OlWslwr4IIRKdEjzEYs4syQJz7k2ktqOp +YI5/UfYnw1sCgYApNaZMaZ/T3YADV646ZFDkix8gjFDmoYOf4WCxGHhpxI4YTwvt +atOtNTgQ4nJyK4DSrP7nTEgNuzj+PmlbHUElVOueEGKf280utWj2a1HqOYVLSSjb +bqQ5SnARUuC11COhtYuO2K5oxb78jDiApY2m3FnpPWUEPxRYdo+IQVbb4wKBgCZ9 +JajJL3phDRDBtXlMNHOtNcDzjKDw+Eik5Zylj05UEumCEmzReVCkrhS8KCWvRwPA +Ynw6w/jH6aNTNRz5p6IpRFlK38DKqnQpDpW4iUISmPAGdekBh+dJA14ZlVWvAUVn +VUFgU1M1l0uZFzGnrJFc3sbU4Mpj3DgIVzfqYezFAoGBALEQD4oCaZfEv77H9c4S +U6xzPe8UcLgdukek5vifLCkT2+6eccTZZjgQRb1plsXbaPHQRJTZcnUmWp9+98gS +8c1vm2YFafgdkSk9Qd1oU2Fv1aOQy4VovOFzJ3CcR+2r7cbRfcpLGnintHtp9yek +02p+d5g4OChfFNDhDtnIqjvY +-----END PRIVATE KEY----- +EOF + +my @enclist = (qw/A128GCM A192GCM A256GCM A128CBC-HS256 A192CBC-HS384 A256CBC-HS512/); +my %jwealg = ( + 'A128KW' => '1234567890123456', #128 bits/16 bytes + 'A192KW' => '123456789012345678901234', #192 bits/24 bytes + 'A256KW' => '12345678901234567890123456789012', #256 bits/32 bytes + 'A128GCMKW' => '1234567890123456', #128 bits/16 bytes + 'A192GCMKW' => '123456789012345678901234', #192 bits/24 bytes + 'A256GCMKW' => '12345678901234567890123456789012', #256 bits/32 bytes + 'PBES2-HS256+A128KW' => 'any length 1', + 'PBES2-HS384+A192KW' => 'any length 12', + 'PBES2-HS512+A256KW' => 'any length 123', + 'RSA-OAEP' => [Crypt::PK::RSA->new(\$rsaPriv), Crypt::PK::RSA->new(\$rsaPub)], + 'RSA-OAEP-256' => [Crypt::PK::RSA->new(\$rsaPriv), Crypt::PK::RSA->new(\$rsaPub)], + 'RSA1_5' => [Crypt::PK::RSA->new(\$rsaPriv), Crypt::PK::RSA->new(\$rsaPub)], + 'ECDH-ES' => [Crypt::PK::ECC->new($Ecc256Private), Crypt::PK::ECC->new($Ecc256Public)], + 'ECDH-ES+A128KW' => [Crypt::PK::ECC->new($Ecc512Private), Crypt::PK::ECC->new($Ecc512Public)], + 'ECDH-ES+A192KW' => [Crypt::PK::ECC->new($Ecc384Private), Crypt::PK::ECC->new($Ecc384Public)], + 'ECDH-ES+A256KW' => [Crypt::PK::ECC->new($Ecc256Private), Crypt::PK::ECC->new($Ecc256Public)], +); +my %jwsalg = ( + 'HS256' => 'any length 1234567890123456', + 'HS384' => 'any length 123456789012345678901234', + 'HS512' => 'any length 12345678901234567890123456789012', + 'RS256' => [Crypt::PK::RSA->new(\$rsaPriv), Crypt::PK::RSA->new(\$rsaPub)], + 'RS384' => [Crypt::PK::RSA->new(\$rsaPriv), Crypt::PK::RSA->new(\$rsaPub)], + 'RS512' => [Crypt::PK::RSA->new(\$rsaPriv), Crypt::PK::RSA->new(\$rsaPub)], + 'PS256' => [Crypt::PK::RSA->new(\$rsaPriv), Crypt::PK::RSA->new(\$rsaPub)], + 'PS384' => [Crypt::PK::RSA->new(\$rsaPriv), Crypt::PK::RSA->new(\$rsaPub)], + 'PS512' => [Crypt::PK::RSA->new(\$rsaPriv), Crypt::PK::RSA->new(\$rsaPub)], + 'ES256' => [Crypt::PK::ECC->new($Ecc256Private), Crypt::PK::ECC->new($Ecc256Public)], + '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, alg=>$alg, allow_none=>1); + 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, alg=>$alg, enc=>$enc); + 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"); +} + +done_testing; \ No newline at end of file diff --git a/t/jwt_params.t b/t/jwt_params.t new file mode 100644 index 0000000..7801249 --- /dev/null +++ b/t/jwt_params.t @@ -0,0 +1,235 @@ +use strict; +use warnings; +use Test::More; + +use Crypt::JWT qw(encode_jwt decode_jwt); +use Crypt::PK::ECC; +use Crypt::PK::RSA; +use Crypt::Misc qw(encode_b64u); +use JSON::MaybeXS qw(encode_json); + +# key password is 'secret' +my $rsaPriv = <<'EOF'; +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-128-CBC,2823DCBA91F7DBA2ED920CAEE40F0BB4 + +KAADjca5SzbAbdz2cF567ZO9WjZz+lA1C40gsOBvHB6LjWU32YGW6Hz9a7pwUjOh +E/gGSFkKv6pTJgXfLs/l+pIDGSohhzChw7hkmN1IgVXqDQZw3koW5Yn7bg6xeJoI +JFwIIQhnft6BHG2o/5MzUTRwHpIxRuIaz2FnZtBNbVtQInHtP8LJIAVoyoO4c0ET +IQBDj7dwOAPdxOsrKCRkjI8IBMwWtKBq7XunkE15dZFFZrZOfIaXUqNYF9DlCHBk +eGV2lZoL99pOtJzHTBzv3rtyPYqCNotTNnui2Z0Jzcq8K97XAlzKhL7BFMw5TSUF +Tf9ECgumaRELXDdlUtEiZ7uACBXAW+qTUxOCrp+EeyfUBYPLuiy9KQvJd4C+8QIs +OIYekzfqZfhbhOdb0U7ZRN3KXfuNS70vKfoMyuW4UVx75QZt3CnJL8M6dn+eijjw +mEVCT/a8SLgTgMKtl2AzFiJK4WqvnUs9iOswlaAWCIpvrMQmxltoL34aim55EZKd +gDlEW5zCcjYe8A5d5abd4cX8vVrN57j2O3Dk9Dgyr4ZHPjBMF8b6LnWqBGrgFrbQ +LpjDZRNm4W7JuROL5VtSBEwP5VAMdl56UPlgGmM6K2MgAvkZ99ycffu0vsKOxd1T +5wpY2y5SBOyoex0XPa9woz0GOLjf9ydpVlVikPHk4XX2ts0+L5VttkQ7wO9GLUj0 +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'"); + + # 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=>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 = 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'"); +} + +done_testing; diff --git a/t/kw_aes.t b/t/kw_aes.t new file mode 100644 index 0000000..dd25d2e --- /dev/null +++ b/t/kw_aes.t @@ -0,0 +1,700 @@ +use strict; +use warnings; +use Test::More; +use Crypt::KeyWrap qw(aes_key_wrap aes_key_unwrap); +use Crypt::Misc qw(decode_b64u); + +my @tv = ( + { + C => "cbe70897e1bf88f157234415d69daf078cfa311eae362109", + D => 0, + I => 0, + K => "d95c908e74e5cb172fbb8bfd7fd3480d", + N => "AES", + P => "45fd2c9c2044984d002e525cea0c7855", + X => "AES:0:0:32:0032:0048", + }, + { + C => "f3f176f516be33146aa632a4308547170197f6294746a4d19e2a844aeb0213ad", + D => 0, + I => 0, + K => "8d7a8d486899fe16ba9df45fab6bd7fe", + N => "AES", + P => "8a9ee446b9fe93fc091b9a250961a7ad184bf9628070ec00", + X => "AES:0:0:32:0048:0064", + }, + { + C => "8ecfc167b2a2b6abf29e25e6f93ab01ad2f18518a3968d4efe64a8b562bb870fb7ab8b0c34b2fe6b", + D => 0, + I => 0, + K => "14483415b4cf128a61e2e2a475600a4f", + N => "AES", + P => "f81e36b0cea09672b54d2c38ed8d15352c72a6ca2ea21bd89099b0d2c883b928", + X => "AES:0:0:32:0064:0080", + }, + { + C => "1b56f43cf33fd209424fea01e0dcd8dd88c5cdd81f13e9476bc7912a32e617962f180f382e3b356d6bd957be68722e8f", + D => 0, + I => 0, + K => "6468354d3bb868f43a6dbc12444d4724", + N => "AES", + P => "a4bab14e6b830f26d91e3f4d142cbb063b9b218ed43e3b73bb74b3bd20000f43bb6b6cbf847ac2cf", + X => "AES:0:0:32:0080:0096", + }, + { + C => "b02c0144fb2a402af8db8681b0c98e00812ec408b6c12598bf60dfa33bb4578ca3989f67e0f0d1f3df186c870bd99f4bde411a1251a50b9a1c7e8b190626e0aae4ad4a115c38f0b5fff1309ab65017a00b38a3db7bebc409275333123a6fc9f0c1c2cf5b0a86cc8658b69e5f85d7c1e0647a6d2372d2879b33ad722baa4e15c89b9c26ada3a480fffbef3230d3c691bbf18477b1f0b3d64c4687b7f81734c340641a114ee47a5eb9e880ddff622133424b2f5d15cc4240cdfd97bbe25e638717f09b4597b44e132df4aa06ab476dc8e98a3b7df980ab5f532db9df1036a1bef4fdf84314132726353e7f524e6a0a2bdc0f755418d5840a0063fbce6f347885c385c5b7301b47088f69d1b31ff4ad55c5e92c72e230ae10933c2f606d792f2049431d5fa316ab5a8caf689fb49fd9478b91d59c35b7f41fa8dbc95d9b87fafe35a101f8066c5a35af6fdcc93c9edf34642050c6a686207f9f8f3bc7d54107da30a3673973cd7f6077da14868c0b77fc3ddd0882bba5923df9d35bb87895439abea7dff9a6dd44606245f830230879c82fa5e935b9bb8932fab68063299800c8221eccdb4dc27eebc17677e15f0471bbc97392c1ebba987f2b632ce40620e2a964a12875c74d3e5e9d89cf55e4bec3db9c66403f5b685296edcb8247ad94a262c7b4afd584b3e702e407f137ae9a56c87244400f404eb7f424a9322d9ec45ab1a83d1eced31e140e00", + D => 0, + I => 0, + K => "c6554c6e17036b09e0c4b764a772e227", + N => "AES", + P => "f2a7b464ed38c259aa9180b8b0b1b3fe7fe854088c71927339065907aab427b58fb192e7abb0427f1edafee621c8b5da132b9e70f48acf818572c172caa50bef468a043012318a94ac861e02b82eb762c9dfae4f3b1dee192fc534ee8f4291a5df982d8929c049036f6047a700e3cf9513fffe17aad829c668ae1d1588b6652296a48613cd27c58635972c144a330f5b2ab40189a572d56126752039275cc00ceffb497b984bf7e3eebf0f04c35f9b335e5e39d390161e17651c77ea798f139bdf6f6af4e77c28b4db0fac96c5a655358b67061486859ccd7ef75faa047692109fd75e667cf4b2335f7e83170334a15f7b5311e9224e99f9d667c02d63b4415b61f86e50997a074673fc69a45f4c874ff4d5bb9180bd1c69bd0faa0398104657558f0c34cda08fcf45eaf7912d69ec230718fa9b4020a0c45720ab83eda46b46784773d8c3bfe07f4727ef9ded8c5dbf8b0482172067cd15463ef74d79d2930ffdfe2ea8642f99f8fbfb9a4e51953b32ad64a0b3694c1e5672ab0a00e811eeead1b3a68d0f7f2021b6fdcb5cc42a8341e2aee90ed2bb1d0f299df97c1f5daa2b2c6638580a273540e96c93d27ca7eb5ac5a644e75c94a164343261a362e61e1f2c4a5c4498bb031f177cece0cea1583fd49092f5151b548b333c2d678e4df3ef34e8ad8311ea786f241f828d28bd208aec12a77b35a8b29542ab65cb398a96db", + X => "AES:0:0:32:1024:1040", + }, + { + C => "a3fa5ab1e13daaee02ef935e054729125195a0dff188ccc6", + D => 0, + I => 0, + K => "cc77273466f04df87c5da005dc25c8d24f7785eadc2fcd83", + N => "AES", + P => "daaebc7b7ff1e52796e6fd83552139cf", + X => "AES:0:0:48:0032:0048", + }, + { + C => "4d2454fa47a395ac05305bb1c125892c51aa11d55238b56ead0ab6256b686fe2", + D => 0, + I => 0, + K => "f87e4c4228031f46257ac932d934c19fbf272256877d8000", + N => "AES", + P => "086a44b36c80838a6af5e5c627e3ea6dac79ed57918df45f", + X => "AES:0:0:48:0048:0064", + }, + { + C => "70506d87e321d908feede827ef2dfe9274b90c50918ce9c6b156814e1802891ea9246ceaebfd6a03", + D => 0, + I => 0, + K => "991af4832a4677c24b6c55d284fb7451c68eb285f2f30a90", + N => "AES", + P => "93e27005847926fe518c794d4e30c34b03720f19b2bf377d6077b2b582a14f4f", + X => "AES:0:0:48:0064:0080", + }, + { + C => "66dd0c30315e9b6718f89ad861b502d0f159d01e20620243666236076756ee93a3c0cb2d068dc66d01c71d1e579a727a", + D => 0, + I => 0, + K => "b1753c2540659ad2e742eaa669d09dd21c1f80d15f214e3c", + N => "AES", + P => "a73a3b220299a1ee07fdc2c7a4eecce823e16b039878f729ff200e14037839fe9deb24150028ea09", + X => "AES:0:0:48:0080:0096", + }, + { + C => "6d9c31a29748f5ccb7df3f95978a6eb6c35190cda4a4772e0cc31b84ff973bbfa44619f157bd97835904a655675efa2bb9dd98da1e76d86ecfe0aa0fd5c2ebf2cd13ece9f738c2a48b7703e15e6bd5175862087aa544b7cf8c4775b52561d398afbaff71d2a75c59640e6629fe7528da663058555b0e6064778a7329c418dd921457fd69542e0d73b628ddd8bb9c2fc76b648c3b624929b26898528e0a1cb533744cf60d5cfb2a6292a216747300e6a5a303207d882427e03be9526c395c1cfbf6f70d857203817d4ee3746aaf6044af514dfe985d4a0c84e249f39a0aa6e6a97da1eaaaa6ed2a4f084da72e48ece3521e8f9fa107130f2f9bfd6518035814e42ad491eb7925cebf7edb861654e194c304202e5bbaefaca4179fee6f1e44af60d4b8e4604cd213bafe27fbb1c1cde2b5fc2addfb2b7e01e8b3dabf269bf8d35740b1fcefb0c0e6642b49cd536b3dd3d27ac9b89471507c9fdab0de7ed43200e3145ce670cda12fb4f00046129aaca1ce966976a3c35a61ecd9481d8d4a875638c3790ec25849755fa60f943acf4a6cfd62c7f77df43c73f699d10e147b89b1c285a66820db161ae13d9075e720d8dfa00324087636920f0325f17d3800a24bc6ee0089b8bb56887db051b743cf3a3ce5a689f9f59555e1585dd2fbffa7795e5eba0c518ac29d98738b4b8efdf11f4ed42ba2286a68e294b94e167e0becadc6675ed59a4e25500cc8", + D => 0, + I => 0, + K => "e4f1cdecd977dcad04d93caf433e48269ebe13db2f929232", + N => "AES", + P => "1a01853bf2bdffde1aaa86424292907275d547bfe693a3d49f9a582fcb8eb0903e17e55a27fd92f6d6534543fad4cefcdfacb6bce5912453ff4a63cf343810f2f5f8d5cb9580a64eb849bd6e20d9be0c404710379ffa644a7e3ba1004b172768277fa56314912c537079d1fca7c47c8d5be0704b5719485ad6a9bcdcdac9de316c797bcb13861fbebe5101f23c546e5dd358f9f448f23bf08f2c08798f6c4c989d5274ee9ac13a85b8b9621bd201ca2c6cef5f209c111b2e2c3945a3774aca8b6a65f826d3f6a7473f674397fa372ba9b4d46e59a6f6c1f32049bb8cad1ddd8d7c3a2e231b874eae34c0a51dc9d564a546e26ae4b5e809236c91af304b8523176bd4dda38ea646e30e784e2330c5c3d6f282986f0c0d7002903c8f624c9f8febbeddd5360c166cfb0dbae67470dfa9ff104174e15f87d4e33d3a43a29bea0e4b024891f8e28bc89a51c505749a65f1b8561763c67b30f79e27faa7a432405add88c1239862a8fce146740cc800504ce495150dab4c761b0258302579c54e711fec31ec960316010d54120b20d5f21c554c9c81952966a1f12ec61611400c0ad09ec2213e5bd56e3b69e73ac36c1d057356856efbd68ee5b2e11e3a7baaf2494d8e1b8dcdf2a092a1066aa668d435da5db94fb7e82878d4eaa6a2960648cbf37df7a93572167713adbbbb81977e32d02ef7cb8fd64ac4782e0084f7d90c2a0010", + X => "AES:0:0:48:1024:1040", + }, + { + C => "f1733778d27dfe1e57a8822d5f5ae62c2837612a93e21420", + D => 0, + I => 0, + K => "4f43cf947aadc06b375ae3cde537e2ac2cd4b1b499406254cd286d459249e929", + N => "AES", + P => "eee8798323b347eb2a06d1e2ae31a7a8", + X => "AES:0:0:64:0032:0048", + }, + { + C => "dbf7b7d96d3f620df3fb2506b080456f1ef9279d9b9dcdc2c99266faa068af8d", + D => 0, + I => 0, + K => "669514545e5c321d6404d69732a3832dc322f96af8ca46ec01e0607e09bf887e", + N => "AES", + P => "5f89e8643b2b026dfb680f0f0dd45d5c24f6805d874b1931", + X => "AES:0:0:64:0048:0064", + }, + { + C => "8cd08b63bb1efd7e2aedc28103c42712aa0c0bf00263cc52aef5d2e333822b6e0c9c715f7353d950", + D => 0, + I => 0, + K => "05b97f0fb02539f5841b2d58bff4f1d57fb2fa62821f537c1dc0d921a9475751", + N => "AES", + P => "2afe143fc9114897ce6129f2a81685130914b1927d452fe2b96c788da94f77ba", + X => "AES:0:0:64:0064:0080", + }, + { + C => "1d056a9cd9c0ac550f1d060e2bb764b0a3e09c3e8ac35d8aa91601125035a19f7bc503cb2794cc68f16b2afc1f325c31", + D => 0, + I => 0, + K => "6e1aeec3b7cc4d7b4d038038b32f49b464f44ae3a787e53ae69c97d0b61bfd13", + N => "AES", + P => "6f8982b37a40559448223c8e31160cb9e0280670808c9b3887323b1c68da20966ee12d05832debed", + X => "AES:0:0:64:0080:0096", + }, + { + C => "c51316caa1a0d5cb5530a28974af76370d7ef6851c04559a11dc584f0796969b1934ce398d56b0e97079b78654a8a5d03ad5eb5d5704bc0c3618c2d422ba8e5ca26e5d973350815498419f53b55f83d5447ec373e1ca8c7415f3c10e419951893aeeb840e8ae4b178a5c911d55710352c3dcc4cb7c5c846cd9e093579abdb1ec83d7c86a5e9c8573deb3f9395c612d562b377363c70bff8663b0fdb574ffb2d32cdda1e1a06af8d2b56b52ce2c2a8dc9a0ab9b16f7553d59bd4b563881e96059e735807b9a668abb8c4bfc1f0f61af25631b6552619394f03ee41dd63db72cf38174b504994395f2c15cf0654071bd3d8a90d8c58c2063fd51986655a01f1744a13f4c2ae7d4af70bb896aae23db0274d580bd358dd034608d26cf798c005282189d9c07b391b9d1a961636f18a7ddb72fabdfb14d871a04ee09bf41c3588fcce16b1f812f864e924a19d415a64b0c29e7e196805abc909db5f6f6b782b5604cb2eb2c8295600670ecd45d1261d0055fe2f468da2240ea27c8648e63dace0ba867115cc72796c4d850e8cc8a2f028e45fe3a49a31a4283638fb5b7d12bb4fa6150986c77fc79fa534f4e3cba65de257dafe8cde538c49f513b34bd20bbc2b77557ef275748dd9cf39b7b1744e9b8751a3578be9e1fe15d6e23909305a69d257cc95be633dc10279d9f4c7cb582003f58749980733f5281b6a3ac01a51f49a201a64087c74b127cc9", + D => 0, + I => 0, + K => "afc8b67cc7582833d4f42e0a74e77a8c51781929ac64cf5863aa6faeff2cf2e8", + N => "AES", + P => "3a1f50258a5c39c216183f9543402f49060ad4d4f6dc552a763f0e81527d9d19286aa23f68c5e3046e29d3c8888eceb5f51fd54e7586597d9b4e452885f34bd4d3ed002f11201ed3c7d825c9959c9e0f61302f43ec68c073a67d2850ba1ab96e520940b409489d5a0417555e28a51bb7330947944eea9b4bf03e8db68269e3968e1f7b6085265bbe0fb844e5a39575e51ba52fb8d9cfc8cc64192ccb8d2981495e10aaaa7a12fa205d4efa4601fa129c9f51d8a70acd95992f4006692b60456d067ed8dcae635f9bd123aedd7286a5d4d3fbebad838f7dfb4863ee538d82ac4a0e69ca215e37e0880a1cf35ed9fa2b20fc9b85951ab8ceeab3aaa9345751a7285b8c7b4ce3e820df79583e8822f48c9afde5d53542a9f4c13582028675dc792539a52fff5341a1bcae2bc8d519f625e76f379de922172d2019489995e510cf16e9e9639264e3a6eb0aa5dfcb2f8ce2cdd9ac1ddb0e6e42d971aad170ead96c780e7a75ab064dacc348144ef66fbc72c2326faa20344d7387356e0eb2bbabf1d9a50d69c617fd8d794e687c7c240ed8a08a5be5d2c25f5f3bfd6ed16820f61f6a48e103ef342fdd26e1d433cb3a2aed7098d84e344a1fc591e7955fe39e14b08d4f8b3f0f3003c945f51988082d00b7947fa6da377650f2ec19e4d019ea98671f5c2936762cd01b2d1e782c44f6743d28a2275e6a42c4e1b309be9895160ef9ec", + X => "AES:0:0:64:1024:1040", + }, + { + C => "58b7cf4889a4dc3ca4f8fa63bd3d6754f9e616541db63dc1", + D => 0, + I => 1, + K => "80d7ce2475c6797556ff339d99a0912a", + N => "AES", + P => "d0f105beb850d66253b7c668d17c51c5", + X => "AES:0:1:32:0032:0048", + }, + { + C => "91b97497a947a84d49e69e4689556cae7b0d67ed039d7adf4615af58c001d13f", + D => 0, + I => 1, + K => "13f56cc2642f8db2964f257b1a2c3054", + N => "AES", + P => "4686ddf7b8b489831692401d7f54e6509502f519060f8dd7", + X => "AES:0:1:32:0048:0064", + }, + { + C => "f3cf8f0747e283bc33b115fe58abf828e90d3171e74f2b23f059f0fc5710ed71435202af9d3f77d9", + D => 0, + I => 1, + K => "fec8932bddb6c45e91195363616c6deb", + N => "AES", + P => "648944481258d2b2b509feec6e4526182c1bf6d7b9206412702610df204557ba", + X => "AES:0:1:32:0064:0080", + }, + { + C => "9a79584225b518596b8c47db50399a14ff88d55d61aa586b0ff88ea9c00ed0e2629a81114787c076bf1ae9b0eb179ef6", + D => 0, + I => 1, + K => "e3aa3e958382a4b396cf038727f22b87", + N => "AES", + P => "b0b6f3342d2a11243f2e9b72551a68469a33dd0f9fed0d71ca3ab08df5ee20fadbafd65163d0028e", + X => "AES:0:1:32:0080:0096", + }, + { + C => "fce2805f034ad6f6e91e0286577bd5ed1ba579e08c328d7de9e9013d2ddc5109ce3b06bdb7e28d992d8adace8e0a4d5bbf8a1f664bd78262c6d9b78a133a6d384709387c1ea9419b16910e219ee489d74f8cc1c733e27902e2f3f68b6f36a269b957253b042ef00fba2f342ad6a50684646291c212b27e4ede2c75ffa01a147c023d0731e7fe406988f73634947bf35080c8caa8f7c2da5e5276f6434f9e693ff646b2ea16c130097ef0745374568ec805cd04eb6e56bbfe50e1225ced3869ae2cd9bad58818f5afe65850d0b7e7b39d8365d537ac2f48c165c6daae86940665feaeae99ae944cb0ae3221f58a59a613a89395283c531ff1509b0d57223515a0ea2a78bebc4cf8a249a4850d93a87b5fa1b695db3251d2d12f569eee7e9900f629dc43878f921db811ab6319b09d26407b8df0bf981349da746ab2f712350220908d0b2d4ee62c913ddb2098af1fa1f40074a883d0bafbce87fad47c3e0e37e554af29857d79251d91ee6f5b9ffac12f543a248b757f3b99edd1030986dbed8fcec1c7131dc09a324d77e92be7e72989c27612125f0c4a1ab878c387cc7806eac130172b16c55baf43bce18190b830e5d37f9cdf7a6bfa31555f9eed1675fcaacec37341aa073949d9d69a97279646b0988d9bd3ff99162adb4056260a1724a72619e4edcd9182410aedebb31be4b61d0d02005d465f7c865bac306dd0d6f1fc14d9cb320c3615e1", + D => 0, + I => 1, + K => "a468dae7466a4605a18d957f88b29d91", + N => "AES", + P => "2301af19cf4b8e6b141d95e258ef2559226e123e75af343617af5d2a6e519e530fad4c62e02b65fe1881f97f2dd32e92bc63556324f62e322c2b19230c803d0cf9689e72daa40d8bc19b20a5986488e06047828d7ef8f94d2832b864abb20e18d661eb0645d22227321faba6bbd3ab2a7c4989613a558a5ed1a4ec70c60448a059d4e7e1149096e694dd22614e548b5da7039785530ffbfbadf650efd47f44cc57dc2af130122fe49fdf2a5ebd13021c4b7e05ea8d8bce229c2ab60b6414b8ac4f553a4662dacb256022bad4a8f72e51a5fe3e1b2de66df51b9e6e6b1b4338edaae992bf162c63c6db78ae2d892db24f812857402660002f48ef123d43063e50c602f2ff91caf4d13d7f10a4e92bb9d9e138a553869a8282f5914503ababa31bb41011f2e7dd9ce332400c4c16913edb13c92b4133204f3b4d7b16c507ec0c2559121ec1da668e485573decaa5014e227084af6eb75ada9b30d33e9420316929751cc2b2d23ce4a93ae59991dff9ac03474e1d74f89c70219e2038da1a67290633fde2f1c0fda31b86d040fc0659b2130ad4f1577a6a1a56f83a7b0eced5c2a362fa78a6875330cd0f92fbb0f6206db556454e9d6d7b89630c174747a83982cdc35dfb292379f50011ef1550d36d37c1a7967f7aa9dfc017f2b46780ac06b0cdc225a760fa99a6e6878ec894b700bd6e9c7b7aa02b4ef28dc888b4115d5fc34f", + X => "AES:0:1:32:1024:1040", + }, + { + C => "efa5fb8f79d28a665c21ff94af6532db58903055c9f35028", + D => 0, + I => 1, + K => "602c451a52920a8b92ceda05db1238f432ea1f763535d949", + N => "AES", + P => "aa9c354b11848092201f29117be6f7a6", + X => "AES:0:1:48:0032:0048", + }, + { + C => "8feb6db6b11c31ebb68a5b98267a33e87e02a21ba7459ee6c77de6b989d9fa74", + D => 0, + I => 1, + K => "b0a54fd8b064b68cc452d9c87d4e5b021e6749ccdff6ca3a", + N => "AES", + P => "009c7c1006ee154e2e8e5f5adcf72cd22664118b856e7fc7", + X => "AES:0:1:48:0048:0064", + }, + { + C => "3cc1e7a52c3b7112782915ca24084e6e391e4c86482608961733caf462952fcb49f2171251d29b2c", + D => 0, + I => 1, + K => "7a1eaf2c21f6e92bbdb89fbb2cd0283ffff3e2ada6b862ce", + N => "AES", + P => "124bd817c0849c9b77a84178086e90a381db21a62fc43b313f4023792691e70a", + X => "AES:0:1:48:0064:0080", + }, + { + C => "294294a20dcfd4f8b20b5105aedec366b776510bb30aae52bc9a00359b634248caaa4841766dd992ad477af54c4e3102", + D => 0, + I => 1, + K => "d2822cb9185ab6bafe423a7d52f66e0a25b7439d4755c0fc", + N => "AES", + P => "ef7c4998b8b4b2d75f3928c86ad3bd02704ae1255b7fa00b03356a07091d4b0af48d337d4b6bbf0b", + X => "AES:0:1:48:0080:0096", + }, + { + C => "9948283f7b4085e2a234d013d4a6f36ec9a90445f64d29c093684e7c087c2669b44e2f6ccbebc28fbba0d57912b5e5876c27706a95671a5e15e373d69d834bed55ab835eb77c22363598ff2cd9ca8b5d8c53cb4b99b70f703ded71220b7dabc0dc4bc189a70f9e952c8eb58f631e21ac4e69fc36f5ca25c413a11f27177f94a3faf4746aac1185da3c44e8e1925d7b70fe0b250e379fa062327c52b1b828973cf7f80b45cc7d7297a8430245f44ca2f4d8a9a2a51d7d490e0864bfbf62c4661f56ba5264e702e7d7a21684bcae33e75a77e8b72bed1f0179465d76c5e9d69f9a909063455b196f395936115b72979f940ef3cce45bb2a6158368a88e11c73b1d6188fe0038a0e8665d48670b29f85a7c338ea28aaa06c1e6b7ec422bb5ccda0d90f2fbb8c21288088adf6ceabec8910f19e1b4b8855e504e23b2b03c5bb579e6a0e62396b95a28a5ad914db55c554fd19ba24bdb0d32afa82ae45723dd894bb67ec4973751e536339568bbebad2f7041186cc0ca23a406393440e9f8d499e3ff8208978cbdc55e741606aa6753dc474aa57f86d8cc06e8369401797b189abdbc5c51e23a15f1dc67323643a7b189e7c2f0ce8be3faabe3eeb23eb7720b99ffcc81f064d2fd699349b9f9a5028a956c656fc13772e52c9cd0d46517fb5f3db40f013a8460b83257250bfe7dd0fb5e460586ed4d7cac617b874948daa7f0e72e909367228a4589ac17", + D => 0, + I => 1, + K => "52a342bb23fd707a7fc37617e8f85aedc2db03ee11cdc126", + N => "AES", + P => "4b1b3189e39048e28a8b19b4aa82bd3f7f5f8c228991ffaede0e044dc8e2b0085e9f646b592b8f0fda3a188a050f4f69af74f17e37e95a3fbedbfdf98ca248a60ca014948087b0eebb3545322181b41b4add568bc0b410d33270c3e0002f6ffa6ab43c74968094cd35bb20babc3fe7c95e62e783faea8aa5c442fdd5e71b2121b329ad13be0da6f15e16558023ded7548494c679b4e3ee3978de709d85dbf5a1b18c729db83852c2e29e99a525c5e33367611200febdc77d95409fb2cdb82be4a77fe6cdaafe0a0f209aa6b494395d8dfeb5b7a7499ef22e03b8c933bf97e24775166e8cb27e1df37991a0153f252fe82c040a59c7a750c501a55d67da82147fc19036a6ac3c6ee3ed5931f800efe77e6c1d2ffe031029308e28c3d330b68e72d433baceb9b996d679070575cb851c2aadae8f51a415883f5c195c876ac16a27b658afbb86330e5967b137c9635c88fe803c953d49203d1f1e36e64ab44e7a1af76405dc3621dbd8fc11c58e94c7265fe1184a180362c50a5f2b8dc7543e260f4faac265df46ff4e9cc10abeb5396cb0eb50452ddcd848c1692ac0981ef31321bfa7e8e01c551bc39371fa2fa9326394b7d170b96628a52a3458706c453cce5aac379b8acab21842fe2af5518be6bc7e8fcb7a64e684b732422e49e703b3784ad5f73d4dadf1a3626a831b9b161082b815daae814cbb48f1ce79aeef4939c1f8", + X => "AES:0:1:48:1024:1040", + }, + { + C => "46e5fa910879cd65c4d5f058612bf72ba5d51441024606e4", + D => 0, + I => 1, + K => "542cb39fc4238ddfe95f618c5b30a3fb188bd08c852cc979524c2451821f815c", + N => "AES", + P => "7c2b3a8e89bdaffba69367117ead7990", + X => "AES:0:1:64:0032:0048", + }, + { + C => "a261c68c7a7469c9b3a01c6794e865a254e5c3e7e57d5056955b75dc60a06415", + D => 0, + I => 1, + K => "31a93c13a21f9a6163eac9ae15da8877a4b09edcf50d04c48516f1971cc88d04", + N => "AES", + P => "81b0f00ebcd04b3a6e88e7be76357c06a4cc391602a63d5c", + X => "AES:0:1:64:0048:0064", + }, + { + C => "d8690b3327acda2d3029bd9a697074887476abfff5fae69e2f676aebcc5f47c39dca89fddb498044", + D => 0, + I => 1, + K => "aa595905d8006352490215d5f63ae6395fb759e1029d124099111aed6cf01e55", + N => "AES", + P => "c29717ff201f7e0af18b7e0620a9e8679e8a759effb5ffad2300897344072238", + X => "AES:0:1:64:0064:0080", + }, + { + C => "01677010a5d047cf0a34c4f5a7ccc42126e4f7e6f47928f309857de9733d7832e85c315e4490d65c8cbc950673d06a9a", + D => 0, + I => 1, + K => "48c4119e6e34b28ec9992246821cd6ab2d3787d1c3f005e500014892895a21ef", + N => "AES", + P => "16dfa3e4d97cf599a13b8fdf9c34538d0ea8a12928dbe63044b805d68be3529d6291e4eb2251a0f5", + X => "AES:0:1:64:0080:0096", + }, + { + C => "de8a69620e4d2eefb8287ba5fcb745630c24d65881eecb36daf1826263990bc1eba546f9f6cf1bf6ef8b73831c38fbd1e546e70c8338f5707b434ece94253a4cdeb483a5c8c533239a44bcc0d7c0e2c7a0b18027bc4f27a09f59367beb44688f95816e1bb296ae65f17578fc66939bee0e023521578e2f5ecf84198750643fd0631b5242ab47d01f5ede07212b307501722dbb2a12cc3c96c8253d1d1663ed259a45ec9bdb5c9dc261c05c25e3f06d08160a82433ab1370a3abd9a72047bbc2cc5ec3695bdef2bbf98f0b9e63bcd3e9dce12c31ece8b32516b4390e8246073b97fe6b7c34be4ac324f997074abade4f2456df63c13b50f89de6754ef543d4d11cb76e7eb6ae5c6429920d78d00b32c1839e4dfdea17e770b06e820343afd6dd8b899a5d802e4309a74ca7d7d6010d813f37e30bb3fac9c6ab985b0bc9825880d848b313714c895210c4d0107d9748bf04b599a58840b75041cb6e766fdb5f1bf1e530c31d12a4d4c5b6dab3fc26e9719381451573e791ce84da1ffc85536f0239436d0d481a9eaf27bec7d923c7f658037014d443dc8102f53bd11035d50b89c0792fa84b83f00bd87308a0fdec737c60e25768052cf4313e1fa837ebefb3ff666aa8c4bca9a9b38b911f1e364e5563689f973bcebf011d8de2d261fa419f05ed5377ab17a2ddc389fdeba85c93a3cc94af8d9de82add84030f192399049052529b8e58dabcede20", + D => 0, + I => 1, + K => "26e1ecc0c4d1ca3d757653fb2364dfe0c06b0e9e31b82cff7be190fe01627208", + N => "AES", + P => "8d2ed7a04a33b20047cf7b54927d7347d6b36823c14727e8cb12d305e1c1dc80bd42c3eadd9f6f094d6a3997eda34f9af4c8623d82d367b483886167cb4aee3e4c72854121fff6981a6516b949d21b2c944d60f133eefe3a2e53266f5bdbbb13cd92c42e7c6bd464b8f8cbfaebf23d9c7ef6c14b4041d76ae15567c68d42f732d6e87bbbc3f502e62ed512a7ebf95f57258d499fcfd87cf9ec9e9ff58ea545c3e7d241902994e666fbf1765a1633c627c29318daafb987cb57d63fde50c45b5d54c15f8da9c8baa3a2120b48667697442f8639999d2f4ea44671c06fee32828595d82bebf31da2f84aba77ed60b787a6096b53b08f9365f87a6b3a9d265a11d3cff7248e59bafdb996fb6ed64520ffc25bece8dae7e379ec3e7645ffdbde2489d09b927bdbe7da76d6b0e6b3d5b8901d0e4d534e14950382bf3e9a035143b88ad393ce2c3b0f9a668c11d40ce57651814872f0faee8ba5bc0e78f9c308452b8501ad5aef7ef9b4068837141d24a346a71a14f00063b7a3711b782d130a4cdf276134230dab1e91846602a352134a8c52be31e0da6b37b1a540f5474d4727aa99571696b092a933963f3317e302ac4a0d970d64ba3848f7eff0a082c4a6cbedb1282cbfbd46ca9d2c25f48a359fc639e675dc51973491e0a38c2150a8caf6bb9c044184a3dbbf58e89692d7e57a5afa31d65461fd93af8043647bd16a6a6b5936", + X => "AES:0:1:64:1024:1040", + }, + { + C => "2ec966209352c959278dbfcefaf51c96", + D => 1, + I => 0, + K => "ab978bb18796ccbe45eaabefd6a13948", + N => "AES", + P => "1e", + X => "AES:1:0:32:0002:0032", + }, + { + C => "e039ce098d8aeaf53ada5b8b634dace7", + D => 1, + I => 0, + K => "160fd350da87a5b5ec47af47a31dd39c", + N => "AES", + P => "b46952d9", + X => "AES:1:0:32:0008:0032", + }, + { + C => "3ed08c1d1f5022ea0631ac70f29d5d5063aba7fde653d621", + D => 1, + I => 0, + K => "87e3893fa12f467e8e9a4b5ad9d98efa", + N => "AES", + P => "0fba5313c5181a46e3", + X => "AES:1:0:32:0018:0048", + }, + { + C => "c7300d659ddb133f6cdaa600f8a0d0c1547d12b13fccf17a", + D => 1, + I => 0, + K => "7fc7a84392968735201b90eea55f57a3", + N => "AES", + P => "3addc5d1251aa009947f97cd", + X => "AES:1:0:32:0024:0048", + }, + { + C => "d3226f55b4b4bb4644f5752aead6505b4fd3ca6e112fb2a4b0e97133ffba7fc2dcdaf6fb68966c1582623180218bee1b768488cc0754d3a7d89367273d9e355ede53e814202666b849c92b5af0f5b0fb816cfcaf3680d8c32a9c17f62224e0367a12f177b46168b282fd4eb1226985ad", + D => 1, + I => 0, + K => "92b6cc0b96b37c9830b37f94232c1c99", + N => "AES", + P => "d86a3ecd24b29196dee5de017b5ac4d7e9a15957953595035b46acf22d462730153f8b52befdcd6f5254f940bf178e3379cb460c5a5748c86cb34d963154dce269255fd95f97f01ed01bbea46834d91b0552d1537d73865e8d161f02b3ac0835fe02a31b73", + X => "AES:1:0:32:0202:0224", + }, + { + C => "6db22bb35bd420e933a9acd713a237df", + D => 1, + I => 0, + K => "954aaa6920fd1a63f8fbfbe3b456d6d7dc97378c71b5bab3", + N => "AES", + P => "29", + X => "AES:1:0:48:0002:0032", + }, + { + C => "2044d516bf94244b59c0eb34eb47ae9a", + D => 1, + I => 0, + K => "aca929cc3fe259c9c9383198424c4a52f90a07ecac6ac7bc", + N => "AES", + P => "8cf43d18", + X => "AES:1:0:48:0008:0032", + }, + { + C => "01edb87ede6a4f66f7c8e3f66be199df676ac3868d0c9297", + D => 1, + I => 0, + K => "00b15609bda6bddad33128aa259044d61b0906f27e6df66d", + N => "AES", + P => "f0095af008a5433bc7", + X => "AES:1:0:48:0018:0048", + }, + { + C => "d3cdc2a5bd9c95f30ea571b6da2ffd726bc2e3e2b963228c", + D => 1, + I => 0, + K => "c4e0a649c6cc8afd9b5bafd373da0e54012858c77519bd9e", + N => "AES", + P => "7e1414138e3701e8bbd70524", + X => "AES:1:0:48:0024:0048", + }, + { + C => "8d138bc57b2e2b19bef1c95a91403bb791a3f2df47a5f2d45bc96aeebff9bb5d0d80be06c9b9cd7bf2c390f457ebaf8ab594177c8a696af28fb0274639c78ee5fbf74e249fffe202056c218d391e7c64034a837072986f82b110e7963a0d7f7a35e62d97b3b524b00caf723a581d497a", + D => 1, + I => 0, + K => "2c4d89e8fd17ecb20bbda47071ec798bf638a570e19c6e79", + N => "AES", + P => "4a4301be175d9d3f3a1ffa904a645e42b92bfcffe12a0260b59e4906ee8be455423647a55b953f8b8879fc5ece14e0e36487b327dabe1f26f6247205015573355bb0fc8ffabdfb3352bf6ed7f21b62c04d4ea4ffb3e35924826c64fc0ab2f43519d6d5386a", + X => "AES:1:0:48:0202:0224", + }, + { + C => "ce1f839cdaf7a85f3a65218a4e677fe9", + D => 1, + I => 0, + K => "3fae5121c5ce495b012bf13fd6c5242c539b31e637aecc56b75957d1d11604d3", + N => "AES", + P => "43", + X => "AES:1:0:64:0002:0032", + }, + { + C => "0a6d5b0d980d4e3c64263d611e89326b", + D => 1, + I => 0, + K => "6f7de62109e5ced236c9aa22141569787b6637048598345655b7bea7a0b940ae", + N => "AES", + P => "4289859e", + X => "AES:1:0:64:0008:0032", + }, + { + C => "fb5df4509ccc171043358e576678c8d72744d175886d450a", + D => 1, + I => 0, + K => "f63f1d6899fe5a1f8038d0be7d39de9e7f6d25c11e94f21ed3e8cdfc1d007f8d", + N => "AES", + P => "9d30e7bf7f9e8d3f6a", + X => "AES:1:0:64:0018:0048", + }, + { + C => "4ef13238834b1389b90888995332c2f1461d85439086770b", + D => 1, + I => 0, + K => "7134f05007d37985bc7f68f92409aef0ece671b397157307a38103a2f2ba8597", + N => "AES", + P => "083436d3dfa539900b6a7485", + X => "AES:1:0:64:0024:0048", + }, + { + C => "45691a184ae6cd921f39bdc497383f7cea822df53e009b224e35feee61af962bfd6c63706dd7c5fca35398ffb0200127775efc35dd1dd847b3191a2b3730b7923c8d31db6422e5b0e878338ac599c2d478b99848b25defb8b3219ac3deaa3ac30390b8986ce4523f30ea4ccde080a434", + D => 1, + I => 0, + K => "ab9583bd52cec5bc0e52ecb491b3463809b44cf52bca44bb6f5e4d1003db9d17", + N => "AES", + P => "1870bf704cbde83813c1346ac787d6542ae3cfdb8f0a6fa8e3e66de6394025d42d2c2acb3f1d8b32cd28269e05aeaa227688ae0142681088d49906bd03e1d7d7233f48243bf30ae34d0e953f4ea9bac3c0fcf149089822f8c2afe885a7ec91f08374313706", + X => "AES:1:0:64:0202:0224", + }, + { + C => "1be58ef58c742d1fc205915bbbb19152", + D => 1, + I => 1, + K => "79f85745de77103bce1c12220867791d", + N => "AES", + P => "13", + X => "AES:1:1:32:0002:0032", + }, + { + C => "bc50b78e685ef73946e05c628da34084", + D => 1, + I => 1, + K => "e97a03ceb118e8ecd6828094f93435b5", + N => "AES", + P => "8787cdc8", + X => "AES:1:1:32:0008:0032", + }, + { + C => "93bbe69d48c9ce29ea3bb60785d127349423f2e18a30e57f", + D => 1, + I => 1, + K => "df1c32e0c1cc317945a50910affe6183", + N => "AES", + P => "52b63a559b659b81e3", + X => "AES:1:1:32:0018:0048", + }, + { + C => "df4b237e5cd1f91b72136dbcdcaa8c4b39778d6cbcf6832e", + D => 1, + I => 1, + K => "2a3597930117b03a9f7dfae8622486ac", + N => "AES", + P => "6d6f08585e7eea010b611b35", + X => "AES:1:1:32:0024:0048", + }, + { + C => "9604f8ac3413b5ce23fb82622e8e60ca5ba4b90bcdf0a52acf52401ce67809dc0e26b0f2f51000be50fe284cdb1ad9fce21a2f969da2392ac7c0822d96aafa082f527149f2fb76ba2a3307d62106f8df3c071a180213b1e7483e32e1dbb00fd47d66ce8f729aad91059533f25351fa4e", + D => 1, + I => 1, + K => "eb542f7099600a16ede45a8379c7e727", + N => "AES", + P => "cae927d387c72ce134885502fc094363326f0d68df297db84fb8291cdf6892ae29814de9f45a93e8ad32c1b330869a29d660f669c0314d54a8804caf5e7d6d18e8369e3bbd21c9e40b102cfc77c2f6f79209c0e4f5a90757d067fd1aea101115c9cf0ff066", + X => "AES:1:1:32:0202:0224", + }, + { + C => "6ab04a9159d271b1c335697d63f3a0e2", + D => 1, + I => 1, + K => "3b388df94ce8e629af07b483513609fd6affaedf62eea248", + N => "AES", + P => "38", + X => "AES:1:1:48:0002:0032", + }, + { + C => "d70db3c10273a5c362fb3891f016010a", + D => 1, + I => 1, + K => "1e5154438b543de803de95592bc780e317d2aaed1e17ad38", + N => "AES", + P => "af04adb8", + X => "AES:1:1:48:0008:0032", + }, + { + C => "9ac19f6f214e8589821d8f1d70a741ec00d256c985624c27", + D => 1, + I => 1, + K => "2b6bf835bd4510cdc860d95a2c1085b9a2f7900c184526f1", + N => "AES", + P => "b72935458e3b310103", + X => "AES:1:1:48:0018:0048", + }, + { + C => "4f030ac89686a214d1fdf9258bedd8d95644730bbfca9e75", + D => 1, + I => 1, + K => "6d115bcb39d5d76bfda9ea47fd7dbb9935a0e973e647519a", + N => "AES", + P => "b7f6b214ee4f852276d59f21", + X => "AES:1:1:48:0024:0048", + }, + { + C => "6f018113edd2b3fde7f0862bc29cdf61715aee28f60cc6a64eb8f8c69d636511fc359a7198644b2da4c0dad4939f99d2a9adf8297a358bd5403b919107e354b685d6ee96c20d9270b27e27a0acd744c5d80c42869376c7a705acb4a7ee5ced56fb0f90e45325c7df8a2527d3cc8896b1", + D => 1, + I => 1, + K => "222716cf9d410f8ece79f82ae038ee9a80ce5709bb173968", + N => "AES", + P => "bcce5c159a9e8802ced0bb50cfa788ba4dc63caa6e2ccaaf3527285bfa0a17c110c63df4691d80f6ef804e78f372be25af286139340383eb55dab74d92a72f47804891e5d5ef7395536cf9cc6bfec6c14e7294225a078f8d5b77c5495d7bcc738a91a151fe", + X => "AES:1:1:48:0202:0224", + }, + { + C => "a205fd2c3fc63fe47b62158bab2e5e85", + D => 1, + I => 1, + K => "cd150528ff9b9c893d907f3186a3d39e7c7ddb76045ce182cebb57f3a7bd93e4", + N => "AES", + P => "e4", + X => "AES:1:1:64:0002:0032", + }, + { + C => "efe397b6f49a26c55b3fce003effb1ac", + D => 1, + I => 1, + K => "24deb093ada160eb0730c6feb55461ffd29a5e8d149cce1a100de48bcd99c358", + N => "AES", + P => "8b2f0549", + X => "AES:1:1:64:0008:0032", + }, + { + C => "753ff2767c4d1b6de70046fe19393fc7e0d3832fe142c92a", + D => 1, + I => 1, + K => "7d5d5d7e6358553c4e62579cde319f7281a1cf7ab0ddd75c33553bef528f0f9e", + N => "AES", + P => "efe51c572741253043", + X => "AES:1:1:64:0018:0048", + }, + { + C => "fd5d5750fc8b9db9783645815a9c3936b0e515cc35b953c7", + D => 1, + I => 1, + K => "914cd9c46121f073064bfbcc1a98c9ab1a395e52255641cbc299632a952e15a7", + N => "AES", + P => "b78624855ceeb1ed6331a653", + X => "AES:1:1:64:0024:0048", + }, + { + C => "24badd566fb5da237de770be86b50b9dbe72113a50b5d85708f2e0a3db42cf6d6a197654f1b87e0a73100e78b08d9ffae7fb19e6181ee82995523f00a62b41e065e98d9173d8cc467c7eacbd76350c0fcdfd293fc5ceb635b180eadc3920475271e3323b409e5fc75159c6c878310e50", + D => 1, + I => 1, + K => "174cc4d43afbf5da1f0eb43ba7fb020596bc9a72fce9c2541a9b3f81a08c95e6", + N => "AES", + P => "c4c70e1de9911430b8d77346e8114db0345638d4dca5ede2aeeaf8e830a7f02ec0ed9d675d61ef410809188de380f5673b746b2818999895a71f835d76074b550a6570896577c90027c7f5508fade8213615f5a56d02d2acac2ce7c98da371c6c94dfbf410", + X => "AES:1:1:64:0202:0224", + }, + { + C => "d8ec69150ba3980aea57ae4a", + D => 0, + I => 0, + K => "09a638783af66b908137c4d3361c1ae152081195fee41dea", + N => "DES_EDE", + P => "965ee3269f2b0a28", + X => "DES_EDE:0:0:48:0016:0024", + }, + { + C => "6f08140b29a5bdc7ee5cf023df19a40f", + D => 0, + I => 0, + K => "df77cfba77e3087ec8fd1b79a8ac5d41c4b7708a9a037a48", + N => "DES_EDE", + P => "2a42f3c55aa2b2a9cb6d2af3", + X => "DES_EDE:0:0:48:0024:0032", + }, + { + C => "960d2f3e0616d00c53a082b4d1b9da833bb31d79", + D => 0, + I => 0, + K => "9ab6db4073be11e46b8c5bf4957956395ac87f98727b52d8", + N => "DES_EDE", + P => "a9cebce4d9a24720e7676eea61674d91", + X => "DES_EDE:0:0:48:0032:0040", + }, + { + C => "d845253b5fa0a55f0f7407060dc2c2753b1e4b3132b8039e", + D => 0, + I => 0, + K => "3a41eaffde08c438aeffc755dafc59d1462c556a7737490a", + N => "DES_EDE", + P => "455c114fca3c2d06c0a717099f3141ef562a79c8", + X => "DES_EDE:0:0:48:0040:0048", + }, + { + C => "a2e8adccaf3c860d22ebc16ab71af0777d02336feb74f46306fc9f2291100b249b250bd67fa67b6c98743a4549fd8f37a7457f50adc10f32e6b289b62bdd53dfc955e29e695a28c2ebe0a9de6176b8053e91181e31c90ac5b6da155f64f3f8712a8c780269752852d648ff1cb21137e1c919679aca0e886a40bf6454952525fc92910733f6d8ac1933f76a247a612aefb00431da8b3a72d23b4358437c9c33150ce96ae297cd403c5d8f8c70b9b84aec5789705da7027d94b23fb4d406fc428e1abab3800a499f009fae12e417f1d392b497a795034edf25c3f31cabcb16f1fa88f9bdbd50276df7ecaea3fe58268e987be02ff08307dbe8933b5942a4677b12729aaa22", + D => 0, + I => 0, + K => "eeecc916cd4f6971f43743a5d8a70e1fe74c1dd22983ddd7", + N => "DES_EDE", + P => "c6cc6ff311ba309831452f316efa53c4a4f62dc35fb7291d3dc361e76246eb9f51cc171fcfeb0579d9b77d950a3edffc756a4c4d9e3fc6eb439f29153549f377442f9d30b31ff25a0c3949dfa4da60f9c26cde4dbfe6d31f1fce1afc8da2a11b06a41807c1956c30406bd00ac4ece42077d055aa2224c73d73524da5710156aa4ec39b92a630cbee4a5d54a877598cbeaa8e6f85b75c82e5d717e013b0be25b59a0e1dfa7b6112519b00f191c95398269c23c783e1af23f7917dc84472f59e7d8626b3b49aa28b842e73603fe81eed72b6ee1c8534108163fae7810650b8f25d58e7422cbb59f168f72ad2aeb4800c39cb28d4ac654bc039a000d865693faf11", + X => "DES_EDE:0:0:48:0512:0520", + }, + { + C => "a79c4069abc19f616412df22", + D => 0, + I => 1, + K => "73613eaaa529180cae20da0d8bdedb75712e7ffe7edbae36", + N => "DES_EDE", + P => "fd5affd829c11b4e", + X => "DES_EDE:0:1:48:0016:0024", + }, + { + C => "dcf83900419ff3ecd23e3d93c23f7d00", + D => 0, + I => 1, + K => "c85b88faf497044b3ca8965509e656a4af5c7015cac8e0b6", + N => "DES_EDE", + P => "c1663e44cc68863b7e5d2acd", + X => "DES_EDE:0:1:48:0024:0032", + }, + { + C => "bedcdc798bd4221c0337921d9c647c470ea03773", + D => 0, + I => 1, + K => "57bc800aca05d2716b0821f33bf90abd5935697ae923f037", + N => "DES_EDE", + P => "0b94f181769412bc82f5c4aedfb8ecc1", + X => "DES_EDE:0:1:48:0032:0040", + }, + { + C => "38b18fbc37790e873071f8f6169e3a4feb1d15ec215fddf8", + D => 0, + I => 1, + K => "8461e5aaa4591328e2e80df6244c2f94d8c412bcd8d0b1a2", + N => "DES_EDE", + P => "10b25e11a6b393913a0ff5f6fc9d255bc196647f", + X => "DES_EDE:0:1:48:0040:0048", + }, + { + C => "1f5b622e928c595f5afb203daaf378a1735a5befb11e13d0a71343a3f1594be95e5e5e753c958f2e0c6c8f8c61cd320feb69a54a0cd1e442f992593d2d2743d0bbc0cdfd5f21b22c53e27f25ecf77639f38d9fd490c97ba37df3af8fe9656a118d7a2c69f25a75c47fe5b34883469bb43614734bfadd22610a5a297ed437d3bb9fdd50c05a04a2f1b952385dec6461f9301205c73fd5af0b3f382f4b3d283351e7f6d4b71861c788df787866df8dfa3fbf4b72e10603787011e829497c5a29726e94cd05c26edadf45e69f8073ecd9bcb04d48bfbe49f7e7c082eb510f2cdd4786f5e012276f1ec49fd45e20043b7feae815db0c14863aa3a403e1d808102e5cd45d3f55", + D => 0, + I => 1, + K => "f1ad7c02d3c4f0ea93ebb210a1817c67a3303c4bd3c368a3", + N => "DES_EDE", + P => "de1886bc77723049d735181560083b180d8358facee0c75519941ca47ccd8b41b6ea0c38fa83d34a80d291cf736beeadadafa691ae35dc6bf8eb5f5660d99ed95e769cc66be5a43ddd6c39dfe2fd4ad679955cf8e00a0f87254b89a901466e8c7da174bcfbbb7145e75e42a0a0383049e93e04c20a110b8a6cdc8f72e30133127a8195575bfcf824e713ee7b7243193330a5b3aec383ed8bf552f6e95bd3e0fe7fc48f57d46e716e61f777c87b3e59ded46f6ec3bb2f9e1e90f088a84b4a6c35855d754706255d35707f427ccfafd513455b849cdb59b90caf505eb874ff93c278acf6afd92cdaf994e9083da0873403175a7b0614982f73e38973cf05fa16f4", + X => "DES_EDE:0:1:48:0512:0520", + }, +); + +{ + # https://www.ietf.org/rfc/rfc3394.txt + my $kek = pack("H*", "000102030405060708090a0b0c0d0e0f1011121314151617"); + my $keydata = pack("H*", "00112233445566778899aabbccddeeff"); + my $wrap = aes_key_wrap($kek, $keydata); + is(unpack("H*", $wrap), "96778b25ae6ca435f92b5b97c050aed2468ab8a17ad84e5d"); + my $unwrap = aes_key_unwrap($kek, $wrap); + is(unpack("H*", $unwrap), "00112233445566778899aabbccddeeff"); +} + +{ + # https://www.ietf.org/rfc/rfc3394.txt + my $kek = pack("H*", "000102030405060708090a0b0c0d0e0f"); + my $keydata = pack("H*", "00112233445566778899aabbccddeeff"); + my $wrap = aes_key_wrap($kek, $keydata); + is(unpack("H*", $wrap), "1fa68b0a8112b447aef34bd8fb5a7b829d3e862371d2cfe5"); + my $unwrap = aes_key_unwrap($kek, $wrap); + is(unpack("H*", $unwrap), "00112233445566778899aabbccddeeff"); +} + +{ + # https://tools.ietf.org/html/rfc5649 + my $kek = pack("H*", "5840df6e29b02af1ab493b705bf16ea1ae8338f4dcc176a8"); + my $keydata = pack("H*", "c37b7e6492584340bed12207808941155068f738"); + my $wrap = aes_key_wrap($kek, $keydata); + is(unpack("H*", $wrap), "138bdeaa9b8fa7fc61f97742e72248ee5ae6ae5360d1ae6a5f54f373fa543b6a"); + my $unwrap = aes_key_unwrap($kek, $wrap); + is(unpack("H*", $unwrap), "c37b7e6492584340bed12207808941155068f738"); +} + +{ + # https://tools.ietf.org/html/rfc5649 + my $kek = pack("H*", "5840df6e29b02af1ab493b705bf16ea1ae8338f4dcc176a8"); + my $keydata = pack("H*", "466f7250617369"); + my $wrap = aes_key_wrap($kek, $keydata); + is(unpack("H*", $wrap), "afbeb0f07dfbf5419200f2ccb50bb24f"); + my $unwrap = aes_key_unwrap($kek, $wrap); + is(unpack("H*", $unwrap), "466f7250617369"); +} + +{ + # https://tools.ietf.org/html/draft-ietf-jose-json-web-encryption + my $keydata = join '', map { chr($_) } (4, 211, 31, 197, 84, 157, 252, 254, 11, 100, 157, 250, 63, 170, 106, 206, 107, 124, 212, 45, 111, 107, 9, 219, 200, 177, 0, 240, 143, 156, 44, 207); + my $kek = decode_b64u("GawgguFyGrWKav7AX4VKUg"); + my $ct = decode_b64u("6KB707dM9YTIgHtLvtgWQ8mKwboJW3of9locizkDTHzBC2IlrT1oOQ"); + my $wrap = aes_key_wrap($kek, $keydata); + is(unpack("H*", $wrap), unpack("H*",$ct)); + my $unwrap = aes_key_unwrap($kek, $wrap); + is(unpack("H*", $unwrap), unpack("H*",$keydata)); +} + +for (@tv) { + my $kek = pack("H*", $_->{K}); + my $keydata = pack("H*", $_->{P}); + my $wrap = aes_key_wrap($kek, $keydata, $_->{N}, $_->{D}, $_->{I}); + is(unpack("H*", $wrap), $_->{C}); + my $unwrap = aes_key_unwrap($kek, $wrap, $_->{N}, $_->{D}, $_->{I}); + is(unpack("H*", $unwrap), $_->{P}); +} + +done_testing; \ No newline at end of file diff --git a/t/kw_ecdh.t b/t/kw_ecdh.t new file mode 100644 index 0000000..95c788e --- /dev/null +++ b/t/kw_ecdh.t @@ -0,0 +1,81 @@ +use strict; +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"}'); + +{ + my $header={ + alg => "ECDH-ES", + enc => "A128CBC-HS256", + epk => { + crv => "P-256", + kty => "EC", + x => "-VMKLnMyoHTtFRZF6qW6wdFnA7mJBGb798WqU0UwAXY", + y => "hPAcQy83U-5B9uSmqnsWpVsluhdbRdMgnvtpgf5XWN8", + }, + }; + my $expected_hex='81cbc97bcec94c11f704a10057ecde25d0c2ad56821e15816e98308bafdf8a5c'; + my $unw = ecdh_key_unwrap($kek_private, $header->{enc}, $header->{epk}, $header->{apu}, $header->{apv}); + is(unpack("H*", $unw), $expected_hex, "ECDH-ES + A128CBC-HS256") +} + +{ + my $header={ + alg => "ECDH-ES", + enc => "A128GCM", + epk => { + crv => "P-256", + kty => "EC", + x => "Ol7jIi8H1iE1krvQNaPxjy-q-czP0N4EWO3R7584hGU", + y => "MdSeu9Snukp9lKde9rUnbjxkz3m_dMjjAw94WwCLZks", + }, + }; + my $expected_hex='20fdcc92d30215765cb346805b5335c1'; + my $unw = ecdh_key_unwrap($kek_private, $header->{enc}, $header->{epk}, $header->{apu}, $header->{apv}); + is(unpack("H*", $unw), $expected_hex, "ECDH-ES + A128GCM") +} + +{ + my $header={ + alg => "ECDH-ES", + enc => "A192GCM", + epk => { + crv => "P-256", + kty => "EC", + x => "PTwTYgcCK6iPn5D8Ne0HiDDmzoCiEaiJsH7C2pCEpsc", + y => "7gT2OTk-q9Ekkj8N58Gx-J6_ckqtgYeO0Drgq6IaOXc", + }, + }; + my $expected_hex='4183d0802022bd2fc68231a7896c1846cdb022f335b68b97'; + my $unw = ecdh_key_unwrap($kek_private, $header->{enc}, $header->{epk}, $header->{apu}, $header->{apv}); + is(unpack("H*", $unw), $expected_hex, "ECDH-ES + A192GCM") +} + +{ + my $header={ + alg => "ECDH-ES", + enc => "A256GCM", + epk => { + crv => "P-256", + kty => "EC", + x => "mExbMerMmx_o3fGmCtM4LwRPNsDlG4MDL55wjc7wpL8", + y => "C--vuVTv8XXS9qOZm_ZYqNxXn-bDWFLCeL1M6QKjIbY", + }, + }; + my $expected_hex='4ddc0f6249fb5a4f1c908cc9fbf27e1a1d275e601bd23079851a0af7a8f18646'; + my $unw = ecdh_key_unwrap($kek_private, $header->{enc}, $header->{epk}, $header->{apu}, $header->{apv}); + is(unpack("H*", $unw), $expected_hex, "ECDH-ES + A256GCM") +} + +{ + my $kek_public=Crypt::PK::ECC->new(\$kek_private->export_key_jwk('public')); + my ($k1, $epk) = ecdh_key_wrap($kek_public, "A256GCM"); + my $k2 = ecdh_key_unwrap($kek_private, "A256GCM", $epk); + is(unpack("H*", $k2), unpack("H*", $k1), "wrap+unwrap ECDH-ES"); +} + +done_testing; \ No newline at end of file diff --git a/t/kw_ecdhaes.t b/t/kw_ecdhaes.t new file mode 100644 index 0000000..d32d4ee --- /dev/null +++ b/t/kw_ecdhaes.t @@ -0,0 +1,68 @@ +use strict; +use warnings; +use Test::More; +use Crypt::KeyWrap qw(ecdhaes_key_wrap ecdhaes_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"}'); + +{ + my $header={ + alg => "ECDH-ES+A128KW", + enc => "A128GCM", + epk => { + crv => "P-256", + kty => "EC", + x => "6ysUfUwOoUlD5JFdojPqWxWwfBwokmZjNVlIDQkpmO0", + y => "JeZbk_Pk22mj0TU0pnnB3UiL2K2IqYzNM1UTOe-JcwY", + }, + }; + my $ct_hex='7b59f7613a2b249f87ede59bcbea5f196cd5c7468349c093'; + my $expected_hex='db216b638fb064b0f3cf64a0ac73735e'; + my $unw = ecdhaes_key_unwrap($kek_private, pack("H*", $ct_hex), $header->{alg}, $header->{epk}, $header->{apu}, $header->{apv}); + is(unpack("H*", $unw), $expected_hex, "ECDH-ES+A128KW"); +} + +{ + my $header={ + alg => "ECDH-ES+A192KW", + enc => "A192GCM", + epk => { + crv => "P-256", + kty => "EC", + x => "Ya1BV1IYxEohUcIrXCAOOzKe0POMuBPIf2dR6kMeSts", + y => "SUujcsl0vfiJn1u_4Y59MMJ5uFGcUZEBTWPu54ARgEE", + }, + }; + my $ct_hex='c293eb5065530ecb616814ee4e88f90f9d4ef9b6d2242070983abb94ae25f1d1'; + my $expected_hex='919ef94365dd2fcca9abf69ee51258c7424e39c05e5f4ea0'; + my $unw = ecdhaes_key_unwrap($kek_private, pack("H*", $ct_hex), $header->{alg}, $header->{epk}, $header->{apu}, $header->{apv}); + is(unpack("H*", $unw), $expected_hex, "ECDH-ES+A192KW"); +} + +{ + my $header={ + alg => "ECDH-ES+A256KW", + enc => "A256GCM", + epk => { + crv => "P-256", + kty => "EC", + x => "ANTg-K8YAUuOk0mQmZLDPUiOqVEPPk-Pf6kRtn6cB2s", + y => "lJi7PLEDe6ZwqJ46jZr-FmPzyswFkpdIU7ZU34t8EDs", + }, + }; + my $ct_hex='22aa77c37c68d76c02ab2355ffcc0d9379b6b472a9066bfae97011b1c1de2ed652fb616c9406df0d'; + my $expected_hex='bf9279a16dd7f284204387bcf9220c0ae9c6061c50ad28d7a42be6a902aedae3'; + my $unw = ecdhaes_key_unwrap($kek_private, pack("H*", $ct_hex), $header->{alg}, $header->{epk}, $header->{apu}, $header->{apv}); + is(unpack("H*", $unw), $expected_hex, "ECDH-ES+A256KW"); +} + +{ + my $kek_public=Crypt::PK::ECC->new(\$kek_private->export_key_jwk('public')); + my ($wrp, $epk) = ecdhaes_key_wrap($kek_public, 'plaintext', "ECDH-ES+A256KW"); + my $unw = ecdhaes_key_unwrap($kek_private, $wrp, "ECDH-ES+A256KW", $epk); + is($unw, 'plaintext', "wrap+unwrap ECDH-ES+A256KW"); +} + +done_testing; \ No newline at end of file diff --git a/t/kw_gcm.t b/t/kw_gcm.t new file mode 100644 index 0000000..eaf49b6 --- /dev/null +++ b/t/kw_gcm.t @@ -0,0 +1,25 @@ +use strict; +use warnings; +use Test::More; +use Crypt::KeyWrap qw(gcm_key_wrap gcm_key_unwrap); +use Crypt::Misc qw(decode_b64u); + +{ + ### test vector from https://github.com/rohe/pyjwkest/blob/5c1e321237dd2affb8b8434f0ca2a15c4da5e2b1/src/jwkest/aes_gcm.py + my $iv = pack("H*", 'cafebabefacedbaddecaf888'); + my $kek = pack("H*", 'feffe9928665731c6d6a8f9467308308'); + my $pt = pack("H*", 'd9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39'); + my $ct = pack("H*", '42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091'); + my $aad = pack("H*", 'feedfacedeadbeeffeedfacedeadbeefabaddad2'); + my $tag = pack("H*", '5bc94fbc3221a5db94fae95ae7121a47'); + + my $rv_pt = gcm_key_unwrap($kek, $ct, $tag, $iv, $aad, 'AES'); + is(unpack("H*", $rv_pt), unpack("H*", $pt), "unwrap aes_gcm.py test vector"); + + my ($rv_ct, $rv_tag, $rv_iv) = gcm_key_wrap($kek, $pt, $aad, 'AES', $iv); + is(unpack("H*", $rv_ct), unpack("H*", $ct), "wrap aes_gcm.py test vector / ct"); + is(unpack("H*", $rv_iv), unpack("H*", $iv), "wrap aes_gcm.py test vector / iv"); + is(unpack("H*", $rv_tag), unpack("H*", $tag), "wrap aes_gcm.py test vector / tag"); +} + +done_testing; \ No newline at end of file diff --git a/t/kw_pbes2.t b/t/kw_pbes2.t new file mode 100644 index 0000000..9b9e008 --- /dev/null +++ b/t/kw_pbes2.t @@ -0,0 +1,30 @@ +use strict; +use warnings; +use Test::More; +use Crypt::KeyWrap qw(pbes2_key_wrap pbes2_key_unwrap); +use Crypt::Misc qw(decode_b64u); + +{ + ### PBES2HS256A128KW test vector from https://github.com/Spomky-Labs/jose/blob/master/tests/PBES2_HS_AESKWKeyEncryptionTest.php + my $header = { + 'alg' => 'PBES2-HS256+A128KW', + 'p2s' => '2WCTcJZ1Rvd_CJuJripQ1w', + 'p2c' => 4096, + 'enc' => 'A128CBC-HS256', + 'cty' => 'jwk+json', + }; + my $b64u_wcek = 'TrqXOwuNUfDV9VPTNbyGvEJ9JMjefAVn-TR1uIxR9p6hsRQh9Tk7BA'; + my $key = join '', map { chr($_) } (84, 104, 117, 115, 32, 102, 114, 111, 109, 32, 109, 121, 32, 108, 105, 112, 115, 44, 32, 98, 121, 32, 121, 111, 117, 114, 115, 44, 32, 109, 121, 32, 115, 105, 110, 32, 105, 115, 32, 112, 117, 114, 103, 101, 100, 46); + my $cek = join '', map { chr($_) } (111, 27, 25, 52, 66, 29, 20, 78, 92, 176, 56, 240, 65, 208, 82, 112, 161, 131, 36, 55, 202, 236, 185, 172, 129, 23, 153, 194, 195, 48, 253, 182); + my $wcek = decode_b64u($b64u_wcek); + my $salt = decode_b64u($header->{p2s}); + my $iter = $header->{p2c}; + + my $unw = pbes2_key_unwrap($key, $wcek, $header->{alg}, $salt, $iter); + is(unpack("H*", $unw), unpack("H*", $cek), "pbes2_key_unwrap"); + + my $wrp = pbes2_key_wrap($key, $cek, $header->{alg}, $salt, $iter); + is(unpack("H*", $wrp), unpack("H*", $wcek), "pbes2_key_wrap"); +} + +done_testing; \ No newline at end of file diff --git a/t/kw_rsa.t b/t/kw_rsa.t new file mode 100644 index 0000000..3a315c5 --- /dev/null +++ b/t/kw_rsa.t @@ -0,0 +1,37 @@ +use strict; +use warnings; +use Test::More; +use Crypt::KeyWrap qw(rsa_key_wrap rsa_key_unwrap); +use Crypt::Misc qw(decode_b64u); +use Crypt::PK::RSA; + +{ + ### RSA key enc test vector from https://github.com/Spomky-Labs/jose/blob/master/tests/RSAKeyEncryptionTest.php + + my $jwk = { + 'kty' => 'RSA', + 'n' => 'sXchDaQebHnPiGvyDOAT4saGEUetSyo9MKLOoWFsueri23bOdgWp4Dy1WlUzewbgBHod5pcM9H95GQRV3JDXboIRROSBigeC5yjU1hGzHHyXss8UDprecbAYxknTcQkhslANGRUZmdTOQ5qTRsLAt6BTYuyvVRdhS8exSZEy_c4gs_7svlJJQ4H9_NxsiIoLwAEk7-Q3UXERGYw_75IDrGA84-lA_-Ct4eTlXHBIY2EaV7t7LjJaynVJCpkv4LKjTTAumiGUIuQhrNhZLuF_RJLqHpM2kgWFLU7-VTdL1VbC2tejvcI2BlMkEpk1BzBZI0KQB0GaDWFLN-aEAw3vRw', + 'e' => 'AQAB', + 'd' => 'VFCWOqXr8nvZNyaaJLXdnNPXZKRaWCjkU5Q2egQQpTBMwhprMzWzpR8Sxq1OPThh_J6MUD8Z35wky9b8eEO0pwNS8xlh1lOFRRBoNqDIKVOku0aZb-rynq8cxjDTLZQ6Fz7jSjR1Klop-YKaUHc9GsEofQqYruPhzSA-QgajZGPbE_0ZaVDJHfyd7UUBUKunFMScbflYAAOYJqVIVwaYR5zWEEceUjNnTNo_CVSj-VvXLO5VZfCUAVLgW4dpf1SrtZjSt34YLsRarSb127reG_DUwg9Ch-KyvjT1SkHgUWRVGcyly7uvVGRSDwsXypdrNinPA4jlhoNdizK2zF2CWQ', + 'p' => '9gY2w6I6S6L0juEKsbeDAwpd9WMfgqFoeA9vEyEUuk4kLwBKcoe1x4HG68ik918hdDSE9vDQSccA3xXHOAFOPJ8R9EeIAbTi1VwBYnbTp87X-xcPWlEPkrdoUKW60tgs1aNd_Nnc9LEVVPMS390zbFxt8TN_biaBgelNgbC95sM', + 'q' => 'uKlCKvKv_ZJMVcdIs5vVSU_6cPtYI1ljWytExV_skstvRSNi9r66jdd9-yBhVfuG4shsp2j7rGnIio901RBeHo6TPKWVVykPu1iYhQXw1jIABfw-MVsN-3bQ76WLdt2SDxsHs7q7zPyUyHXmps7ycZ5c72wGkUwNOjYelmkiNS0', + 'dp' => 'w0kZbV63cVRvVX6yk3C8cMxo2qCM4Y8nsq1lmMSYhG4EcL6FWbX5h9yuvngs4iLEFk6eALoUS4vIWEwcL4txw9LsWH_zKI-hwoReoP77cOdSL4AVcraHawlkpyd2TWjE5evgbhWtOxnZee3cXJBkAi64Ik6jZxbvk-RR3pEhnCs', + 'dq' => 'o_8V14SezckO6CNLKs_btPdFiO9_kC1DsuUTd2LAfIIVeMZ7jn1Gus_Ff7B7IVx3p5KuBGOVF8L-qifLb6nQnLysgHDh132NDioZkhH7mI7hPG-PYE_odApKdnqECHWw0J-F0JWnUd6D2B_1TvF9mXA2Qx-iGYn8OVV1Bsmp6qU', + 'qi' => 'eNho5yRBEBxhGBtQRww9QirZsB66TrfFReG_CcteI1aCneT0ELGhYlRlCtUkTRclIfuEPmNsNDPbLoLqqCVznFbvdB7x-Tl-m0l_eFTj2KiqwGqE9PZB9nNTwMVvH3VRRSLWACvPnSiwP8N5Usy-WRXS-V7TbpxIhvepTfE0NNo', + }; + + my $cek = join '', map { chr($_) } (4, 211, 31, 197, 84, 157, 252, 254, 11, 100, 157, 250, 63, 170, 106, 206, 107, 124, 212, 45, 111, 107, 9, 219, 200, 177, 0, 240, 143, 156, 44, 207); + my $wcek = decode_b64u('UGhIOguC7IuEvf_NPVaXsGMoLOmwvc1GyqlIKOK1nN94nHPoltGRhWhw7Zx0-kFm1NJn8LE9XShH59_i8J0PH5ZZyNfGy2xGdULU7sHNF6Gp2vPLgNZ__deLKxGHZ7PcHALUzoOegEI-8E66jX2E4zyJKx-YxzZIItRzC5hlRirb6Y5Cl_p-ko3YvkkysZIFNPccxRU7qve1WYPxqbb2Yw8kZqa2rMWI5ng8OtvzlV7elprCbuPhcCdZ6XDP0_F8rkXds2vE4X-ncOIM8hAYHHi29NX0mcKiRaD0-D-ljQTP-cFPgwCp6X-nZZd9OHBv-B3oWh2TbqmScqXMR4gp_A'); + + my $key = Crypt::PK::RSA->new($jwk); + my $unw = rsa_key_unwrap($key, $wcek, 'RSA1_5'); + is(unpack("H*", $unw), unpack("H*", $cek), "rsa_key_unwrap"); + + for my $alg (qw/RSA1_5 RSA-OAEP-256 RSA-OAEP/) { + my $w = rsa_key_wrap($key, $cek, $alg); + my $u = rsa_key_unwrap($key, $w, $alg); + is(unpack("H*", $u), unpack("H*", $cek), "rsa_key_(un)wrap $alg"); + } +} + +done_testing; \ No newline at end of file