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 3f87f424..97df29c8 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -33,6 +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) 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 6194d45b..ae022598 100644 --- a/include/jwt-cpp/jwt.h +++ b/include/jwt-cpp/jwt.h @@ -1132,13 +1132,31 @@ namespace jwt { struct hmacsha { /** * Construct new hmac algorithm - * + * + * \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(std::move(key)), md(md), alg_name(std::move(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=(hmacsha&& other) = delete; /** * Sign jwt data * @@ -1150,8 +1168,13 @@ 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()), - reinterpret_cast(data.data()), static_cast(data.size()), + + 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, 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; @@ -1190,7 +1213,7 @@ namespace jwt { private: /// HMAC secret - const std::string secret; + BIGNUM* secret; /// HMAC hash generator const EVP_MD* (*md)(); /// algorithm's name @@ -1787,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 @@ -1797,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 @@ -1807,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";