Crypt-JWT
view release on metacpan or search on metacpan
t/rfc7519.t view on Meta::CPAN
use Test::More;
use Crypt::JWT qw(decode_jwt);
# Test vectors from RFC 7519 (JSON Web Token).
# https://www.rfc-editor.org/rfc/rfc7519.txt
#
# RFC 7519 Section 3.1 contains the canonical "joe" JWT example, and
# Appendix A.1/A.2 give an Encrypted JWT and a Nested JWT. The HMAC
# signing key for Section 3.1 is taken from RFC 7515 Appendix A.1; the
# RSA key for Appendix A.1/A.2 is taken from RFC 7516 Appendix A.2.
# Both key sources are referenced explicitly by RFC 7519.
my $hs256_jwk = {
kty => "oct",
k => "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow",
};
my $rsa_jwk = {
kty => "RSA",
n => "sXchDaQebHnPiGvyDOAT4saGEUetSyo9MKLOoWFsueri23bOdgWp4Dy1WlUzewbgBHod5pcM9H95GQRV3JDXboIRROSBigeC5yjU1hGzHHyXss8UDprecbAYxknTcQkhslANGRUZmdTOQ5qTRsLAt6BTYuyvVRdhS8exSZEy_c4gs_7svlJJQ4H9_NxsiIoLwAEk7-Q3UXERGYw_75IDrGA84-lA_-Ct4eTlXHBIY2EaV7...
e => "AQAB",
d => "VFCWOqXr8nvZNyaaJLXdnNPXZKRaWCjkU5Q2egQQpTBMwhprMzWzpR8Sxq1OPThh_J6MUD8Z35wky9b8eEO0pwNS8xlh1lOFRRBoNqDIKVOku0aZb-rynq8cxjDTLZQ6Fz7jSjR1Klop-YKaUHc9GsEofQqYruPhzSA-QgajZGPbE_0ZaVDJHfyd7UUBUKunFMScbflYAAOYJqVIVwaYR5zWEEceUjNnTNo_CVSj-VvXLO...
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",
};
#----------------------------------------------------------------------
# Section 3.1: Example JWT (HS256, identical to RFC 7515 A.1)
#----------------------------------------------------------------------
{
my $jwt = "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9".
".eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ".
".dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk";
my ($header, $payload) = decode_jwt(token=>$jwt, key=>$hs256_jwk, decode_header=>1, verify_exp=>0);
is($header->{typ}, "JWT", "Section 3.1 header typ");
is($header->{alg}, "HS256", "Section 3.1 header alg");
is($payload->{iss}, "joe", "Section 3.1 payload iss");
is($payload->{exp}, 1300819380, "Section 3.1 payload exp");
ok($payload->{"http://example.com/is_root"}, "Section 3.1 payload http://example.com/is_root");
}
#----------------------------------------------------------------------
# Section 6.1: Unsecured JWT (alg=none)
#----------------------------------------------------------------------
{
my $jwt = "eyJhbGciOiJub25lIn0".
".eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ".
".";
my ($header, $payload) = decode_jwt(token=>$jwt, allow_none=>1, decode_header=>1, verify_exp=>0);
is($header->{alg}, "none", "Section 6.1 alg=none header");
is($payload->{iss}, "joe", "Section 6.1 payload iss");
}
#----------------------------------------------------------------------
# Appendix A.1: Example Encrypted JWT (RSA1_5 + A128CBC-HS256).
# Same JWE computation/keys as RFC 7516 Appendix A.2, but the JWE
# Plaintext is the JWT Claims Set from Section 3.1.
#----------------------------------------------------------------------
{
my $jwt = "eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0".
".QR1Owv2ug2WyPBnbQrRARTeEk9kDO2w8qDcjiHnSJflSdv1iNqhWXaKH4MqAkQtMoNfABIPJaZm0HaA415sv3aeuBWnD8J-Ui7Ah6cWafs3ZwwFKDFUUsWHSK-IPKxLGTkND09XyjORj_CHAgOPJ-Sd8ONQRnJvWn_hXV1BNMHzUjPyYwEsRhDhzjAD26imasOTsgruobpYGoQcXUwFDn7moXPRfDE8-NoQX7N7ZYM...
".AxY8DCtDaGlsbGljb3RoZQ".
".MKOle7UQrG6nSxTLX6Mqwt0orbHvAKeWnDYvpIAeZ72deHxz3roJDXQyhxx0wKaMHDjUEOKIwrtkHthpqEanSBNYHZgmNOV7sln1Eu9g3J8".
".fiK51VwhsxJ-siBMR-YFiA";
my ($header, $payload) = decode_jwt(token=>$jwt, key=>$rsa_jwk, decode_header=>1, verify_exp=>0);
is($header->{alg}, "RSA1_5", "Appendix A.1 header alg");
is($header->{enc}, "A128CBC-HS256", "Appendix A.1 header enc");
is($payload->{iss}, "joe", "Appendix A.1 payload iss");
is($payload->{exp}, 1300819380, "Appendix A.1 payload exp");
ok($payload->{"http://example.com/is_root"}, "Appendix A.1 payload http://example.com/is_root");
}
#----------------------------------------------------------------------
# Appendix A.2: Nested JWT (JWE wrapping a JWS).
# The plaintext of the JWE is the JWS from RFC 7515 Appendix A.2; the
# JWE uses RSA1_5 + A128CBC-HS256 with cty=JWT to mark nesting.
# Crypt::JWT walks the cty=JWT chain when decode_payload defaults
# apply, so the inner JWS payload is what comes back.
#----------------------------------------------------------------------
{
my $jwt = "eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiY3R5IjoiSldUIn0".
".g_hEwksO1Ax8Qn7HoN-BVeBoa8FXe0kpyk_XdcSmxvcM5_P296JXXtoHISr_DD_MqewaQSH4dZOQHoUgKLeFly-9RI11TG-_Ge1bZFazBPwKC5lJ6OLANLMd0QSL4fYEb9ERe-epKYE3xb2jfY1AltHqBO-PM6j23Guj2yDKnFv6WO72tteVzm_2n17SBFvhDuR9a2nHTE67pe0XGBUS_TK7ecA-iVq5COeVdJR4U4...
".UmVkbW9uZCBXQSA5ODA1Mg".
".VwHERHPvCNcHHpTjkoigx3_ExK0Qc71RMEParpatm0X_qpg-w8kozSjfNIPPXiTBBLXR65CIPkFqz4l1Ae9w_uowKiwyi9acgVztAi-pSL8GQSXnaamh9kX1mdh3M_TT-FZGQFQsFhu0Z72gJKGdfGE-OE7hS1zuBD5oEUfk0Dmb0VzWEzpxxiSSBbBAzP10l56pPfAtrjEYw-7ygeMkwBl6Z_mLS6w6xUgKlvW6UL...
".AVO9iT5AV4CzvDJCdhSFlQ";
# Decrypt the outer JWE; the plaintext is the inner JWS compact form.
my ($header, $inner) = decode_jwt(token=>$jwt, key=>$rsa_jwk,
decode_header=>1, decode_payload=>0,
verify_exp=>0);
is($header->{alg}, "RSA1_5", "Appendix A.2 outer alg");
is($header->{enc}, "A128CBC-HS256", "Appendix A.2 outer enc");
is($header->{cty}, "JWT", "Appendix A.2 outer cty=JWT (nested)");
# The inner JWS is the RFC 7515 Appendix A.2 example.
my $expected_inner = "eyJhbGciOiJSUzI1NiJ9".
".eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ".
".cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqt...
is($inner, $expected_inner, "Appendix A.2 inner JWS recovered byte-for-byte");
# The inner JWS is signed with the RSA key from RFC 7515 Appendix A.2
# (a different key than the one that wraps the JWE above).
my $inner_signing_jwk = {
kty => "RSA",
n => "ofgWCuLjybRlzo0tZWJjNiuSfb4p4fAkd_wWJcyQoTbji9k0l8W26mPddxHmfHQp-Vaw-4qPCJrcS2mJPMEzP1Pt0Bm4d4QlL-yRT-SFd2lZS-pCgNMsD1W_YpRPEwOWvG6b32690r2jZ47soMZo9wGzjb_7OMg0LOL-bSf63kpaSHSXndS5z5rexMdbBYUsLA9e-KXBdQOS-UTo7WTBEMa2R2CapHg665xsmtdVMT...
e => "AQAB",
};
my $inner_payload = decode_jwt(token=>$inner, key=>$inner_signing_jwk, verify_exp=>0);
is($inner_payload->{iss}, "joe", "Appendix A.2 inner JWS payload iss");
is($inner_payload->{exp}, 1300819380, "Appendix A.2 inner JWS payload exp");
}
done_testing;
( run in 0.516 second using v1.01-cache-2.11-cpan-e1769b4cff6 )