diff --git a/include/jwt-cpp/jwt.h b/include/jwt-cpp/jwt.h index 8cfefcc9..2a25d54b 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 * @@ -850,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 * @@ -976,6 +1014,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 @@ -1093,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 @@ -1452,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 @@ -1610,6 +1657,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 @@ -1625,6 +1674,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 @@ -1640,6 +1691,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 @@ -1657,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 @@ -1674,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 @@ -1691,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 @@ -1707,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) @@ -1769,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 @@ -1784,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 @@ -1799,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 @@ -3119,6 +3186,309 @@ 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: + 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(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" + + // 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" + } +#endif + + /** + * 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(); } + + helper::evp_pkey_handle get_pkey() const { return k.get_asymmetric_key(); } + + std::string get_oct_key() const { return k.get_symmetric_key(); } + + private: + 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(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 + // 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 + } + + 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\""); + } + + 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, 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(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 k; + }; + /** * Verifier class used to check if a decoded token contains all claims required by your application and has a valid * signature. @@ -3160,6 +3530,92 @@ 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 + 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) { + ctx.claim_key = c.first; + c.second(ctx, ec); + if (ec) return; + } + } + + 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()) { + 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::unique_ptr>( + new algo(jwt::algorithm::rs256(key.get_pkey()))); + } else if (alg_name == "RS384") { + return std::unique_ptr>( + new algo(jwt::algorithm::rs384(key.get_pkey()))); + } else if (alg_name == "RS512") { + return std::unique_ptr>( + new algo(jwt::algorithm::rs512(key.get_pkey()))); + } else if (alg_name == "PS256") { + return std::unique_ptr>( + new algo(jwt::algorithm::ps256(key.get_pkey()))); + } else if (alg_name == "PS384") { + return std::unique_ptr>( + new algo(jwt::algorithm::ps384(key.get_pkey()))); + } else if (alg_name == "PS512") { + return std::unique_ptr>( + new algo(jwt::algorithm::ps512(key.get_pkey()))); + } else if (alg_name == "ES256") { + return std::unique_ptr>( + new algo(jwt::algorithm::es256(key.get_pkey()))); + } else if (alg_name == "ES384") { + return std::unique_ptr>( + new algo(jwt::algorithm::es384(key.get_pkey()))); + } else if (alg_name == "ES512") { + return std::unique_ptr>( + new algo(jwt::algorithm::es512(key.get_pkey()))); + } else if (alg_name == "ES256K") { + return std::unique_ptr>( + new algo(jwt::algorithm::es256k(key.get_pkey()))); + } else if (alg_name == "HS256") { + return std::unique_ptr>( + new algo(jwt::algorithm::hs256(key.get_oct_key()))); + } else if (alg_name == "HS384") { + return std::unique_ptr>( + new algo(jwt::algorithm::hs384(key.get_oct_key()))); + } else if (alg_name == "HS512") { + return std::unique_ptr>( + new algo(jwt::algorithm::hs512(key.get_oct_key()))); + } + + ec = error::token_verification_error::wrong_algorithm; + return nullptr; + } public: /** @@ -3325,6 +3781,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 @@ -3345,225 +3813,35 @@ 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 (is_valid_combination(key, algo)) { + key_found = true; + auto alg = from_key_and_alg(key, algo, ec); + alg->verify(data, sig, ec); + break; + } + } } - 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; + if (!key_found) { + auto alg = algs.find(algo); + if (alg == algs.end()) { + ec = error::token_verification_error::wrong_algorithm; + return; + } + alg->second->verify(data, sig, ec); } - } - }; - - /** - * \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); + if (ec) return; + verify_claims(jwt, ec); } - - 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; } }; /** @@ -3584,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(); @@ -3593,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 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..37e7fd62 --- /dev/null +++ b/tests/JwkTest.cpp @@ -0,0 +1,78 @@ +#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, RsaKey) { + 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 verifier = jwt::verify(); + verifier.allow_key(jwk); + auto decoded_token = jwt::decode(token); + ASSERT_NO_THROW(verifier.verify(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)); +}