This simple test decodes a JSON Web token after verifying it and asserts that the claim contained inside are as expected. The RSA public key is the one used by https://jwt.io/ by default and thus allowing easy experimentation. For future extensibility of the tests, the private key is appended, but is not currently used. As rsatoc has a build-time openssl dependency, which we would complicate running the test suite everywhere, we ship a precompiled C file. To regenerate, REGENERATE_RSATOC can be specified as build argument. The reason, we don't use the standard make mechanism of file timestamps is that after a git checkout, we aren't guaranteed that the shipped file will be newer than the pem file, which renders the mechanism useless for allowing users to build all unit tests without OpenSSL. Signed-off-by: Ahmad Fatoum <a.fatoum@xxxxxxxxxxxxxx> --- test/self/Kconfig | 7 ++ test/self/Makefile | 11 ++- test/self/jwt.c | 157 +++++++++++++++++++++++++++++++ test/self/jwt_test.pem | 37 ++++++++ test/self/jwt_test.pem.c_shipped | 49 ++++++++++ 5 files changed, 260 insertions(+), 1 deletion(-) create mode 100644 test/self/jwt.c create mode 100644 test/self/jwt_test.pem create mode 100644 test/self/jwt_test.pem.c_shipped diff --git a/test/self/Kconfig b/test/self/Kconfig index e7da07491a91..5850dc95973b 100644 --- a/test/self/Kconfig +++ b/test/self/Kconfig @@ -36,12 +36,15 @@ config SELFTEST_ENABLE_ALL select SELFTEST_FS_RAMFS if FS_RAMFS select SELFTEST_TFTP if FS_TFTP select SELFTEST_JSON if JSMN + select SELFTEST_JWT if JWT select SELFTEST_DIGEST if DIGEST select SELFTEST_MMU if MMU select SELFTEST_STRING select SELFTEST_SETJMP if ARCH_HAS_SJLJ select SELFTEST_REGULATOR if REGULATOR && OFDEVICE select SELFTEST_TEST_COMMAND if CMD_TEST + help + Selects all self-tests compatible with current configuration config SELFTEST_MALLOC bool "malloc() selftest" @@ -73,6 +76,10 @@ config SELFTEST_JSON bool "JSON selftest" depends on JSMN +config SELFTEST_JWT + bool "JSON Web Token selftest" + depends on JWT + config SELFTEST_MMU bool "MMU remapping selftest" select MEMTEST diff --git a/test/self/Makefile b/test/self/Makefile index 8168abf26278..24e78a186513 100644 --- a/test/self/Makefile +++ b/test/self/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_SELFTEST_OF_MANIPULATION) += of_manipulation.o of_manipulation.dtb. obj-$(CONFIG_SELFTEST_ENVIRONMENT_VARIABLES) += envvar.o obj-$(CONFIG_SELFTEST_FS_RAMFS) += ramfs.o obj-$(CONFIG_SELFTEST_JSON) += json.o +obj-$(CONFIG_SELFTEST_JWT) += jwt.o jwt_test.pem.o obj-$(CONFIG_SELFTEST_DIGEST) += digest.o obj-$(CONFIG_SELFTEST_MMU) += mmu.o obj-$(CONFIG_SELFTEST_STRING) += string.o @@ -16,5 +17,13 @@ obj-$(CONFIG_SELFTEST_SETJMP) += setjmp.o obj-$(CONFIG_SELFTEST_REGULATOR) += regulator.o test_regulator.dtbo.o obj-$(CONFIG_SELFTEST_TEST_COMMAND) += test_command.o -clean-files := *.dtb *.dtb.S .*.dtc .*.pre .*.dts *.dtb.z +ifdef REGENERATE_RSATOC + +$(obj)/jwt_test.pem.c_shipped: $(src)/jwt_test.pem FORCE + $(call if_changed,rsa_keys,$(basename $(target-stem)):$<,-s) + +endif + +clean-files := *.dtb *.dtb.S .*.dtc .*.pre .*.dts *.dtb.z * clean-files += *.dtbo *.dtbo.S .*.dtso +clean-files += *.pem.c diff --git a/test/self/jwt.c b/test/self/jwt.c new file mode 100644 index 000000000000..f37b44be22b8 --- /dev/null +++ b/test/self/jwt.c @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <common.h> +#include <rsa.h> +#include <bselftest.h> +#include <crypto/jwt.h> +#include <console.h> + +BSELFTEST_GLOBALS(); + +static const jsmntok_t *check_token(const jsmntok_t *token, + const char *claim, + const char *payload, + jsmntype_t expected_type, + const char *expected_value) +{ + total_tests++; + + if (token->type != expected_type) { + failed_tests++; + printf("claim %s has type mismatch: got %d, but %d expected\n", + claim, token->type, expected_type); + return NULL; + } + + total_tests++; + + if (!jsmn_eq(expected_value, payload, token)) { + failed_tests++; + printf("claim %s: value has mismatch: got %.*s, but %s expected\n", + claim, (int)(token->end - token->start), + &payload[token->start], expected_value); + return NULL; + } + + return token; +} + +static const jsmntok_t *jwt_check_claim(const struct jwt *jwt, + const char *claim, + jsmntype_t expected_type, + const char *expected_value) +{ + const jsmntok_t *token; + + total_tests++; + + token = jwt_get_claim(jwt, claim); + if (!token) { + failed_tests++; + printf("claim %s couldn't be located\n", claim); + return NULL; + } + + return check_token(token, claim, jwt_get_payload(jwt), + expected_type, expected_value); +} + +static const char jwt_rs256[] = + " eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9." + "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0." + "NHVaYe26MbtOYhSKkoKYdFVomg4i8ZJd8_-RU8VNbftc4TSMb4bXP3l3YlNWACwyXPGf" + "fz5aXHc6lty1Y2t4SWRqGteragsVdZufDn5BlnJl9pdR_kdVFUsra2rWKEofkZeIC4yW" + "ytE58sMIihvo9H1ScmmVwBcQP6XETqYd0aSHp1gOa9RdUPDvoXQ5oqygTqVtxaDr6wUF" + "KrKItgBMzWIdNZ6y7O9E0DhEPTbE9rfBo6KTFsHAZnMg4k68CDp2woYIaXbmYTWcvbzI" + "uHO7_37GT79XdIwkm95QJ7hYC9RiwrV7mesbY4PAahERJawntho0my942XheVLmGwLMBkQ\n \n"; + +static void test_jwt(void) +{ + char *jwt_rs256_mangled, *ch; + struct jwt_key jwt_key; + struct jwt *jwt; + extern const struct rsa_public_key __key_jwt_test; + int old_loglevel; + + jwt_key.alg = JWT_ALG_RS256; + jwt_key.material.rsa_pub = &__key_jwt_test; + total_tests++; + + jwt = jwt_decode(jwt_rs256, &jwt_key); + if (IS_ERR(jwt)) { + printf("failed to parse jwt\n"); + failed_tests++; + } else { + jwt_check_claim(jwt, "sub", JSMN_STRING, "1234567890"); + jwt_check_claim(jwt, "name", JSMN_STRING, "John Doe"); + jwt_check_claim(jwt, "admin", JSMN_PRIMITIVE, "true"); + jwt_check_claim(jwt, "iat", JSMN_PRIMITIVE, "1516239022"); + + jwt_free(jwt); + } + + /* + * Following tests intentionally fail and JWT failures are intentionally + * noisy, so we decrease logging a bit during their run + */ + + old_loglevel = barebox_loglevel; + barebox_loglevel = MSG_CRIT; + + jwt_rs256_mangled = strdup(jwt_rs256); + ch = &jwt_rs256_mangled[strlen(jwt_rs256_mangled) - 1]; + *ch = *ch == '_' ? '-' : '_'; + + total_tests++; + + jwt = jwt_decode(jwt_rs256_mangled, &jwt_key); + if (!IS_ERR(jwt)) { + printf("%s:%d expected JWT verification to fail\n", __func__, __LINE__); + failed_tests++; + jwt_free(jwt); + } + + free(jwt_rs256_mangled); + + jwt_rs256_mangled = strdup(jwt_rs256); + ch = &jwt_rs256_mangled[0]; + *ch = *ch == '_' ? '-' : '_'; + + total_tests++; + + jwt = jwt_decode(jwt_rs256_mangled, &jwt_key); + if (!IS_ERR(jwt)) { + printf("%s:%d expected JWT verification to fail\n", __func__, __LINE__); + failed_tests++; + jwt_free(jwt); + } + + free(jwt_rs256_mangled); + + total_tests++; + + jwt_key.alg = JWT_ALG_RS384; + + jwt = jwt_decode(jwt_rs256, &jwt_key); + if (!IS_ERR(jwt)) { + printf("%s:%d expected JWT verification to fail\n", __func__, __LINE__); + failed_tests++; + jwt_free(jwt); + } + + total_tests++; + + jwt_key.alg = JWT_ALG_NONE; + + jwt = jwt_decode(jwt_rs256, &jwt_key); + if (!IS_ERR(jwt)) { + printf("%s:%d expected JWT verification to fail\n", __func__, __LINE__); + failed_tests++; + jwt_free(jwt); + } + + barebox_loglevel = old_loglevel; +} +bselftest(parser, test_jwt); diff --git a/test/self/jwt_test.pem b/test/self/jwt_test.pem new file mode 100644 index 000000000000..349a5b6a47f0 --- /dev/null +++ b/test/self/jwt_test.pem @@ -0,0 +1,37 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1SU1LfVLPHCozMxH2Mo +4lgOEePzNm0tRgeLezV6ffAt0gunVTLw7onLRnrq0/IzW7yWR7QkrmBL7jTKEn5u ++qKhbwKfBstIs+bMY2Zkp18gnTxKLxoS2tFczGkPLPgizskuemMghRniWaoLcyeh +kd3qqGElvW/VDL5AaWTg0nLVkjRo9z+40RQzuVaE8AkAFmxZzow3x+VJYKdjykkJ +0iT9wCS0DRTXu269V264Vf/3jvredZiKRkgwlL9xNAwxXFg0x/XFw005UWVRIkdg +cKWTjpBP2dPwVZ4WWC+9aGVd+Gyn1o0CLelf4rEjGoXbAAEgAqeGUxrcIlbjXfbc +mwIDAQAB +-----END PUBLIC KEY----- +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC7VJTUt9Us8cKj +MzEfYyjiWA4R4/M2bS1GB4t7NXp98C3SC6dVMvDuictGeurT8jNbvJZHtCSuYEvu +NMoSfm76oqFvAp8Gy0iz5sxjZmSnXyCdPEovGhLa0VzMaQ8s+CLOyS56YyCFGeJZ +qgtzJ6GR3eqoYSW9b9UMvkBpZODSctWSNGj3P7jRFDO5VoTwCQAWbFnOjDfH5Ulg +p2PKSQnSJP3AJLQNFNe7br1XbrhV//eO+t51mIpGSDCUv3E0DDFcWDTH9cXDTTlR +ZVEiR2BwpZOOkE/Z0/BVnhZYL71oZV34bKfWjQIt6V/isSMahdsAASACp4ZTGtwi +VuNd9tybAgMBAAECggEBAKTmjaS6tkK8BlPXClTQ2vpz/N6uxDeS35mXpqasqskV +laAidgg/sWqpjXDbXr93otIMLlWsM+X0CqMDgSXKejLS2jx4GDjI1ZTXg++0AMJ8 +sJ74pWzVDOfmCEQ/7wXs3+cbnXhKriO8Z036q92Qc1+N87SI38nkGa0ABH9CN83H +mQqt4fB7UdHzuIRe/me2PGhIq5ZBzj6h3BpoPGzEP+x3l9YmK8t/1cN0pqI+dQwY +dgfGjackLu/2qH80MCF7IyQaseZUOJyKrCLtSD/Iixv/hzDEUPfOCjFDgTpzf3cw +ta8+oE4wHCo1iI1/4TlPkwmXx4qSXtmw4aQPz7IDQvECgYEA8KNThCO2gsC2I9PQ +DM/8Cw0O983WCDY+oi+7JPiNAJwv5DYBqEZB1QYdj06YD16XlC/HAZMsMku1na2T +N0driwenQQWzoev3g2S7gRDoS/FCJSI3jJ+kjgtaA7Qmzlgk1TxODN+G1H91HW7t +0l7VnL27IWyYo2qRRK3jzxqUiPUCgYEAx0oQs2reBQGMVZnApD1jeq7n4MvNLcPv +t8b/eU9iUv6Y4Mj0Suo/AU8lYZXm8ubbqAlwz2VSVunD2tOplHyMUrtCtObAfVDU +AhCndKaA9gApgfb3xw1IKbuQ1u4IF1FJl3VtumfQn//LiH1B3rXhcdyo3/vIttEk +48RakUKClU8CgYEAzV7W3COOlDDcQd935DdtKBFRAPRPAlspQUnzMi5eSHMD/ISL +DY5IiQHbIH83D4bvXq0X7qQoSBSNP7Dvv3HYuqMhf0DaegrlBuJllFVVq9qPVRnK +xt1Il2HgxOBvbhOT+9in1BzA+YJ99UzC85O0Qz06A+CmtHEy4aZ2kj5hHjECgYEA +mNS4+A8Fkss8Js1RieK2LniBxMgmYml3pfVLKGnzmng7H2+cwPLhPIzIuwytXywh +2bzbsYEfYx3EoEVgMEpPhoarQnYPukrJO4gwE2o5Te6T5mJSZGlQJQj9q4ZB2Dfz +et6INsK0oG8XVGXSpQvQh3RUYekCZQkBBFcpqWpbIEsCgYAnM3DQf3FJoSnXaMhr +VBIovic5l0xFkEHskAjFTevO86Fsz1C2aSeRKSqGFoOQ0tmJzBEs1R6KqnHInicD +TQrKhArgLXX4v3CddjfTRJkFWDbE/CkvKZNOrcf1nhaGCPspRJj2KUkj1Fhl9Cnc +dn/RsYEONbwQSjIfMPkvxF+8HQ== +-----END PRIVATE KEY----- diff --git a/test/self/jwt_test.pem.c_shipped b/test/self/jwt_test.pem.c_shipped new file mode 100644 index 000000000000..2142ae15dfb6 --- /dev/null +++ b/test/self/jwt_test.pem.c_shipped @@ -0,0 +1,49 @@ +#include <rsa.h> + +static uint32_t jwt_test_modulus[] = { + 0x5df6dc9b, 0xdc2256e3, 0xa786531a, 0x00012002, + 0x231a85db, 0xe95fe2b1, 0xd68d022d, 0x5df86ca7, + 0x2fbd6865, 0x559e1658, 0x4fd9d3f0, 0xa5938e90, + 0x22476070, 0x39516551, 0xf5c5c34d, 0x5c5834c7, + 0x71340c31, 0x483094bf, 0x75988a46, 0xf78efade, + 0x6eb855ff, 0xbb6ebd57, 0xb40d14d7, 0x24fdc024, + 0xca4909d2, 0x4960a763, 0x8c37c7e5, 0x166c59ce, + 0x84f00900, 0x1433b956, 0xf73fb8d1, 0xd5923468, + 0x64e0d272, 0x0cbe4069, 0x25bd6fd5, 0xddeaa861, + 0x7327a191, 0xe259aa0b, 0x63208519, 0xcec92e7a, + 0x0f2cf822, 0xd15ccc69, 0x2f1a12da, 0x209d3c4a, + 0x6664a75f, 0xb3e6cc63, 0x9f06cb48, 0xa2a16f02, + 0x127e6efa, 0x4bee34ca, 0xb424ae60, 0x5bbc9647, + 0xead3f233, 0x89cb467a, 0x5532f0ee, 0x2dd20ba7, + 0x357a7df0, 0x46078b7b, 0xf3366d2d, 0x580e11e3, + 0x1f6328e2, 0xc2a33331, 0xb7d52cf1, 0xbb5494d4, +}; + +static uint32_t jwt_test_rr[] = { + 0xec4954b7, 0x61f69199, 0x9e489481, 0x14f25ec8, + 0x712de1ab, 0x9c4ed93b, 0xcff16ec3, 0xb6e0c808, + 0x56551022, 0x1206f0dc, 0x72051e96, 0x6ab07919, + 0x8d29bea3, 0xa2a79109, 0x18a5e53d, 0x0a1ed2ae, + 0xae6544f4, 0x5fb16424, 0x5253250c, 0x3fc04654, + 0x9b9a3028, 0xf7219ed8, 0x8f9a7d60, 0x1020027e, + 0xa7bb0182, 0xca68b839, 0x86a507ca, 0x725d9efb, + 0xf43e09cd, 0xd373027e, 0x6c22f55c, 0x074bee70, + 0x49525052, 0x0506900e, 0xf51bde0d, 0xc8f82c0e, + 0x4a00d71e, 0x0a517ae2, 0x616e76fb, 0xb17b75d0, + 0x4bfcbb90, 0x3efd07cf, 0xaf30c7cb, 0xa18dee7f, + 0x02ed9615, 0x9185d985, 0x630a209e, 0xef23435c, + 0x46277653, 0x57d47ec5, 0x86e58fcf, 0x8f0ebe09, + 0x3b26c77e, 0xa3ef370d, 0xf83df63e, 0xa30a742e, + 0x49c2fb64, 0xea9fbed9, 0xb7471da7, 0x7a411345, + 0x303732ed, 0x6660f318, 0xe3a7df4c, 0x6a784bd5, +}; + +struct rsa_public_key __key_jwt_test; +struct rsa_public_key __key_jwt_test = { + .len = 64, + .n0inv = 0x17d8566d, + .modulus = jwt_test_modulus, + .rr = jwt_test_rr, + .exponent = 0x10001, + .key_name_hint = "jwt_test", +}; -- 2.39.2