From: Pritha Srivastava Date: Thu, 26 Dec 2024 08:56:30 +0000 (+0530) Subject: rgw/sts: adding code for JWT signature validation X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=b368cab8064925674c008c0e8c39d17fbf964ac6;p=ceph.git rgw/sts: adding code for JWT signature validation using modulus and exponent for RSA group of algorithms. A couple of issues and a fix have been suggested by Pupu Toivonen (pupu.toivonen@csc.fi) in signature calculation using modulus and exponent. Fixes: https://tracker.ceph.com/issues/51018 Signed-off-by: Pritha Srivastava --- diff --git a/src/rgw/jwt-cpp/jwt.h b/src/rgw/jwt-cpp/jwt.h index 352b80010b866..995a6410ac154 100644 --- a/src/rgw/jwt-cpp/jwt.h +++ b/src/rgw/jwt-cpp/jwt.h @@ -11,6 +11,10 @@ #include #include #include +#include +#include + +#include "rgw/rgw_b64.h" //If openssl version less than 1.1 #if OPENSSL_VERSION_NUMBER < 269484032 @@ -233,6 +237,17 @@ namespace jwt { } else throw rsa_exception("at least one of public or private key need to be present"); } + + rsa(const EVP_MD*(*md)(), const std::string& name) : md(md), alg_name(name) + {} + + void setModulusExponentCalcPublicKey(const std::string& modulus, const std::string& exponent) + { + this->modulus = modulus; + this->exponent = exponent; + calculatePublicKey(); + } + /** * Sign jwt data * \param data The data to sign @@ -296,8 +311,43 @@ namespace jwt { std::shared_ptr pkey; /// Hash generator const EVP_MD*(*md)(); + /// Modulus + std::string modulus; + /// Exponent + std::string exponent; /// Algorithmname const std::string alg_name; + + void calculatePublicKey() + { + std::string n_str = base64_decode_url(modulus); + std::string e_str = base64_decode_url(exponent); + unsigned char* u_n = (unsigned char *)n_str.c_str(); + unsigned char* u_e = (unsigned char *)e_str.c_str(); + BIGNUM *n = BN_bin2bn(u_n, n_str.size(), NULL); + BIGNUM *e = BN_bin2bn(u_e, e_str.size(), NULL); + + if (e && n) { + EVP_PKEY* pRsaKey = EVP_PKEY_new(); + RSA* rsa = RSA_new(); + RSA_set0_key(rsa, n, e, nullptr); + EVP_PKEY_assign_RSA(pRsaKey, rsa); + std::shared_ptr p(pRsaKey, EVP_PKEY_free); + pkey = p; + } else { + if (n) BN_free(n); + if (e) BN_free(e); + throw rsa_exception("Invalid encoding for modulus or exponent\n"); + } + } + + std::string base64_decode_url(const std::string& str) const { + std::string s = "===="; + std::string padded_str = str.length() % 4 == 0 ? str : str + s.substr(0, str.length() % 4); + std::replace(padded_str.begin(), padded_str.end(), '_', '/'); + std::replace(padded_str.begin(), padded_str.end(), '-', '+'); + return rgw::from_base64(padded_str); + } }; /** * Base class for ECDSA family of algorithms @@ -629,6 +679,15 @@ 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") {} + + rs256() : rsa(EVP_sha256, "RS256") + {} + + rs256& setModulusAndExponent(const std::string& modulus, const std::string& exponent) + { + rsa::setModulusExponentCalcPublicKey(modulus, exponent); + return *this; + } }; /** * RS384 algorithm @@ -644,6 +703,15 @@ 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") {} + + rs384() : rsa(EVP_sha384, "RS384") + {} + + rs384& setModulusAndExponent(const std::string& modulus, const std::string& exponent) + { + rsa::setModulusExponentCalcPublicKey(modulus, exponent); + return *this; + } }; /** * RS512 algorithm @@ -659,6 +727,15 @@ 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") {} + + rs512() : rsa(EVP_sha512, "RS512") + {} + + rs512& setModulusAndExponent(const std::string& modulus, const std::string& exponent) + { + rsa::setModulusExponentCalcPublicKey(modulus, exponent); + return *this; + } }; /** * ES256 algorithm diff --git a/src/rgw/rgw_rest_sts.cc b/src/rgw/rgw_rest_sts.cc index c0c6b5e89acd2..b9308b5cf941b 100644 --- a/src/rgw/rgw_rest_sts.cc +++ b/src/rgw/rgw_rest_sts.cc @@ -339,6 +339,30 @@ WebTokenEngine::get_cert_url(const string& iss, const DoutPrefixProvider *dpp, o return cert_url; } +void +WebTokenEngine::validate_signature_using_n_e(const DoutPrefixProvider* dpp, const jwt::decoded_jwt& decoded, const std::string &algorithm, const std::string& n, const std::string& e) const +{ + try { + if (algorithm == "RS256") { + auto verifier = jwt::verify() + .allow_algorithm(jwt::algorithm::rs256().setModulusAndExponent(n,e)); + verifier.verify(decoded); + } else if (algorithm == "RS384") { + auto verifier = jwt::verify() + .allow_algorithm(jwt::algorithm::rs384().setModulusAndExponent(n,e)); + verifier.verify(decoded); + } else if (algorithm == "RS512") { + auto verifier = jwt::verify() + .allow_algorithm(jwt::algorithm::rs512().setModulusAndExponent(n,e)); + verifier.verify(decoded); + } + } catch (const std::exception& e) { + throw std::system_error(EACCES, std::system_category(), std::string("Signature validation using n, e failed: ") + e.what()); + } + ldpp_dout(dpp, 10) << "Verified signature using n and e"<< dendl; + return; +} + void WebTokenEngine::validate_signature(const DoutPrefixProvider* dpp, const jwt::decoded_jwt& decoded, const string& algorithm, const string& iss, const vector& thumbprints, optional_yield y) const { @@ -450,8 +474,17 @@ WebTokenEngine::validate_signature(const DoutPrefixProvider* dpp, const jwt::dec throw; } } else { - ldpp_dout(dpp, 0) << "x5c not present" << dendl; - throw -EINVAL; + if (algorithm == "RS256" || algorithm == "RS384" || algorithm == "RS512") { + string n, e; //modulus and exponent + if (JSONDecoder::decode_json("n", n, &parser) && JSONDecoder::decode_json("e", e, &parser)) { + validate_signature_using_n_e(dpp, decoded, algorithm, n, e); + return; + } + ldpp_dout(dpp, 0) << "x5c not present or n, e not present" << dendl; + throw std::system_error(EINVAL, std::system_category()); + } else { + throw std::system_error(EINVAL, std::system_category(), "Invalid algorithm: " + algorithm); + } } } else { ldpp_dout(dpp, 0) << "Malformed JSON object for keys" << dendl; diff --git a/src/rgw/rgw_rest_sts.h b/src/rgw/rgw_rest_sts.h index 432244009202f..8d25f6784329e 100644 --- a/src/rgw/rgw_rest_sts.h +++ b/src/rgw/rgw_rest_sts.h @@ -53,6 +53,8 @@ class WebTokenEngine : public rgw::auth::Engine { std::tuple, boost::optional> get_from_jwt(const DoutPrefixProvider* dpp, const std::string& token, const req_state* const s, optional_yield y) const; + void validate_signature_using_n_e(const DoutPrefixProvider* dpp, const jwt::decoded_jwt& decoded, const std::string &algorithm, const std::string& n, const std::string& e) const; + void validate_signature (const DoutPrefixProvider* dpp, const jwt::decoded_jwt& decoded, const std::string& algorithm, const std::string& iss, const std::vector& thumbprints, optional_yield y) const; result_t authenticate(const DoutPrefixProvider* dpp,