|
73 | 73 | #define JWT_CLAIM_EXPLICIT explicit
|
74 | 74 | #endif
|
75 | 75 |
|
| 76 | +#ifdef JWT_OPENSSL_3_0 |
| 77 | +#include <openssl/param_build.h> |
| 78 | +#endif |
| 79 | + |
76 | 80 | /**
|
77 | 81 | * \brief JSON Web Token
|
78 | 82 | *
|
@@ -976,6 +980,9 @@ namespace jwt {
|
976 | 980 | } else
|
977 | 981 | throw rsa_exception(error::rsa_error::no_key_provided);
|
978 | 982 | }
|
| 983 | + |
| 984 | + rsa(helper::evp_pkey_handle pkey, const EVP_MD* (*md)(), std::string name) |
| 985 | + : pkey(pkey), md(md), alg_name(std::move(name)) {} |
979 | 986 | /**
|
980 | 987 | * Sign jwt data
|
981 | 988 | * \param data The data to sign
|
@@ -3134,10 +3141,28 @@ namespace jwt {
|
3134 | 3141 |
|
3135 | 3142 | public:
|
3136 | 3143 | JWT_CLAIM_EXPLICIT jwk(const typename json_traits::string_type& str)
|
3137 |
| - : jwk_claims(details::map_of_claims<json_traits>::parse_claims(str)) {} |
| 3144 | + : jwk(details::map_of_claims<json_traits>::parse_claims(str)) {} |
| 3145 | + |
| 3146 | + JWT_CLAIM_EXPLICIT jwk(const typename json_traits::value_type& json) : jwk(json_traits::as_object(json)) {} |
| 3147 | + |
| 3148 | + JWT_CLAIM_EXPLICIT jwk(const typename json_traits::object_type& json) |
| 3149 | + : jwk_claims(json), key(build_key(jwk_claims)) { |
| 3150 | + // https://datatracker.ietf.org/doc/html/rfc7518#section-6.1 |
| 3151 | + // * indicate required params |
| 3152 | + // "kty"* : "EC", "RSA", "oct" |
3138 | 3153 |
|
3139 |
| - JWT_CLAIM_EXPLICIT jwk(const typename json_traits::value_type& json) |
3140 |
| - : jwk_claims(json_traits::as_object(json)) {} |
| 3154 | + // if "EC", then "crv"*, then "x"*. if "crv" is any of "P-256", "P-384", "P-521", then "y"* |
| 3155 | + // if "EC" and private key, then "d"* |
| 3156 | + |
| 3157 | + // if "RSA", then "n"*, "e"* |
| 3158 | + // if "RSA" and private, then "d"* |
| 3159 | + // if "RSA" and any of the following is present, then all must be present |
| 3160 | + // "p", "q", "dp", "dq", "qi" |
| 3161 | + // "oth" - array of objects consisting of "r"*, "d"*, "t"* |
| 3162 | + |
| 3163 | + // if "oct", then "k"* |
| 3164 | + // if "oct", then SHOULD contain "alg" |
| 3165 | + } |
3141 | 3166 |
|
3142 | 3167 | /**
|
3143 | 3168 | * Get key type claim
|
@@ -3316,6 +3341,109 @@ namespace jwt {
|
3316 | 3341 | }
|
3317 | 3342 |
|
3318 | 3343 | bool empty() const noexcept { return jwk_claims.empty(); }
|
| 3344 | + |
| 3345 | + helper::evp_pkey_handle get_pkey() const { return key.get_asymmetric_key(); } |
| 3346 | + |
| 3347 | + std::string get_oct_key() const { return key.get_symmetric_key(); } |
| 3348 | + |
| 3349 | + private: |
| 3350 | + class key { |
| 3351 | + public: |
| 3352 | + static key symmetric(const std::string& bytes) { return key(bytes); } |
| 3353 | + |
| 3354 | + static key asymmetric(helper::evp_pkey_handle pkey) { return key(pkey); } |
| 3355 | + |
| 3356 | + std::string get_symmetric_key() const { |
| 3357 | + if (!is_symmetric) { throw std::logic_error("not a symmetric key"); } |
| 3358 | + |
| 3359 | + return oct_key; |
| 3360 | + } |
| 3361 | + |
| 3362 | + helper::evp_pkey_handle get_asymmetric_key() const { |
| 3363 | + if (is_symmetric) { throw std::logic_error("not an asymmetric key"); } |
| 3364 | + |
| 3365 | + return pkey; |
| 3366 | + } |
| 3367 | + |
| 3368 | + private: |
| 3369 | + key(const std::string& key) { |
| 3370 | + is_symmetric = true; |
| 3371 | + oct_key = key; |
| 3372 | + } |
| 3373 | + |
| 3374 | + key(helper::evp_pkey_handle key) { |
| 3375 | + is_symmetric = false; |
| 3376 | + pkey = key; |
| 3377 | + } |
| 3378 | + |
| 3379 | + bool is_symmetric; |
| 3380 | + helper::evp_pkey_handle pkey; |
| 3381 | + std::string oct_key; |
| 3382 | + }; |
| 3383 | + |
| 3384 | + static helper::evp_pkey_handle build_rsa_key(const details::map_of_claims<json_traits>& claims) { |
| 3385 | + EVP_PKEY* evp_key = nullptr; |
| 3386 | + auto n = jwt::helper::raw2bn( |
| 3387 | + base::decode<alphabet::base64url>(base::pad<alphabet::base64url>(claims.get_claim("n").as_string()))); |
| 3388 | + auto e = jwt::helper::raw2bn( |
| 3389 | + base::decode<alphabet::base64url>(base::pad<alphabet::base64url>(claims.get_claim("e").as_string()))); |
| 3390 | +#ifdef JWT_OPENSSL_3_0 |
| 3391 | + // https://www.openssl.org/docs/manmaster/man7/EVP_PKEY-RSA.html |
| 3392 | + // see https://www.openssl.org/docs/man3.0/man3/EVP_PKEY_fromdata.html |
| 3393 | + // and https://stackoverflow.com/questions/68465716/how-to-properly-create-an-rsa-key-from-raw-data-in-openssl-3-0-in-c-language |
| 3394 | + std::unique_ptr<EVP_PKEY_CTX, decltype(&EVP_PKEY_CTX_free)> ctx( |
| 3395 | + EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL), EVP_PKEY_CTX_free); |
| 3396 | + if (!ctx) { throw std::runtime_error("EVP_PKEY_CTX_new_from_name failed"); } |
| 3397 | + |
| 3398 | + std::unique_ptr<OSSL_PARAM_BLD, decltype(&OSSL_PARAM_BLD_free)> params_build(OSSL_PARAM_BLD_new(), |
| 3399 | + OSSL_PARAM_BLD_free); |
| 3400 | + OSSL_PARAM_BLD_push_BN(params_build.get(), "n", n.get()); |
| 3401 | + OSSL_PARAM_BLD_push_BN(params_build.get(), "e", e.get()); |
| 3402 | + |
| 3403 | + std::unique_ptr<OSSL_PARAM, decltype(&OSSL_PARAM_free)> params(OSSL_PARAM_BLD_to_param(params_build.get()), |
| 3404 | + OSSL_PARAM_free); |
| 3405 | + EVP_PKEY_fromdata_init(ctx.get()); |
| 3406 | + EVP_PKEY_fromdata(ctx.get(), &evp_key, EVP_PKEY_PUBLIC_KEY, params.get()); |
| 3407 | + return helper::evp_pkey_handle(evp_key); |
| 3408 | +#else |
| 3409 | + RSA* rsa = RSA_new(); |
| 3410 | + evp_key = EVP_PKEY_new(); |
| 3411 | +#if defined(JWT_OPENSSL_1_0_0) && !defined(LIBWOLFSSL_VERSION_HEX) |
| 3412 | + rsa->e = e.release(); |
| 3413 | + rsa->n = n.release(); |
| 3414 | +#else |
| 3415 | + RSA_set0_key(rsa, n.release(), e.release(), nullptr); |
| 3416 | +#endif |
| 3417 | + EVP_PKEY_assign_RSA(evp_key, rsa); |
| 3418 | + return helper::evp_pkey_handle(evp_key); |
| 3419 | +#endif |
| 3420 | + } |
| 3421 | + |
| 3422 | + static key build_key(const details::map_of_claims<json_traits>& claims) { |
| 3423 | + if (!claims.has_claim("kty")) { |
| 3424 | + // TODO: custom exception or error code |
| 3425 | + throw std::runtime_error("missing required claim \"kty\""); |
| 3426 | + } |
| 3427 | + |
| 3428 | + if (claims.get_claim("kty").get_type() != json::type::string) { |
| 3429 | + // TODO: custom exception or error code |
| 3430 | + throw std::runtime_error("\"kty\" claim must be of type 'string'"); |
| 3431 | + } |
| 3432 | + |
| 3433 | + if (claims.get_claim("kty").as_string() == "RSA") { |
| 3434 | + return key::asymmetric(build_rsa_key(claims)); |
| 3435 | + } else if (claims.get_claim("kty").as_string() == "EC") { |
| 3436 | + // TODO: build EC key |
| 3437 | + throw std::runtime_error("not implemented"); |
| 3438 | + } else if (claims.get_claim("kty").as_string() == "oct") { |
| 3439 | + return key::symmetric(base::decode<alphabet::base64url>(claims.get_claim("k").as_string())); |
| 3440 | + } else { |
| 3441 | + // TODO: do not build error messages like this |
| 3442 | + throw std::runtime_error("unknown key type (\"kty\"):" + claims.get_claim("kty").as_string()); |
| 3443 | + } |
| 3444 | + } |
| 3445 | + |
| 3446 | + key key; |
3319 | 3447 | };
|
3320 | 3448 |
|
3321 | 3449 | /**
|
|
0 commit comments