From acaf9c5d282e54beda5e72f3eac4cabc9f427b28 Mon Sep 17 00:00:00 2001 From: Marek Date: Wed, 23 Feb 2022 23:03:29 +0100 Subject: [PATCH 01/10] move jwk above verifier --- include/jwt-cpp/jwt.h | 404 +++++++++++++++++++++--------------------- 1 file changed, 199 insertions(+), 205 deletions(-) diff --git a/include/jwt-cpp/jwt.h b/include/jwt-cpp/jwt.h index 8cfefcc9..66c96506 100644 --- a/include/jwt-cpp/jwt.h +++ b/include/jwt-cpp/jwt.h @@ -3119,6 +3119,205 @@ namespace jwt { }; } // namespace verify_ops + /** + * \brief JSON Web Key + * + * https://tools.ietf.org/html/rfc7517 + * + * A JSON object that represents a cryptographic key. The members of + * the object represent properties of the key, including its value. + */ + template + class jwk { + using basic_claim_t = basic_claim; + const details::map_of_claims jwk_claims; + + public: + JWT_CLAIM_EXPLICIT jwk(const typename json_traits::string_type& str) + : jwk_claims(details::map_of_claims::parse_claims(str)) {} + + JWT_CLAIM_EXPLICIT jwk(const typename json_traits::value_type& json) + : jwk_claims(json_traits::as_object(json)) {} + + /** + * Get key type claim + * + * This returns the general type (e.g. RSA or EC), not a specific algorithm value. + * \return key type as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_key_type() const { return get_jwk_claim("kty").as_string(); } + + /** + * Get public key usage claim + * \return usage parameter as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_use() const { return get_jwk_claim("use").as_string(); } + + /** + * Get key operation types claim + * \return key operation types as a set of strings + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename basic_claim_t::set_t get_key_operations() const { return get_jwk_claim("key_ops").as_set(); } + + /** + * Get algorithm claim + * \return algorithm as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_algorithm() const { return get_jwk_claim("alg").as_string(); } + + /** + * Get key id claim + * \return key id as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_key_id() const { return get_jwk_claim("kid").as_string(); } + + /** + * \brief Get curve claim + * + * https://www.rfc-editor.org/rfc/rfc7518.html#section-6.2.1.1 + * https://www.iana.org/assignments/jose/jose.xhtml#table-web-key-elliptic-curve + * + * \return curve as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_curve() const { return get_jwk_claim("crv").as_string(); } + + /** + * Get x5c claim + * \return x5c as an array + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a array (Should not happen in a valid token) + */ + typename json_traits::array_type get_x5c() const { return get_jwk_claim("x5c").as_array(); }; + + /** + * Get X509 URL claim + * \return x5u as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_x5u() const { return get_jwk_claim("x5u").as_string(); }; + + /** + * Get X509 thumbprint claim + * \return x5t as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_x5t() const { return get_jwk_claim("x5t").as_string(); }; + + /** + * Get X509 SHA256 thumbprint claim + * \return x5t#S256 as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_x5t_sha256() const { return get_jwk_claim("x5t#S256").as_string(); }; + + /** + * Get x5c claim as a string + * \return x5c as an string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_x5c_key_value() const { + auto x5c_array = get_jwk_claim("x5c").as_array(); + if (x5c_array.size() == 0) throw error::claim_not_present_exception(); + + return json_traits::as_string(x5c_array.front()); + }; + + /** + * Check if a key type is present ("kty") + * \return true if present, false otherwise + */ + bool has_key_type() const noexcept { return has_jwk_claim("kty"); } + + /** + * Check if a public key usage indication is present ("use") + * \return true if present, false otherwise + */ + bool has_use() const noexcept { return has_jwk_claim("use"); } + + /** + * Check if a key operations parameter is present ("key_ops") + * \return true if present, false otherwise + */ + bool has_key_operations() const noexcept { return has_jwk_claim("key_ops"); } + + /** + * Check if algortihm is present ("alg") + * \return true if present, false otherwise + */ + bool has_algorithm() const noexcept { return has_jwk_claim("alg"); } + + /** + * Check if curve is present ("crv") + * \return true if present, false otherwise + */ + bool has_curve() const noexcept { return has_jwk_claim("crv"); } + + /** + * Check if key id is present ("kid") + * \return true if present, false otherwise + */ + bool has_key_id() const noexcept { return has_jwk_claim("kid"); } + + /** + * Check if X509 URL is present ("x5u") + * \return true if present, false otherwise + */ + bool has_x5u() const noexcept { return has_jwk_claim("x5u"); } + + /** + * Check if X509 Chain is present ("x5c") + * \return true if present, false otherwise + */ + bool has_x5c() const noexcept { return has_jwk_claim("x5c"); } + + /** + * Check if a X509 thumbprint is present ("x5t") + * \return true if present, false otherwise + */ + bool has_x5t() const noexcept { return has_jwk_claim("x5t"); } + + /** + * Check if a X509 SHA256 thumbprint is present ("x5t#S256") + * \return true if present, false otherwise + */ + bool has_x5t_sha256() const noexcept { return has_jwk_claim("x5t#S256"); } + + /** + * Check if a jwks claim is present + * \return true if claim was present, false otherwise + */ + bool has_jwk_claim(const typename json_traits::string_type& name) const noexcept { + return jwk_claims.has_claim(name); + } + + /** + * Get jwks claim + * \return Requested claim + * \throw std::runtime_error If claim was not present + */ + basic_claim_t get_jwk_claim(const typename json_traits::string_type& name) const { + return jwk_claims.get_claim(name); + } + + bool empty() const noexcept { return jwk_claims.empty(); } + }; + /** * Verifier class used to check if a decoded token contains all claims required by your application and has a valid * signature. @@ -3361,211 +3560,6 @@ namespace jwt { } }; - /** - * \brief JSON Web Key - * - * https://tools.ietf.org/html/rfc7517 - * - * A JSON object that represents a cryptographic key. The members of - * the object represent properties of the key, including its value. - */ - template - class jwk { - using basic_claim_t = basic_claim; - const details::map_of_claims jwk_claims; - - public: - JWT_CLAIM_EXPLICIT jwk(const typename json_traits::string_type& str) - : jwk_claims(details::map_of_claims::parse_claims(str)) {} - - JWT_CLAIM_EXPLICIT jwk(const typename json_traits::value_type& json) - : jwk_claims(json_traits::as_object(json)) {} - - /** - * Get key type claim - * - * This returns the general type (e.g. RSA or EC), not a specific algorithm value. - * \return key type as string - * \throw std::runtime_error If claim was not present - * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) - */ - typename json_traits::string_type get_key_type() const { return get_jwk_claim("kty").as_string(); } - - /** - * Get public key usage claim - * \return usage parameter as string - * \throw std::runtime_error If claim was not present - * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) - */ - typename json_traits::string_type get_use() const { return get_jwk_claim("use").as_string(); } - - /** - * Get key operation types claim - * \return key operation types as a set of strings - * \throw std::runtime_error If claim was not present - * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) - */ - typename basic_claim_t::set_t get_key_operations() const { return get_jwk_claim("key_ops").as_set(); } - - /** - * Get algorithm claim - * \return algorithm as string - * \throw std::runtime_error If claim was not present - * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) - */ - typename json_traits::string_type get_algorithm() const { return get_jwk_claim("alg").as_string(); } - - /** - * Get key id claim - * \return key id as string - * \throw std::runtime_error If claim was not present - * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) - */ - typename json_traits::string_type get_key_id() const { return get_jwk_claim("kid").as_string(); } - - /** - * \brief Get curve claim - * - * https://www.rfc-editor.org/rfc/rfc7518.html#section-6.2.1.1 - * https://www.iana.org/assignments/jose/jose.xhtml#table-web-key-elliptic-curve - * - * \return curve as string - * \throw std::runtime_error If claim was not present - * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) - */ - typename json_traits::string_type get_curve() const { return get_jwk_claim("crv").as_string(); } - - /** - * Get x5c claim - * \return x5c as an array - * \throw std::runtime_error If claim was not present - * \throw std::bad_cast Claim was present but not a array (Should not happen in a valid token) - */ - typename json_traits::array_type get_x5c() const { return get_jwk_claim("x5c").as_array(); }; - - /** - * Get X509 URL claim - * \return x5u as string - * \throw std::runtime_error If claim was not present - * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) - */ - typename json_traits::string_type get_x5u() const { return get_jwk_claim("x5u").as_string(); }; - - /** - * Get X509 thumbprint claim - * \return x5t as string - * \throw std::runtime_error If claim was not present - * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) - */ - typename json_traits::string_type get_x5t() const { return get_jwk_claim("x5t").as_string(); }; - - /** - * Get X509 SHA256 thumbprint claim - * \return x5t#S256 as string - * \throw std::runtime_error If claim was not present - * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) - */ - typename json_traits::string_type get_x5t_sha256() const { return get_jwk_claim("x5t#S256").as_string(); }; - - /** - * Get x5c claim as a string - * \return x5c as an string - * \throw std::runtime_error If claim was not present - * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) - */ - typename json_traits::string_type get_x5c_key_value() const { - auto x5c_array = get_jwk_claim("x5c").as_array(); - if (x5c_array.size() == 0) throw error::claim_not_present_exception(); - - return json_traits::as_string(x5c_array.front()); - }; - - /** - * Check if a key type is present ("kty") - * \return true if present, false otherwise - */ - bool has_key_type() const noexcept { return has_jwk_claim("kty"); } - - /** - * Check if a public key usage indication is present ("use") - * \return true if present, false otherwise - */ - bool has_use() const noexcept { return has_jwk_claim("use"); } - - /** - * Check if a key operations parameter is present ("key_ops") - * \return true if present, false otherwise - */ - bool has_key_operations() const noexcept { return has_jwk_claim("key_ops"); } - - /** - * Check if algortihm is present ("alg") - * \return true if present, false otherwise - */ - bool has_algorithm() const noexcept { return has_jwk_claim("alg"); } - - /** - * Check if curve is present ("crv") - * \return true if present, false otherwise - */ - bool has_curve() const noexcept { return has_jwk_claim("crv"); } - - /** - * Check if key id is present ("kid") - * \return true if present, false otherwise - */ - bool has_key_id() const noexcept { return has_jwk_claim("kid"); } - - /** - * Check if X509 URL is present ("x5u") - * \return true if present, false otherwise - */ - bool has_x5u() const noexcept { return has_jwk_claim("x5u"); } - - /** - * Check if X509 Chain is present ("x5c") - * \return true if present, false otherwise - */ - bool has_x5c() const noexcept { return has_jwk_claim("x5c"); } - - /** - * Check if a X509 thumbprint is present ("x5t") - * \return true if present, false otherwise - */ - bool has_x5t() const noexcept { return has_jwk_claim("x5t"); } - - /** - * Check if a X509 SHA256 thumbprint is present ("x5t#S256") - * \return true if present, false otherwise - */ - bool has_x5t_sha256() const noexcept { return has_jwk_claim("x5t#S256"); } - - /** - * Check if a jwks claim is present - * \return true if claim was present, false otherwise - */ - bool has_jwk_claim(const typename json_traits::string_type& name) const noexcept { - return jwk_claims.has_claim(name); - } - - /** - * Get jwks claim - * \return Requested claim - * \throw std::runtime_error If claim was not present - */ - basic_claim_t get_jwk_claim(const typename json_traits::string_type& name) const { - return jwk_claims.get_claim(name); - } - - bool empty() const noexcept { return jwk_claims.empty(); } - - /** - * Get all jwk claims - * \return Map of claims - */ - typename json_traits::object_type get_claims() const { return this->jwk_claims.claims; } - }; - /** * \brief JWK Set * From 89b4e8a0c88bd2e7df7481dcbb9955e387ccc822 Mon Sep 17 00:00:00 2001 From: Marek Date: Wed, 23 Feb 2022 22:38:40 +0100 Subject: [PATCH 02/10] validate claims in separate method --- include/jwt-cpp/jwt.h | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/include/jwt-cpp/jwt.h b/include/jwt-cpp/jwt.h index 66c96506..169d8958 100644 --- a/include/jwt-cpp/jwt.h +++ b/include/jwt-cpp/jwt.h @@ -3360,6 +3360,15 @@ namespace jwt { /// Supported algorithms std::unordered_map> algs; + void verify_claims(const decoded_jwt& jwt, std::error_code& ec) const { + verify_ops::verify_context ctx{clock.now(), jwt, default_leeway}; + for (auto& c : claims) { + ctx.claim_key = c.first; + c.second(ctx, ec); + if (ec) return; + } + } + public: /** * Constructor for building a new verifier instance @@ -3551,12 +3560,7 @@ namespace jwt { algs.at(algo)->verify(data, sig, ec); if (ec) return; - verify_ops::verify_context ctx{clock.now(), jwt, default_leeway}; - for (auto& c : claims) { - ctx.claim_key = c.first; - c.second(ctx, ec); - if (ec) return; - } + verify_claims(jwt, ec); } }; From 0cadf3d8dc7cd7b679d5b719385765a881be8775 Mon Sep 17 00:00:00 2001 From: Marek Date: Tue, 1 Feb 2022 23:05:10 +0100 Subject: [PATCH 03/10] read rsa public key from JWK --- include/jwt-cpp/jwt.h | 134 +++++++++++++++++++++++++++++++++++++++++- tests/CMakeLists.txt | 3 +- tests/JwkTest.cpp | 63 ++++++++++++++++++++ 3 files changed, 196 insertions(+), 4 deletions(-) create mode 100644 tests/JwkTest.cpp diff --git a/include/jwt-cpp/jwt.h b/include/jwt-cpp/jwt.h index 169d8958..c60d9474 100644 --- a/include/jwt-cpp/jwt.h +++ b/include/jwt-cpp/jwt.h @@ -73,6 +73,10 @@ #define JWT_CLAIM_EXPLICIT explicit #endif +#ifdef JWT_OPENSSL_3_0 +#include +#endif + /** * \brief JSON Web Token * @@ -976,6 +980,9 @@ namespace jwt { } else throw rsa_exception(error::rsa_error::no_key_provided); } + + rsa(helper::evp_pkey_handle pkey, const EVP_MD* (*md)(), std::string name) + : pkey(pkey), md(md), alg_name(std::move(name)) {} /** * Sign jwt data * \param data The data to sign @@ -3134,10 +3141,28 @@ namespace jwt { public: JWT_CLAIM_EXPLICIT jwk(const typename json_traits::string_type& str) - : jwk_claims(details::map_of_claims::parse_claims(str)) {} + : jwk(details::map_of_claims::parse_claims(str)) {} + + JWT_CLAIM_EXPLICIT jwk(const typename json_traits::value_type& json) : jwk(json_traits::as_object(json)) {} + + JWT_CLAIM_EXPLICIT jwk(const typename json_traits::object_type& json) + : jwk_claims(json), key(build_key(jwk_claims)) { + // https://datatracker.ietf.org/doc/html/rfc7518#section-6.1 + // * indicate required params + // "kty"* : "EC", "RSA", "oct" - JWT_CLAIM_EXPLICIT jwk(const typename json_traits::value_type& json) - : jwk_claims(json_traits::as_object(json)) {} + // if "EC", then "crv"*, then "x"*. if "crv" is any of "P-256", "P-384", "P-521", then "y"* + // if "EC" and private key, then "d"* + + // if "RSA", then "n"*, "e"* + // if "RSA" and private, then "d"* + // if "RSA" and any of the following is present, then all must be present + // "p", "q", "dp", "dq", "qi" + // "oth" - array of objects consisting of "r"*, "d"*, "t"* + + // if "oct", then "k"* + // if "oct", then SHOULD contain "alg" + } /** * Get key type claim @@ -3316,6 +3341,109 @@ namespace jwt { } bool empty() const noexcept { return jwk_claims.empty(); } + + helper::evp_pkey_handle get_pkey() const { return key.get_asymmetric_key(); } + + std::string get_oct_key() const { return key.get_symmetric_key(); } + + private: + class key { + public: + static key symmetric(const std::string& bytes) { return key(bytes); } + + static key asymmetric(helper::evp_pkey_handle pkey) { return key(pkey); } + + std::string get_symmetric_key() const { + if (!is_symmetric) { throw std::logic_error("not a symmetric key"); } + + return oct_key; + } + + helper::evp_pkey_handle get_asymmetric_key() const { + if (is_symmetric) { throw std::logic_error("not an asymmetric key"); } + + return pkey; + } + + private: + key(const std::string& key) { + is_symmetric = true; + oct_key = key; + } + + key(helper::evp_pkey_handle key) { + is_symmetric = false; + pkey = key; + } + + bool is_symmetric; + helper::evp_pkey_handle pkey; + std::string oct_key; + }; + + static helper::evp_pkey_handle build_rsa_key(const details::map_of_claims& claims) { + EVP_PKEY* evp_key = nullptr; + auto n = jwt::helper::raw2bn( + base::decode(base::pad(claims.get_claim("n").as_string()))); + auto e = jwt::helper::raw2bn( + base::decode(base::pad(claims.get_claim("e").as_string()))); +#ifdef JWT_OPENSSL_3_0 + // https://www.openssl.org/docs/manmaster/man7/EVP_PKEY-RSA.html + // see https://www.openssl.org/docs/man3.0/man3/EVP_PKEY_fromdata.html + // and https://stackoverflow.com/questions/68465716/how-to-properly-create-an-rsa-key-from-raw-data-in-openssl-3-0-in-c-language + std::unique_ptr ctx( + EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL), EVP_PKEY_CTX_free); + if (!ctx) { throw std::runtime_error("EVP_PKEY_CTX_new_from_name failed"); } + + std::unique_ptr params_build(OSSL_PARAM_BLD_new(), + OSSL_PARAM_BLD_free); + OSSL_PARAM_BLD_push_BN(params_build.get(), "n", n.get()); + OSSL_PARAM_BLD_push_BN(params_build.get(), "e", e.get()); + + std::unique_ptr params(OSSL_PARAM_BLD_to_param(params_build.get()), + OSSL_PARAM_free); + EVP_PKEY_fromdata_init(ctx.get()); + EVP_PKEY_fromdata(ctx.get(), &evp_key, EVP_PKEY_PUBLIC_KEY, params.get()); + return helper::evp_pkey_handle(evp_key); +#else + RSA* rsa = RSA_new(); + evp_key = EVP_PKEY_new(); +#if defined(JWT_OPENSSL_1_0_0) && !defined(LIBWOLFSSL_VERSION_HEX) + rsa->e = e.release(); + rsa->n = n.release(); +#else + RSA_set0_key(rsa, n.release(), e.release(), nullptr); +#endif + EVP_PKEY_assign_RSA(evp_key, rsa); + return helper::evp_pkey_handle(evp_key); +#endif + } + + static key build_key(const details::map_of_claims& claims) { + if (!claims.has_claim("kty")) { + // TODO: custom exception or error code + throw std::runtime_error("missing required claim \"kty\""); + } + + if (claims.get_claim("kty").get_type() != json::type::string) { + // TODO: custom exception or error code + throw std::runtime_error("\"kty\" claim must be of type 'string'"); + } + + if (claims.get_claim("kty").as_string() == "RSA") { + return key::asymmetric(build_rsa_key(claims)); + } else if (claims.get_claim("kty").as_string() == "EC") { + // TODO: build EC key + throw std::runtime_error("not implemented"); + } else if (claims.get_claim("kty").as_string() == "oct") { + return key::symmetric(base::decode(claims.get_claim("k").as_string())); + } else { + // TODO: do not build error messages like this + throw std::runtime_error("unknown key type (\"kty\"):" + claims.get_claim("kty").as_string()); + } + } + + key key; }; /** diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5599aa2b..e0122beb 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -18,7 +18,8 @@ set(TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/Keys.cpp ${CMAKE_CURRENT_SOURCE_DIR}/HelperTest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/TestMain.cpp ${CMAKE_CURRENT_SOURCE_DIR}/TokenFormatTest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/TokenTest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/JwksTest.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/OpenSSLErrorTest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/traits/NlohmannTest.cpp) + ${CMAKE_CURRENT_SOURCE_DIR}/OpenSSLErrorTest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/traits/NlohmannTest.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/JwkTest.cpp) find_package(jsoncons CONFIG) if(TARGET jsoncons) diff --git a/tests/JwkTest.cpp b/tests/JwkTest.cpp new file mode 100644 index 00000000..c1e79cc8 --- /dev/null +++ b/tests/JwkTest.cpp @@ -0,0 +1,63 @@ +#include "jwt-cpp/jwt.h" +#include + +/* +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA2KvGTTxorqX2rvLOASWcBOqevGo5eIFFUVSlQIWe8x/a19mT +XbYz6l3FYZ0s2W172nBpzJggi6PLK3397JsjjrzXb1pgXCVjIjM4hs5HDNtoWmC3 +/+O1jWknfrUHS95KWJkKg77B3ShGO1n8drfXCL4lWdo8F3UtVjHgfYEHI1xLSv3n +v8QfGpOnWh4MAdmXhcankZQNuip6pwKlF+3y2WfukHN3rAmMx1MyID77srgPW7A3 +f0FTiGUz5gfUdp+5MMSQbhSZTM9fXu0buU4cmL35W9GoExjfjXlixpl70HWdyeLY +SNyZKaw/Tqjwq769xf5sJZBe5UP6hL/dbLYhvQIDAQABAoIBACtorsAGnEpxQazn +RFKCgHGTt92zwnPcIlEbDkiQ/Llk5mlcU+PwfxIzWzolTTj6cFfhMbElwU94r1m1 +Ukw3ALa2KstKZgfQDb5qWKbZaO6wfoWs3vBLZLJCIQGHr0CJ9octkie27gwq53c4 +nhYC2vgLcFxCFsv0U/Ly5zD9yrpQgv3DElKbc2zal/Z+kBt9MAN+2S4Fh2LaEUUl +8QXjxdxbe3PHvX4nO5TWM3ztcfANzPDAWFJDeOgciUK6wEqTxkmgjh4uMaDG5X3V +5xQRLBnFVXYzdwAVjzJVk9RvIDSQnEgYyHLBX6d190F85G8zQMVEwvs2VD1qJO+0 +BppwloECgYEA7T2xC7xGtxn/Tg9lhItZzE371mTffZDNbhL2YDQt6jBHq40cmmBi +MzAYhV0Z7nky3bVHQUdaDLnJYIsqIrqqxGUZjcnkajhsSd1YGBwTHAv1njr+BX9a +zY15u/pNb+OYY6naFHuTmen/NKSha+s+kHmQGCEKzErhfZ4yXhrPETECgYEA6c2y +3iojU/P73RyUcoWQnDdOuQ8YNMAqoGwh/FzkG9futAiyhB3mGPmn7nw79rIO02Og +Rjk7t1qSSL5DX1oAP3Fq/G1HE9epgM4j82Sa9vpUZpoLBefD2wUDjD4QmzKcFuYv +M/Wl6dMLURllL24IdsEctR1p76Y7Spm251k/1k0CgYBpMxMAFjPxW6jXb4JfvP9L +1kTXNBHad0xxBB2WWW0GzPPrAX7ugdDpy+kDsl4eXkYNBCadrssim3vNwMglcErr +Hb2wHxeXdn+mXW2D+2cJ58+5o4Ui4O9d+N9DWOHfvLfFcfsPXCD+fkG5kUs3NLCg +lhcsa/KC1q2Y636ANjkd8QKBgQCP882AilNMGnnljvY7eM8rz8XRnWCbAgJ82Xcn +aY4tMotPH9fCDqKgl/50kNterg0AzGNfOVfyMXrF/ReAOurSJSPpHeNYbT15B/MM +pdHf5QtYTNoinatyS6j+jSwuUj/WvY0sob+wsvdRzKAHTuk5LPde8ChMnH3/FZuO +3921NQKBgQCSJf1kVFuxoWtdxqII0QJv6jSMaftK5xNkWCiLpmdttDpOdLRnMrYb +XXkgbME3NheApU1oXIehLyXG46DmFXCNKME98NuJ5ENvLVkOsGyPhZDtQtQT099J +z8gE19JF3RSmwvcaNpsLRg24BId/GmrZKgz9TEQMm+5wt93XcKtj6w== +-----END RSA PRIVATE KEY----- +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2KvGTTxorqX2rvLOASWc +BOqevGo5eIFFUVSlQIWe8x/a19mTXbYz6l3FYZ0s2W172nBpzJggi6PLK3397Jsj +jrzXb1pgXCVjIjM4hs5HDNtoWmC3/+O1jWknfrUHS95KWJkKg77B3ShGO1n8drfX +CL4lWdo8F3UtVjHgfYEHI1xLSv3nv8QfGpOnWh4MAdmXhcankZQNuip6pwKlF+3y +2WfukHN3rAmMx1MyID77srgPW7A3f0FTiGUz5gfUdp+5MMSQbhSZTM9fXu0buU4c +mL35W9GoExjfjXlixpl70HWdyeLYSNyZKaw/Tqjwq769xf5sJZBe5UP6hL/dbLYh +vQIDAQAB +-----END PUBLIC KEY----- +*/ + +TEST(JwkTest, ParseKey) { + std::string token = + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.IzM0dgbhU1CsRbjmwyPHXkc8LagqFtsZD6p1ls_" + "WBugkEKNfFmZmhOM1YYiFg59xId_KtzNdp4puzGIafut15U06DL2ZGH_H4xE7ONy6WLA_i5z5H8gPxD3ui2W4nHEEf-mvqKSn-" + "bU8YPUydrwK3dVRfP5JA9XJT0KhssSCnty99y853xvuTh0484atxMjIk2LvnIWlYXFgoggC8TMY-4AtAJDfF8aVJXT0m-" + "90oNevJbxMsuf5XFKo30TWxlnRw-y-QsYr9pxj2sA0BdwqRKVRRg5KF-" + "p6rIEbAv3A6UuzLORvtixp5AASS7nrBlZ1BB8q2hYFCPtOv6UETIIkaQ"; + std::string public_key = R"({ + "kty": "RSA", + "n": "2KvGTTxorqX2rvLOASWcBOqevGo5eIFFUVSlQIWe8x_a19mTXbYz6l3FYZ0s2W172nBpzJggi6PLK3397JsjjrzXb1pgXCVjIjM4hs5HDNtoWmC3_-O1jWknfrUHS95KWJkKg77B3ShGO1n8drfXCL4lWdo8F3UtVjHgfYEHI1xLSv3nv8QfGpOnWh4MAdmXhcankZQNuip6pwKlF-3y2WfukHN3rAmMx1MyID77srgPW7A3f0FTiGUz5gfUdp-5MMSQbhSZTM9fXu0buU4cmL35W9GoExjfjXlixpl70HWdyeLYSNyZKaw_Tqjwq769xf5sJZBe5UP6hL_dbLYhvQ", + "e": "AQAB" + })"; + + auto jwk = jwt::parse_jwk(public_key); + ASSERT_EQ("RSA", jwk.get_key_type()); + auto alg = jwt::algorithm::rsa(jwk.get_pkey(), EVP_sha256, "RS256"); + auto verify = jwt::verify(); + auto decoded_token = jwt::decode(token); + + ASSERT_NO_THROW(verify.do_verify(jwk, decoded_token)); +} From 7a735e70f3af41e2a9b8dc9d26eb4812cc68bd6c Mon Sep 17 00:00:00 2001 From: Marek Date: Thu, 3 Mar 2022 22:59:15 +0100 Subject: [PATCH 04/10] add allow_key interface --- include/jwt-cpp/jwt.h | 109 ++++++++++++++++++++++++++++++++++++++++-- tests/JwkTest.cpp | 23 +++++++-- 2 files changed, 123 insertions(+), 9 deletions(-) diff --git a/include/jwt-cpp/jwt.h b/include/jwt-cpp/jwt.h index c60d9474..16e2453e 100644 --- a/include/jwt-cpp/jwt.h +++ b/include/jwt-cpp/jwt.h @@ -1617,6 +1617,8 @@ namespace jwt { explicit rs256(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") : rsa(public_key, private_key, public_key_password, private_key_password, EVP_sha256, "RS256") {} + + explicit rs256(helper::evp_pkey_handle pkey) : rsa(pkey, EVP_sha256, "RS256") {} }; /** * RS384 algorithm @@ -1632,6 +1634,8 @@ namespace jwt { explicit rs384(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") : rsa(public_key, private_key, public_key_password, private_key_password, EVP_sha384, "RS384") {} + + explicit rs384(helper::evp_pkey_handle pkey) : rsa(pkey, EVP_sha384, "RS384") {} }; /** * RS512 algorithm @@ -1647,6 +1651,8 @@ namespace jwt { explicit rs512(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") : rsa(public_key, private_key, public_key_password, private_key_password, EVP_sha512, "RS512") {} + + explicit rs512(helper::evp_pkey_handle pkey) : rsa(pkey, EVP_sha512, "RS512") {} }; /** * ES256 algorithm @@ -3126,6 +3132,12 @@ namespace jwt { }; } // namespace verify_ops + using alg_name = std::string; + using alg_list = std::vector; + using algorithms = std::unordered_map; + static const algorithms supported_alg = {{"RSA", {"RS256", "RS384", "RS512", "PS256", "PS384", "PS512"}}, + {"EC", {"ES256", "ES384", "ES512", "ES256K"}}, + {"oct", {"HS256", "HS384", "HS512"}}}; /** * \brief JSON Web Key * @@ -3346,6 +3358,11 @@ namespace jwt { std::string get_oct_key() const { return key.get_symmetric_key(); } + bool supports(const std::string& alg_name) const { + const alg_list& x = supported_alg.find(get_key_type())->second; + return std::find(x.begin(), x.end(), alg_name) != x.end(); + } + private: class key { public: @@ -3488,6 +3505,11 @@ namespace jwt { /// Supported algorithms std::unordered_map> algs; + typedef std::vector> key_list; + /// https://datatracker.ietf.org/doc/html/rfc7517#section-4.5 - kid to keys + typedef std::unordered_map keysets; + keysets keys; + void verify_claims(const decoded_jwt& jwt, std::error_code& ec) const { verify_ops::verify_context ctx{clock.now(), jwt, default_leeway}; for (auto& c : claims) { @@ -3497,6 +3519,52 @@ namespace jwt { } } + static inline std::unique_ptr from_key_and_alg(const jwt::jwk& key, + const std::string& alg_name, std::error_code& ec) { + ec.clear(); + algorithms::const_iterator it = supported_alg.find(key.get_key_type()); + if (it == supported_alg.end()) { + ec = error::token_verification_error::wrong_algorithm; + return nullptr; + } + + const alg_list& supported_jwt_algorithms = it->second; + if (std::find(supported_jwt_algorithms.begin(), supported_jwt_algorithms.end(), alg_name) == + supported_jwt_algorithms.end()) { + ec = error::token_verification_error::wrong_algorithm; + return nullptr; + } + + if (alg_name == "RS256") { + return std::make_unique>(jwt::algorithm::rs256(key.get_pkey())); + } else if (alg_name == "RS384") { + return std::make_unique>(jwt::algorithm::rs384(key.get_pkey())); + } else if (alg_name == "RS512") { + return std::make_unique>(jwt::algorithm::rs512(key.get_pkey())); + } else if (alg_name == "PS256") { + return std::make_unique>(jwt::algorithm::ps256(key.get_pkey())); + } else if (alg_name == "PS384") { + return std::make_unique>(jwt::algorithm::ps384(key.get_pkey())); + } else if (alg_name == "PS512") { + return std::make_unique>(jwt::algorithm::ps512(key.get_pkey())); + } else if (alg_name == "ES256") { + return std::make_unique>(jwt::algorithm::es256(key.get_pkey())); + } else if (alg_name == "ES384") { + return std::make_unique>(jwt::algorithm::es384(key.get_pkey())); + } else if (alg_name == "ES512") { + return std::make_unique>(jwt::algorithm::es512(key.get_pkey())); + } else if (alg_name == "HS256") { + return std::make_unique>(jwt::algorithm::hs256(key.get_oct_key())); + } else if (alg_name == "HS384") { + return std::make_unique>(jwt::algorithm::hs384(key.get_oct_key())); + } else if (alg_name == "HS512") { + return std::make_unique>(jwt::algorithm::hs512(key.get_oct_key())); + } + + ec = error::token_verification_error::wrong_algorithm; + return nullptr; + } + public: /** * Constructor for building a new verifier instance @@ -3661,6 +3729,18 @@ namespace jwt { return *this; } + verifier& allow_key(const jwt::jwk& key) { + std::string keyid = ""; + if (key.has_key_id()) { + keyid = key.get_key_id(); + auto it = keys.find(keyid); + if (it == keys.end()) { keys[keyid] = key_list(); } + } + + keys[keyid].push_back(key); + return *this; + } + /** * Verify the given token. * \param jwt Token to check @@ -3681,13 +3761,32 @@ namespace jwt { const typename json_traits::string_type data = jwt.get_header_base64() + "." + jwt.get_payload_base64(); const typename json_traits::string_type sig = jwt.get_signature(); const std::string algo = jwt.get_algorithm(); - if (algs.count(algo) == 0) { - ec = error::token_verification_error::wrong_algorithm; - return; + std::string kid(""); + if (jwt.has_header_claim("kid")) { kid = jwt.get_header_claim("kid").as_string(); } + + typename keysets::const_iterator key_set_it = keys.find(kid); + bool key_found = false; + if (key_set_it != keys.end()) { + const key_list& keys = key_set_it->second; + for (const auto& key : keys) { + if (key.supports(algo)) { + key_found = true; + auto alg = from_key_and_alg(key, algo, ec); + alg->verify(data, sig, ec); + break; + } + } + } + + if (!key_found) { + if (algs.count(algo) == 0) { + ec = error::token_verification_error::wrong_algorithm; + return; + } + algs.at(algo)->verify(data, sig, ec); } - algs.at(algo)->verify(data, sig, ec); - if (ec) return; + if (ec) return; verify_claims(jwt, ec); } }; diff --git a/tests/JwkTest.cpp b/tests/JwkTest.cpp index c1e79cc8..37e7fd62 100644 --- a/tests/JwkTest.cpp +++ b/tests/JwkTest.cpp @@ -40,7 +40,7 @@ vQIDAQAB -----END PUBLIC KEY----- */ -TEST(JwkTest, ParseKey) { +TEST(JwkTest, RsaKey) { std::string token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.IzM0dgbhU1CsRbjmwyPHXkc8LagqFtsZD6p1ls_" "WBugkEKNfFmZmhOM1YYiFg59xId_KtzNdp4puzGIafut15U06DL2ZGH_H4xE7ONy6WLA_i5z5H8gPxD3ui2W4nHEEf-mvqKSn-" @@ -55,9 +55,24 @@ TEST(JwkTest, ParseKey) { auto jwk = jwt::parse_jwk(public_key); ASSERT_EQ("RSA", jwk.get_key_type()); - auto alg = jwt::algorithm::rsa(jwk.get_pkey(), EVP_sha256, "RS256"); - auto verify = jwt::verify(); + auto verifier = jwt::verify(); + verifier.allow_key(jwk); auto decoded_token = jwt::decode(token); + ASSERT_NO_THROW(verifier.verify(decoded_token)); +} - ASSERT_NO_THROW(verify.do_verify(jwk, decoded_token)); +TEST(JwkTest, HmacKey) { + std::string token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; + std::string secret_key = R"({ + "kty": "oct", + "k": "c2VjcmV0" + })"; + + auto jwk = jwt::parse_jwk(secret_key); + ASSERT_EQ("oct", jwk.get_key_type()); + auto verifier = jwt::verify(); + verifier.allow_key(jwk); + auto decoded_token = jwt::decode(token); + ASSERT_NO_THROW(verifier.verify(decoded_token)); } From a631f06e4629071206d86879d435c683469940d8 Mon Sep 17 00:00:00 2001 From: Marek Date: Sun, 3 Apr 2022 20:55:28 +0200 Subject: [PATCH 05/10] add interfaces to set pkeys directly to ECDSA and PSS algorithms --- include/jwt-cpp/jwt.h | 98 ++++++++++++++++++++++++++----------------- 1 file changed, 60 insertions(+), 38 deletions(-) diff --git a/include/jwt-cpp/jwt.h b/include/jwt-cpp/jwt.h index 16e2453e..453b1a07 100644 --- a/include/jwt-cpp/jwt.h +++ b/include/jwt-cpp/jwt.h @@ -854,6 +854,40 @@ namespace jwt { } } // namespace helper + class key { + public: + static key symmetric(const std::string& bytes) { return key(bytes); } + + static key asymmetric(helper::evp_pkey_handle pkey) { return key(pkey); } + + std::string get_symmetric_key() const { + if (!is_symmetric) { throw std::logic_error("not a symmetric key"); } + + return oct_key; + } + + helper::evp_pkey_handle get_asymmetric_key() const { + if (is_symmetric) { throw std::logic_error("not an asymmetric key"); } + + return pkey; + } + + private: + key(const std::string& key) { + is_symmetric = true; + oct_key = key; + } + + key(helper::evp_pkey_handle key) { + is_symmetric = false; + pkey = key; + } + + bool is_symmetric; + helper::evp_pkey_handle pkey; + std::string oct_key; + }; + /** * \brief Various cryptographic algorithms when working with JWT * @@ -1100,6 +1134,9 @@ namespace jwt { throw ecdsa_exception(error::ecdsa_error::invalid_key_size); } + ecdsa(helper::evp_pkey_handle pkey, const EVP_MD* (*md)(), std::string name, size_t siglen) + : pkey(pkey), md(md), alg_name(std::move(name)), signature_length(siglen) {} + /** * Sign jwt data * \param data The data to sign @@ -1459,6 +1496,9 @@ namespace jwt { throw rsa_exception(error::rsa_error::no_key_provided); } + pss(helper::evp_pkey_handle pkey, const EVP_MD* (*md)(), std::string name) + : pkey(pkey), md(md), alg_name(std::move(name)) {} + /** * Sign jwt data * \param data The data to sign @@ -1670,6 +1710,8 @@ namespace jwt { explicit es256(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") : ecdsa(public_key, private_key, public_key_password, private_key_password, EVP_sha256, "ES256", 64) {} + + explicit es256(helper::evp_pkey_handle pkey) : ecdsa(pkey, EVP_sha256, "ES256", 64) {} }; /** * ES384 algorithm @@ -1687,6 +1729,8 @@ namespace jwt { explicit es384(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") : ecdsa(public_key, private_key, public_key_password, private_key_password, EVP_sha384, "ES384", 96) {} + + explicit es384(helper::evp_pkey_handle pkey) : ecdsa(pkey, EVP_sha384, "ES384", 96) {} }; /** * ES512 algorithm @@ -1704,6 +1748,8 @@ namespace jwt { explicit es512(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") : ecdsa(public_key, private_key, public_key_password, private_key_password, EVP_sha512, "ES512", 132) {} + + explicit es512(helper::evp_pkey_handle pkey) : ecdsa(pkey, EVP_sha512, "ES512", 132) {} }; /** * ES256K algorithm @@ -1720,6 +1766,8 @@ namespace jwt { explicit es256k(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") : ecdsa(public_key, private_key, public_key_password, private_key_password, EVP_sha256, "ES256K", 64) {} + + explicit es256k(helper::evp_pkey_handle pkey) : ecdsa(pkey, EVP_sha256, "ES256K", 64) {} }; #if !defined(JWT_OPENSSL_1_0_0) && !defined(JWT_OPENSSL_1_1_0) @@ -1782,6 +1830,8 @@ namespace jwt { explicit ps256(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") : pss(public_key, private_key, public_key_password, private_key_password, EVP_sha256, "PS256") {} + + explicit ps256(helper::evp_pkey_handle pkey) : pss(pkey, EVP_sha256, "PS256") {} }; /** * PS384 algorithm @@ -1797,6 +1847,8 @@ namespace jwt { explicit ps384(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") : pss(public_key, private_key, public_key_password, private_key_password, EVP_sha384, "PS384") {} + + explicit ps384(helper::evp_pkey_handle pkey) : pss(pkey, EVP_sha384, "PS384") {} }; /** * PS512 algorithm @@ -1812,6 +1864,8 @@ namespace jwt { explicit ps512(const std::string& public_key, const std::string& private_key = "", const std::string& public_key_password = "", const std::string& private_key_password = "") : pss(public_key, private_key, public_key_password, private_key_password, EVP_sha512, "PS512") {} + + explicit ps512(helper::evp_pkey_handle pkey) : pss(pkey, EVP_sha512, "PS512") {} }; } // namespace algorithm @@ -3158,7 +3212,7 @@ namespace jwt { JWT_CLAIM_EXPLICIT jwk(const typename json_traits::value_type& json) : jwk(json_traits::as_object(json)) {} JWT_CLAIM_EXPLICIT jwk(const typename json_traits::object_type& json) - : jwk_claims(json), key(build_key(jwk_claims)) { + : jwk_claims(json), k(build_key(jwk_claims)) { // https://datatracker.ietf.org/doc/html/rfc7518#section-6.1 // * indicate required params // "kty"* : "EC", "RSA", "oct" @@ -3354,9 +3408,9 @@ namespace jwt { bool empty() const noexcept { return jwk_claims.empty(); } - helper::evp_pkey_handle get_pkey() const { return key.get_asymmetric_key(); } + helper::evp_pkey_handle get_pkey() const { return k.get_asymmetric_key(); } - std::string get_oct_key() const { return key.get_symmetric_key(); } + std::string get_oct_key() const { return k.get_symmetric_key(); } bool supports(const std::string& alg_name) const { const alg_list& x = supported_alg.find(get_key_type())->second; @@ -3364,40 +3418,6 @@ namespace jwt { } private: - class key { - public: - static key symmetric(const std::string& bytes) { return key(bytes); } - - static key asymmetric(helper::evp_pkey_handle pkey) { return key(pkey); } - - std::string get_symmetric_key() const { - if (!is_symmetric) { throw std::logic_error("not a symmetric key"); } - - return oct_key; - } - - helper::evp_pkey_handle get_asymmetric_key() const { - if (is_symmetric) { throw std::logic_error("not an asymmetric key"); } - - return pkey; - } - - private: - key(const std::string& key) { - is_symmetric = true; - oct_key = key; - } - - key(helper::evp_pkey_handle key) { - is_symmetric = false; - pkey = key; - } - - bool is_symmetric; - helper::evp_pkey_handle pkey; - std::string oct_key; - }; - static helper::evp_pkey_handle build_rsa_key(const details::map_of_claims& claims) { EVP_PKEY* evp_key = nullptr; auto n = jwt::helper::raw2bn( @@ -3460,7 +3480,7 @@ namespace jwt { } } - key key; + key k; }; /** @@ -3553,6 +3573,8 @@ namespace jwt { return std::make_unique>(jwt::algorithm::es384(key.get_pkey())); } else if (alg_name == "ES512") { return std::make_unique>(jwt::algorithm::es512(key.get_pkey())); + } else if (alg_name == "ES256K") { + return std::make_unique>(jwt::algorithm::es256k(key.get_pkey())); } else if (alg_name == "HS256") { return std::make_unique>(jwt::algorithm::hs256(key.get_oct_key())); } else if (alg_name == "HS384") { From 58fe6bcbadd86f386fb4b4d4f2eed38d2e84f57a Mon Sep 17 00:00:00 2001 From: Marek Date: Mon, 11 Apr 2022 01:19:06 +0200 Subject: [PATCH 06/10] prevent duplicate lookup when searching for verification algorithm --- include/jwt-cpp/jwt.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/jwt-cpp/jwt.h b/include/jwt-cpp/jwt.h index 453b1a07..bdf7a7b5 100644 --- a/include/jwt-cpp/jwt.h +++ b/include/jwt-cpp/jwt.h @@ -3801,11 +3801,12 @@ namespace jwt { } if (!key_found) { - if (algs.count(algo) == 0) { + auto alg = algs.find(algo); + if (alg == algs.end()) { ec = error::token_verification_error::wrong_algorithm; return; } - algs.at(algo)->verify(data, sig, ec); + alg->second->verify(data, sig, ec); } if (ec) return; From 70c9a0ca182a794cf03893b08da892467e0eeded Mon Sep 17 00:00:00 2001 From: Marek Date: Mon, 11 Apr 2022 02:11:55 +0200 Subject: [PATCH 07/10] enable user defined base64 decoding function --- include/jwt-cpp/jwt.h | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/include/jwt-cpp/jwt.h b/include/jwt-cpp/jwt.h index bdf7a7b5..ca8acd57 100644 --- a/include/jwt-cpp/jwt.h +++ b/include/jwt-cpp/jwt.h @@ -3206,13 +3206,28 @@ namespace jwt { const details::map_of_claims jwk_claims; public: + template + jwk(const typename json_traits::string_type& str, Decode&& decode) + : jwk(details::map_of_claims::parse_claims(str), decode) {} + + template + jwk(const typename json_traits::value_type& json, Decode&& decode) + : jwk(json_traits::as_object(json), decode) {} + + template + jwk(const typename json_traits::object_type& json, Decode&& decode) + : jwk_claims(json), k(build_key(jwk_claims, decode)) {} + +#ifndef JWT_DISABLE_BASE64 JWT_CLAIM_EXPLICIT jwk(const typename json_traits::string_type& str) : jwk(details::map_of_claims::parse_claims(str)) {} JWT_CLAIM_EXPLICIT jwk(const typename json_traits::value_type& json) : jwk(json_traits::as_object(json)) {} JWT_CLAIM_EXPLICIT jwk(const typename json_traits::object_type& json) - : jwk_claims(json), k(build_key(jwk_claims)) { + : jwk(json, [](const typename json_traits::string_type& str) { + return base::decode(base::pad(str)); + }) { // https://datatracker.ietf.org/doc/html/rfc7518#section-6.1 // * indicate required params // "kty"* : "EC", "RSA", "oct" @@ -3229,6 +3244,7 @@ namespace jwt { // if "oct", then "k"* // if "oct", then SHOULD contain "alg" } +#endif /** * Get key type claim @@ -3418,12 +3434,12 @@ namespace jwt { } private: - static helper::evp_pkey_handle build_rsa_key(const details::map_of_claims& claims) { + template + static helper::evp_pkey_handle build_rsa_key(const details::map_of_claims& claims, + Decode&& decode) { EVP_PKEY* evp_key = nullptr; - auto n = jwt::helper::raw2bn( - base::decode(base::pad(claims.get_claim("n").as_string()))); - auto e = jwt::helper::raw2bn( - base::decode(base::pad(claims.get_claim("e").as_string()))); + auto n = jwt::helper::raw2bn(decode(claims.get_claim("n").as_string())); + auto e = jwt::helper::raw2bn(decode(claims.get_claim("e").as_string())); #ifdef JWT_OPENSSL_3_0 // https://www.openssl.org/docs/manmaster/man7/EVP_PKEY-RSA.html // see https://www.openssl.org/docs/man3.0/man3/EVP_PKEY_fromdata.html @@ -3456,7 +3472,8 @@ namespace jwt { #endif } - static key build_key(const details::map_of_claims& claims) { + template + static key build_key(const details::map_of_claims& claims, Decode&& decode) { if (!claims.has_claim("kty")) { // TODO: custom exception or error code throw std::runtime_error("missing required claim \"kty\""); @@ -3468,12 +3485,12 @@ namespace jwt { } if (claims.get_claim("kty").as_string() == "RSA") { - return key::asymmetric(build_rsa_key(claims)); + return key::asymmetric(build_rsa_key(claims, decode)); } else if (claims.get_claim("kty").as_string() == "EC") { // TODO: build EC key throw std::runtime_error("not implemented"); } else if (claims.get_claim("kty").as_string() == "oct") { - return key::symmetric(base::decode(claims.get_claim("k").as_string())); + return key::symmetric(decode(claims.get_claim("k").as_string())); } else { // TODO: do not build error messages like this throw std::runtime_error("unknown key type (\"kty\"):" + claims.get_claim("kty").as_string()); From bb1409accfc0854d5d5cfdbcb87654b8fc9b94cc Mon Sep 17 00:00:00 2001 From: Marek Date: Mon, 11 Apr 2022 02:30:13 +0200 Subject: [PATCH 08/10] do not use std::make_unique --- include/jwt-cpp/jwt.h | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/include/jwt-cpp/jwt.h b/include/jwt-cpp/jwt.h index ca8acd57..9e0b629c 100644 --- a/include/jwt-cpp/jwt.h +++ b/include/jwt-cpp/jwt.h @@ -3573,31 +3573,44 @@ namespace jwt { } if (alg_name == "RS256") { - return std::make_unique>(jwt::algorithm::rs256(key.get_pkey())); + return std::unique_ptr>( + new algo(jwt::algorithm::rs256(key.get_pkey()))); } else if (alg_name == "RS384") { - return std::make_unique>(jwt::algorithm::rs384(key.get_pkey())); + return std::unique_ptr>( + new algo(jwt::algorithm::rs384(key.get_pkey()))); } else if (alg_name == "RS512") { - return std::make_unique>(jwt::algorithm::rs512(key.get_pkey())); + return std::unique_ptr>( + new algo(jwt::algorithm::rs512(key.get_pkey()))); } else if (alg_name == "PS256") { - return std::make_unique>(jwt::algorithm::ps256(key.get_pkey())); + return std::unique_ptr>( + new algo(jwt::algorithm::ps256(key.get_pkey()))); } else if (alg_name == "PS384") { - return std::make_unique>(jwt::algorithm::ps384(key.get_pkey())); + return std::unique_ptr>( + new algo(jwt::algorithm::ps384(key.get_pkey()))); } else if (alg_name == "PS512") { - return std::make_unique>(jwt::algorithm::ps512(key.get_pkey())); + return std::unique_ptr>( + new algo(jwt::algorithm::ps512(key.get_pkey()))); } else if (alg_name == "ES256") { - return std::make_unique>(jwt::algorithm::es256(key.get_pkey())); + return std::unique_ptr>( + new algo(jwt::algorithm::es256(key.get_pkey()))); } else if (alg_name == "ES384") { - return std::make_unique>(jwt::algorithm::es384(key.get_pkey())); + return std::unique_ptr>( + new algo(jwt::algorithm::es384(key.get_pkey()))); } else if (alg_name == "ES512") { - return std::make_unique>(jwt::algorithm::es512(key.get_pkey())); + return std::unique_ptr>( + new algo(jwt::algorithm::es512(key.get_pkey()))); } else if (alg_name == "ES256K") { - return std::make_unique>(jwt::algorithm::es256k(key.get_pkey())); + return std::unique_ptr>( + new algo(jwt::algorithm::es256k(key.get_pkey()))); } else if (alg_name == "HS256") { - return std::make_unique>(jwt::algorithm::hs256(key.get_oct_key())); + return std::unique_ptr>( + new algo(jwt::algorithm::hs256(key.get_oct_key()))); } else if (alg_name == "HS384") { - return std::make_unique>(jwt::algorithm::hs384(key.get_oct_key())); + return std::unique_ptr>( + new algo(jwt::algorithm::hs384(key.get_oct_key()))); } else if (alg_name == "HS512") { - return std::make_unique>(jwt::algorithm::hs512(key.get_oct_key())); + return std::unique_ptr>( + new algo(jwt::algorithm::hs512(key.get_oct_key()))); } ec = error::token_verification_error::wrong_algorithm; From ea8f066f5344b825da5109ed49597f60ff7a5d5f Mon Sep 17 00:00:00 2001 From: Marek Date: Sat, 23 Apr 2022 18:45:01 +0200 Subject: [PATCH 09/10] enable user defined base64 decoding function --- include/jwt-cpp/jwt.h | 12 ++++++++++-- include/jwt-cpp/traits/kazuho-picojson/defaults.h | 14 ++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/include/jwt-cpp/jwt.h b/include/jwt-cpp/jwt.h index 9e0b629c..b20856f1 100644 --- a/include/jwt-cpp/jwt.h +++ b/include/jwt-cpp/jwt.h @@ -3862,7 +3862,8 @@ namespace jwt { using iterator = typename jwt_vector_t::iterator; using const_iterator = typename jwt_vector_t::const_iterator; - JWT_CLAIM_EXPLICIT jwks(const typename json_traits::string_type& str) { + template + jwks(const typename json_traits::string_type& str, Decode decode) { typename json_traits::value_type parsed_val; if (!json_traits::parse(parsed_val, str)) throw error::invalid_json_exception(); @@ -3871,9 +3872,16 @@ namespace jwt { auto jwk_list = jwks_json.get_claim("keys").as_array(); std::transform(jwk_list.begin(), jwk_list.end(), std::back_inserter(jwk_claims), - [](const typename json_traits::value_type& val) { return jwk_t{val}; }); + [&](const typename json_traits::value_type& val) { return jwk_t(val, decode); }); } +#ifndef JWT_DISABLE_BASE64 + JWT_CLAIM_EXPLICIT jwks(const typename json_traits::string_type& str) + : jwks(str, [](const typename json_traits::string_type& str) { + return base::decode(base::pad(str)); + }) {} +#endif + iterator begin() { return jwk_claims.begin(); } iterator end() { return jwk_claims.end(); } const_iterator cbegin() const { return jwk_claims.begin(); } diff --git a/include/jwt-cpp/traits/kazuho-picojson/defaults.h b/include/jwt-cpp/traits/kazuho-picojson/defaults.h index 0c82133a..26f304bd 100644 --- a/include/jwt-cpp/traits/kazuho-picojson/defaults.h +++ b/include/jwt-cpp/traits/kazuho-picojson/defaults.h @@ -54,6 +54,7 @@ namespace jwt { return decoded_jwt(token, decode); } +#ifndef JWT_DISABLE_BASE64 /** * Parse a jwk * \param token JWK Token to parse @@ -63,7 +64,14 @@ namespace jwt { inline jwk parse_jwk(const traits::kazuho_picojson::string_type& token) { return jwk(token); } +#endif + + template + jwk parse_jwk(const traits::kazuho_picojson::string_type& token, Decode decode) { + return jwk(token, decode); + } +#ifndef JWT_DISABLE_BASE64 /** * Parse a jwks * \param token JWKs Token to parse @@ -73,6 +81,12 @@ namespace jwt { inline jwks parse_jwks(const traits::kazuho_picojson::string_type& token) { return jwks(token); } +#endif + + template + jwks parse_jwks(const traits::kazuho_picojson::string_type& token, Decode decode) { + return jwks(token, decode); + } /** * This type is the specialization of the \ref verify_ops::verify_context class which From 55b5b14df4f00587a0c132b5ef284765e828ade0 Mon Sep 17 00:00:00 2001 From: Marek Date: Sun, 24 Apr 2022 01:46:40 +0200 Subject: [PATCH 10/10] move supported algorithms to the verifier class --- include/jwt-cpp/jwt.h | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/include/jwt-cpp/jwt.h b/include/jwt-cpp/jwt.h index b20856f1..2a25d54b 100644 --- a/include/jwt-cpp/jwt.h +++ b/include/jwt-cpp/jwt.h @@ -3186,12 +3186,6 @@ namespace jwt { }; } // namespace verify_ops - using alg_name = std::string; - using alg_list = std::vector; - using algorithms = std::unordered_map; - static const algorithms supported_alg = {{"RSA", {"RS256", "RS384", "RS512", "PS256", "PS384", "PS512"}}, - {"EC", {"ES256", "ES384", "ES512", "ES256K"}}, - {"oct", {"HS256", "HS384", "HS512"}}}; /** * \brief JSON Web Key * @@ -3428,11 +3422,6 @@ namespace jwt { std::string get_oct_key() const { return k.get_symmetric_key(); } - bool supports(const std::string& alg_name) const { - const alg_list& x = supported_alg.find(get_key_type())->second; - return std::find(x.begin(), x.end(), alg_name) != x.end(); - } - private: template static helper::evp_pkey_handle build_rsa_key(const details::map_of_claims& claims, @@ -3541,6 +3530,12 @@ namespace jwt { Clock clock; /// Supported algorithms std::unordered_map> algs; + using alg_name = std::string; + using alg_list = std::vector; + using algorithms = std::unordered_map; + algorithms supported_alg = {{"RSA", {"RS256", "RS384", "RS512", "PS256", "PS384", "PS512"}}, + {"EC", {"ES256", "ES384", "ES512", "ES256K"}}, + {"oct", {"HS256", "HS384", "HS512"}}}; typedef std::vector> key_list; /// https://datatracker.ietf.org/doc/html/rfc7517#section-4.5 - kid to keys @@ -3556,8 +3551,13 @@ namespace jwt { } } - static inline std::unique_ptr from_key_and_alg(const jwt::jwk& key, - const std::string& alg_name, std::error_code& ec) { + bool is_valid_combination(const jwt::jwk& key, const std::string& alg_name) const { + const alg_list& x = supported_alg.find(key.get_key_type())->second; + return std::find(x.cbegin(), x.cend(), alg_name) != x.cend(); + } + + inline std::unique_ptr from_key_and_alg(const jwt::jwk& key, + const std::string& alg_name, std::error_code& ec) const { ec.clear(); algorithms::const_iterator it = supported_alg.find(key.get_key_type()); if (it == supported_alg.end()) { @@ -3821,7 +3821,7 @@ namespace jwt { if (key_set_it != keys.end()) { const key_list& keys = key_set_it->second; for (const auto& key : keys) { - if (key.supports(algo)) { + if (is_valid_combination(key, algo)) { key_found = true; auto alg = from_key_and_alg(key, algo, ec); alg->verify(data, sig, ec);