From 93194f752a309eb4e1366c3dd9f9131eb2b888a4 Mon Sep 17 00:00:00 2001 From: Chris Mc Date: Tue, 16 Jan 2024 22:43:40 -0500 Subject: [PATCH 01/10] replace hmac impl to use BIGNUM --- include/jwt-cpp/jwt.h | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/include/jwt-cpp/jwt.h b/include/jwt-cpp/jwt.h index 6194d45b..291544dd 100644 --- a/include/jwt-cpp/jwt.h +++ b/include/jwt-cpp/jwt.h @@ -1132,13 +1132,14 @@ namespace jwt { struct hmacsha { /** * Construct new hmac algorithm - * + * + * \deprecated Using a character is not recommeded and hardened applications should use BIGNUM * \param key Key to use for HMAC * \param md Pointer to hash function * \param name Name of the algorithm */ hmacsha(std::string key, const EVP_MD* (*md)(), std::string name) - : secret(std::move(key)), md(md), alg_name(std::move(name)) {} + : secret(helper::raw2bn(key)), md(md), alg_name(std::move(name)) {} /** * Sign jwt data * @@ -1150,7 +1151,12 @@ namespace jwt { ec.clear(); std::string res(static_cast(EVP_MAX_MD_SIZE), '\0'); auto len = static_cast(res.size()); - if (HMAC(md(), secret.data(), static_cast(secret.size()), + + const int secret_size = BN_num_bytes(secret.get()); + std::vector buffer(size, '\0'); + BN_bn2bin(secret.get(), buffer.data()); + + if (HMAC(md(), secret.data(), secret_size, reinterpret_cast(data.data()), static_cast(data.size()), (unsigned char*)res.data(), // NOLINT(google-readability-casting) requires `const_cast` &len) == nullptr) { @@ -1190,7 +1196,7 @@ namespace jwt { private: /// HMAC secret - const std::string secret; + const std::unique_ptr secret; /// HMAC hash generator const EVP_MD* (*md)(); /// algorithm's name From 6400858642e752bc6d470ef16930575751df06ea Mon Sep 17 00:00:00 2001 From: Chris Mc Date: Tue, 16 Jan 2024 22:55:21 -0500 Subject: [PATCH 02/10] fix typo --- include/jwt-cpp/jwt.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/jwt-cpp/jwt.h b/include/jwt-cpp/jwt.h index 291544dd..d0ae3b27 100644 --- a/include/jwt-cpp/jwt.h +++ b/include/jwt-cpp/jwt.h @@ -1153,7 +1153,7 @@ namespace jwt { auto len = static_cast(res.size()); const int secret_size = BN_num_bytes(secret.get()); - std::vector buffer(size, '\0'); + std::vector buffer(size, '\0'); BN_bn2bin(secret.get(), buffer.data()); if (HMAC(md(), secret.data(), secret_size, From eeaa6c64d5453f9367ad71e7fd006afc3852b36b Mon Sep 17 00:00:00 2001 From: Chris Mc Date: Wed, 17 Jan 2024 08:16:09 -0500 Subject: [PATCH 03/10] Update jwt.h --- include/jwt-cpp/jwt.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/include/jwt-cpp/jwt.h b/include/jwt-cpp/jwt.h index d0ae3b27..e07b3334 100644 --- a/include/jwt-cpp/jwt.h +++ b/include/jwt-cpp/jwt.h @@ -1152,11 +1152,12 @@ namespace jwt { std::string res(static_cast(EVP_MAX_MD_SIZE), '\0'); auto len = static_cast(res.size()); - const int secret_size = BN_num_bytes(secret.get()); - std::vector buffer(size, '\0'); - BN_bn2bin(secret.get(), buffer.data()); + const BIGNUM* secret_bn = secret.get(); + std::vector buffer(BN_num_bytes(secret_bn), '\0'); + const auto buffer_size = BN_bn2bin(secret_bn, buffer.data()); + buffer.resize(buffer_size); - if (HMAC(md(), secret.data(), secret_size, + if (HMAC(md(), buffer.data(), buffer_size, reinterpret_cast(data.data()), static_cast(data.size()), (unsigned char*)res.data(), // NOLINT(google-readability-casting) requires `const_cast` &len) == nullptr) { From 730630d5d00c10473c2a370be1a8682eb8645eee Mon Sep 17 00:00:00 2001 From: Chris Mc Date: Tue, 23 Jan 2024 21:21:16 -0800 Subject: [PATCH 04/10] hmacsha needs to be copyable at ctor --- include/jwt-cpp/jwt.h | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/include/jwt-cpp/jwt.h b/include/jwt-cpp/jwt.h index e07b3334..35ed92fe 100644 --- a/include/jwt-cpp/jwt.h +++ b/include/jwt-cpp/jwt.h @@ -1140,6 +1140,18 @@ namespace jwt { */ hmacsha(std::string key, const EVP_MD* (*md)(), std::string name) : secret(helper::raw2bn(key)), md(md), alg_name(std::move(name)) {} + hmacsha(const hmacsha& other) : + secret(BN_dup(other.secret)), md(other.md), alg_name(other.alg_name) { + } + hmacsha(hmacsha&& other) : + secret(BN_copy(other.secret)), md(std::move(other.md)), alg_name(std::move(other.alg_name)) { + } + ~hmacsha(){ + BN_free(secret) + } + hmacsha& operator=(const hmacsha& other) = default; + hmacsha& operator=(hmacsha&& other) = default; + /** * Sign jwt data * @@ -1152,9 +1164,8 @@ namespace jwt { std::string res(static_cast(EVP_MAX_MD_SIZE), '\0'); auto len = static_cast(res.size()); - const BIGNUM* secret_bn = secret.get(); - std::vector buffer(BN_num_bytes(secret_bn), '\0'); - const auto buffer_size = BN_bn2bin(secret_bn, buffer.data()); + std::vector buffer(BN_num_bytes(secret), '\0'); + const auto buffer_size = BN_bn2bin(secret, buffer.data()); buffer.resize(buffer_size); if (HMAC(md(), buffer.data(), buffer_size, @@ -1197,7 +1208,7 @@ namespace jwt { private: /// HMAC secret - const std::unique_ptr secret; + const BIGNUM* secret; /// HMAC hash generator const EVP_MD* (*md)(); /// algorithm's name From 272892483a9aff70a64d9c89c838cd2c489f784d Mon Sep 17 00:00:00 2001 From: Chris Mc Date: Tue, 23 Jan 2024 21:50:10 -0800 Subject: [PATCH 05/10] touchups making copyable --- include/jwt-cpp/jwt.h | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/include/jwt-cpp/jwt.h b/include/jwt-cpp/jwt.h index 35ed92fe..63a07fb9 100644 --- a/include/jwt-cpp/jwt.h +++ b/include/jwt-cpp/jwt.h @@ -1139,18 +1139,18 @@ namespace jwt { * \param name Name of the algorithm */ hmacsha(std::string key, const EVP_MD* (*md)(), std::string name) - : secret(helper::raw2bn(key)), md(md), alg_name(std::move(name)) {} - hmacsha(const hmacsha& other) : - secret(BN_dup(other.secret)), md(other.md), alg_name(other.alg_name) { - } - hmacsha(hmacsha&& other) : - secret(BN_copy(other.secret)), md(std::move(other.md)), alg_name(std::move(other.alg_name)) { + : secret(helper::raw2bn(key).release()), md(md), alg_name(std::move(name)) {} + hmacsha(const hmacsha& other) + : secret(BN_dup(other.secret)), md(other.md), alg_name(other.alg_name) {} + hmacsha(hmacsha&& other) + : secret(nullptr), md(std::move(other.md)), alg_name(std::move(other.alg_name)) { + if(BN_copy(other.secret, secret) == nullptr) throw std::runtime_error("failed to copy BN"); } ~hmacsha(){ - BN_free(secret) + BN_free(secret); } - hmacsha& operator=(const hmacsha& other) = default; - hmacsha& operator=(hmacsha&& other) = default; + hmacsha& operator=(const hmacsha& other) = delete; + hmacsha& operator=(hmacsha&& other) = delete; /** * Sign jwt data @@ -1208,7 +1208,7 @@ namespace jwt { private: /// HMAC secret - const BIGNUM* secret; + BIGNUM* secret; /// HMAC hash generator const EVP_MD* (*md)(); /// algorithm's name From e3120bba956acc753608763ecb8db1d062d23a49 Mon Sep 17 00:00:00 2001 From: Chris Mc Date: Tue, 23 Jan 2024 21:55:14 -0800 Subject: [PATCH 06/10] linter --- include/jwt-cpp/jwt.h | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/include/jwt-cpp/jwt.h b/include/jwt-cpp/jwt.h index 63a07fb9..789ef913 100644 --- a/include/jwt-cpp/jwt.h +++ b/include/jwt-cpp/jwt.h @@ -1140,15 +1140,11 @@ namespace jwt { */ hmacsha(std::string key, const EVP_MD* (*md)(), std::string name) : secret(helper::raw2bn(key).release()), md(md), alg_name(std::move(name)) {} - hmacsha(const hmacsha& other) - : secret(BN_dup(other.secret)), md(other.md), alg_name(other.alg_name) {} - hmacsha(hmacsha&& other) - : secret(nullptr), md(std::move(other.md)), alg_name(std::move(other.alg_name)) { - if(BN_copy(other.secret, secret) == nullptr) throw std::runtime_error("failed to copy BN"); - } - ~hmacsha(){ - BN_free(secret); + hmacsha(const hmacsha& other) : secret(BN_dup(other.secret)), md(other.md), alg_name(other.alg_name) {} + hmacsha(hmacsha&& other) : secret(nullptr), md(std::move(other.md)), alg_name(std::move(other.alg_name)) { + if(BN_copy(other.secret, secret) == nullptr) throw std::runtime_error("failed to copy BN"); } + ~hmacsha(){ BN_free(secret); } hmacsha& operator=(const hmacsha& other) = delete; hmacsha& operator=(hmacsha&& other) = delete; @@ -1168,8 +1164,8 @@ namespace jwt { const auto buffer_size = BN_bn2bin(secret, buffer.data()); buffer.resize(buffer_size); - if (HMAC(md(), buffer.data(), buffer_size, - reinterpret_cast(data.data()), static_cast(data.size()), + if (HMAC(md(), buffer.data(), buffer_size, reinterpret_cast(data.data()), + static_cast(data.size()), (unsigned char*)res.data(), // NOLINT(google-readability-casting) requires `const_cast` &len) == nullptr) { ec = error::signature_generation_error::hmac_failed; From a047c214f6d91b9da0fe1d6240b6ce287eccd66e Mon Sep 17 00:00:00 2001 From: Chris Mc Date: Wed, 24 Jan 2024 19:31:25 -0800 Subject: [PATCH 07/10] linter --- include/jwt-cpp/jwt.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/jwt-cpp/jwt.h b/include/jwt-cpp/jwt.h index 789ef913..b5315ffd 100644 --- a/include/jwt-cpp/jwt.h +++ b/include/jwt-cpp/jwt.h @@ -1142,9 +1142,9 @@ namespace jwt { : secret(helper::raw2bn(key).release()), md(md), alg_name(std::move(name)) {} hmacsha(const hmacsha& other) : secret(BN_dup(other.secret)), md(other.md), alg_name(other.alg_name) {} hmacsha(hmacsha&& other) : secret(nullptr), md(std::move(other.md)), alg_name(std::move(other.alg_name)) { - if(BN_copy(other.secret, secret) == nullptr) throw std::runtime_error("failed to copy BN"); + if (BN_copy(other.secret, secret) == nullptr) throw std::runtime_error("failed to copy BN"); } - ~hmacsha(){ BN_free(secret); } + ~hmacsha() { BN_free(secret); } hmacsha& operator=(const hmacsha& other) = delete; hmacsha& operator=(hmacsha&& other) = delete; From 66bd2c6e86123a9b6137469e98b965aab283fc5b Mon Sep 17 00:00:00 2001 From: Chris Mc Date: Wed, 24 Jan 2024 21:26:11 -0800 Subject: [PATCH 08/10] linter --- include/jwt-cpp/jwt.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/jwt-cpp/jwt.h b/include/jwt-cpp/jwt.h index b5315ffd..370e018c 100644 --- a/include/jwt-cpp/jwt.h +++ b/include/jwt-cpp/jwt.h @@ -1147,7 +1147,6 @@ namespace jwt { ~hmacsha() { BN_free(secret); } hmacsha& operator=(const hmacsha& other) = delete; hmacsha& operator=(hmacsha&& other) = delete; - /** * Sign jwt data * From 7c4214cb556725d2d0c5ccb0aeccde0753e04dd2 Mon Sep 17 00:00:00 2001 From: Christopher McArthur Date: Mon, 8 Jul 2024 13:01:31 -0700 Subject: [PATCH 09/10] new tests and example for working with BIGNUM --- example/CMakeLists.txt | 3 +++ example/hs512.cpp | 60 ++++++++++++++++++++++++++++++++++++++++++ include/jwt-cpp/jwt.h | 36 +++++++++++++++++++++++-- tests/TokenTest.cpp | 26 ++++++++++++++++++ 4 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 example/hs512.cpp diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 3f87f424..6b328e89 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -34,5 +34,8 @@ add_custom_target(jwks-verify-run COMMAND jwks-verify) add_executable(es256k es256k.cpp) target_link_libraries(es256k jwt-cpp::jwt-cpp) +add_executable(hs512 hs512.cpp) +target_link_libraries(hs512 jwt-cpp::jwt-cpp) + add_executable(partial-claim-verifier partial-claim-verifier.cpp) target_link_libraries(partial-claim-verifier jwt-cpp::jwt-cpp nlohmann_json::nlohmann_json) diff --git a/example/hs512.cpp b/example/hs512.cpp new file mode 100644 index 00000000..c49b78d9 --- /dev/null +++ b/example/hs512.cpp @@ -0,0 +1,60 @@ +/// @file hs512.cpp +#include +#include +#include +#include + +BIGNUM* make_bn(); + +int main() { + BIGNUM* cipher = make_bn(); + + /* [use HMAC algo with BIGNUM] */ + auto token = jwt::create() + .set_issuer("auth0") + .set_type("JWT") + .set_id("hs512-create-example") + .set_issued_now() + .set_expires_in(std::chrono::seconds{36000}) + .set_payload_claim("sample", jwt::claim(std::string{"test"})) + .sign(jwt::algorithm::hs512(cipher)); + /* [use HMAC algo with BIGNUM] */ + + std::cout << "token:\n" << token << std::endl; + + auto verify = jwt::verify().allow_algorithm(jwt::algorithm::hs512(cipher)).with_issuer("auth0"); + + auto decoded = jwt::decode(token); + + verify.verify(decoded); + + for (auto& e : decoded.get_header_json()) + std::cout << e.first << " = " << e.second << std::endl; + for (auto& e : decoded.get_payload_json()) + std::cout << e.first << " = " << e.second << std::endl; + + BN_free(cipher); +} + +BIGNUM* make_bn() { + // https://stackoverflow.com/a/70790564 + unsigned char n1[64]; + unsigned char n2[64]; + + RAND_bytes(n1, sizeof n1); + RAND_bytes(n2, sizeof n2); + + BIGNUM* bn1 = BN_bin2bn(n1, sizeof n1, NULL); + BIGNUM* bn2 = BN_bin2bn(n2, sizeof n2, NULL); + BIGNUM* bn3 = BN_new(); + + // create context + BN_CTX* ctx = BN_CTX_new(); + BN_mul(bn3, bn1, bn2, ctx); + BN_CTX_free(ctx); + + BN_free(bn1); + BN_free(bn2); + + return bn3; +} diff --git a/include/jwt-cpp/jwt.h b/include/jwt-cpp/jwt.h index 370e018c..ae022598 100644 --- a/include/jwt-cpp/jwt.h +++ b/include/jwt-cpp/jwt.h @@ -1133,19 +1133,29 @@ namespace jwt { /** * Construct new hmac algorithm * - * \deprecated Using a character is not recommeded and hardened applications should use BIGNUM + * \deprecated Using a character is not recommended and hardened applications should use BIGNUM * \param key Key to use for HMAC * \param md Pointer to hash function * \param name Name of the algorithm */ hmacsha(std::string key, const EVP_MD* (*md)(), std::string name) : secret(helper::raw2bn(key).release()), md(md), alg_name(std::move(name)) {} + /** + * Construct new hmac algorithm + * + * \param key Key to use for HMAC + * \param md Pointer to hash function + * \param name Name of the algorithm + */ + hmacsha(const BIGNUM* key, const EVP_MD* (*md)(), std::string name) + : secret(BN_dup(key)), md(md), alg_name(std::move(name)) {} hmacsha(const hmacsha& other) : secret(BN_dup(other.secret)), md(other.md), alg_name(other.alg_name) {} hmacsha(hmacsha&& other) : secret(nullptr), md(std::move(other.md)), alg_name(std::move(other.alg_name)) { if (BN_copy(other.secret, secret) == nullptr) throw std::runtime_error("failed to copy BN"); + other.secret = nullptr; } ~hmacsha() { BN_free(secret); } - hmacsha& operator=(const hmacsha& other) = delete; + hmacsha& operator=(const hmacsha& other)= delete; hmacsha& operator=(hmacsha&& other) = delete; /** * Sign jwt data @@ -1800,9 +1810,15 @@ namespace jwt { struct hs256 : public hmacsha { /** * Construct new instance of algorithm + * \deprecated Using a character is not recommended and hardened applications should use BIGNUM * \param key HMAC signing key */ explicit hs256(std::string key) : hmacsha(std::move(key), EVP_sha256, "HS256") {} + /** + * Construct new instance of algorithm + * \param key HMAC signing key + */ + explicit hs256(const BIGNUM* key) : hmacsha(key, EVP_sha256, "HS256") {} }; /** * HS384 algorithm @@ -1810,9 +1826,15 @@ namespace jwt { struct hs384 : public hmacsha { /** * Construct new instance of algorithm + * \deprecated Using a character is not recommended and hardened applications should use BIGNUM * \param key HMAC signing key */ explicit hs384(std::string key) : hmacsha(std::move(key), EVP_sha384, "HS384") {} + /** + * Construct new instance of algorithm + * \param key HMAC signing key + */ + explicit hs384(const BIGNUM* key) : hmacsha(key, EVP_sha384, "HS384") {} }; /** * HS512 algorithm @@ -1820,9 +1842,19 @@ namespace jwt { struct hs512 : public hmacsha { /** * Construct new instance of algorithm + * \deprecated Using a character is not recommended and hardened applications should use BIGNUM * \param key HMAC signing key */ explicit hs512(std::string key) : hmacsha(std::move(key), EVP_sha512, "HS512") {} + /** + * Construct new instance of algorithm + * + * This can be used to sign and verify tokens. + * \snippet{trimleft} hs512.cpp use HMAC algo with BIGNUM + * + * \param key HMAC signing key + */ + explicit hs512(const BIGNUM* key) : hmacsha(key, EVP_sha512, "HS512") {} }; /** * RS256 algorithm. diff --git a/tests/TokenTest.cpp b/tests/TokenTest.cpp index 53574a6f..65dde222 100644 --- a/tests/TokenTest.cpp +++ b/tests/TokenTest.cpp @@ -59,6 +59,17 @@ TEST(TokenTest, CreateTokenHS256) { token); } +TEST(TokenTest, CreateTokenHS256Bytes) { + // https://stackoverflow.com/a/70790564 + const char bytes[] = "1234567891234578912345678912345678912345678912345789123456789123456789"; + BIGNUM *cipher = nullptr; + ASSERT_NE(0, BN_dec2bn(&cipher, bytes)); + ASSERT_NE(nullptr, cipher); + auto token = jwt::create().set_issuer("auth0").set_type("JWS").sign(jwt::algorithm::hs256{cipher}); + ASSERT_EQ("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.iXeab-Ef-S-JlVH5zxpqR4BIrz7DiUNH-0EljbYaf68", + token); +} + TEST(TokenTest, CreateTokenRS256) { auto token = jwt::create().set_issuer("auth0").set_type("JWS").sign( jwt::algorithm::rs256(rsa_pub_key, rsa_priv_key, "", "")); @@ -359,6 +370,21 @@ TEST(TokenTest, VerifyTokenHS256) { verify.verify(decoded_token); } +TEST(TokenTest, VerifyTokenHS256Bytes) { + std::string token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.iXeab-Ef-S-JlVH5zxpqR4BIrz7DiUNH-0EljbYaf68"; + + + const char bytes[] = "1234567891234578912345678912345678912345678912345789123456789123456789"; + BIGNUM *cipher = nullptr; + ASSERT_NE(0, BN_dec2bn(&cipher, bytes)); + ASSERT_NE(nullptr, cipher); + auto verify = jwt::verify().allow_algorithm(jwt::algorithm::hs256{cipher}).with_issuer("auth0"); + + auto decoded_token = jwt::decode(token); + verify.verify(decoded_token); +} + TEST(TokenTest, VerifyTokenHS256Fail) { std::string token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; From c77386732c05c5dfbbe3c18466621703944b0a5d Mon Sep 17 00:00:00 2001 From: Christopher McArthur Date: Mon, 8 Jul 2024 13:07:19 -0700 Subject: [PATCH 10/10] add examples to CI --- .github/workflows/jwt.yml | 4 ++++ example/CMakeLists.txt | 2 ++ 2 files changed, 6 insertions(+) diff --git a/.github/workflows/jwt.yml b/.github/workflows/jwt.yml index 46eed971..e7583574 100644 --- a/.github/workflows/jwt.yml +++ b/.github/workflows/jwt.yml @@ -72,6 +72,8 @@ jobs: cmake --build --preset ci-asan --target rsa-create-run cmake --build --preset ci-asan --target rsa-verify-run cmake --build --preset ci-asan --target jwks-verify-run + cmake --build --preset ci-asan --target es256k-run + cmake --build --preset ci-asan --target hs512-run cmake --build --preset ci-asan --target jwt-cpp-test-run ubsan: @@ -92,4 +94,6 @@ jobs: cmake --build --preset ci-ubsan --target rsa-create-run cmake --build --preset ci-ubsan --target rsa-verify-run cmake --build --preset ci-ubsan --target jwks-verify-run + cmake --build --preset ci-ubsan --target es256k-run + cmake --build --preset ci-ubsan --target hs512-run cmake --build --preset ci-ubsan --target jwt-cpp-test-run diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 6b328e89..97df29c8 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -33,9 +33,11 @@ add_custom_target(jwks-verify-run COMMAND jwks-verify) add_executable(es256k es256k.cpp) target_link_libraries(es256k jwt-cpp::jwt-cpp) +add_custom_target(es256k-run COMMAND es256k) add_executable(hs512 hs512.cpp) target_link_libraries(hs512 jwt-cpp::jwt-cpp) +add_custom_target(hs512-run COMMAND hs512) add_executable(partial-claim-verifier partial-claim-verifier.cpp) target_link_libraries(partial-claim-verifier jwt-cpp::jwt-cpp nlohmann_json::nlohmann_json)