foundationdb/fdbrpc/JsonWebKeySet.cpp

848 lines
33 KiB
C++

/*
* JsonWebKeySet.cpp
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2013-2022 Apple Inc. and the FoundationDB project authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "flow/Arena.h"
#include "flow/AutoCPointer.h"
#include "flow/Error.h"
#include "flow/IRandom.h"
#include "flow/MkCert.h"
#include "flow/PKey.h"
#include "flow/UnitTest.h"
#include "fdbrpc/Base64Encode.h"
#include "fdbrpc/Base64Decode.h"
#include "fdbrpc/JsonWebKeySet.h"
#if defined(HAVE_WOLFSSL)
#include <wolfssl/options.h>
#endif
#include <openssl/bn.h>
#include <openssl/ec.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/opensslv.h>
#include <openssl/rsa.h>
#include <openssl/x509.h>
#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(_WIN32)
#define USE_V3_API 1
#else
#define USE_V3_API 0
#endif
#if USE_V3_API
#include <openssl/core_names.h>
#include <openssl/param_build.h>
#endif
#include <rapidjson/document.h>
#include <rapidjson/error/en.h>
#include <rapidjson/writer.h>
#include <rapidjson/stringbuffer.h>
#include <array>
#include <string_view>
#include <type_traits>
#define JWKS_ERROR(issue, op) \
TraceEvent(SevWarnAlways, "JsonWebKeySet" #op "Error").suppressFor(10).detail("Issue", issue)
#define JWKS_PARSE_ERROR(issue) JWKS_ERROR(issue, Parse)
#define JWKS_WRITE_ERROR(issue) JWKS_ERROR(issue, Write)
#define JWK_PARSE_ERROR(issue) \
TraceEvent(SevWarnAlways, "JsonWebKeyParseError") \
.suppressFor(10) \
.detail("Issue", issue) \
.detail("KeyIndexBase0", keyIndex)
#define JWK_WRITE_ERROR(issue) \
TraceEvent(SevWarnAlways, "JsonWebKeyWriteError") \
.suppressFor(10) \
.detail("Issue", issue) \
.detail("KeyName", keyName.toString())
#define JWK_ERROR_OSSL(issue, op) \
do { \
char buf[256]{ \
0, \
}; \
if (auto err = ::ERR_get_error()) { \
::ERR_error_string_n(err, buf, sizeof(buf)); \
} \
JWK_##op##_ERROR(issue).detail("OpenSSLError", static_cast<char const*>(buf)); \
} while (0)
#define JWK_PARSE_ERROR_OSSL(issue) JWK_ERROR_OSSL(issue, PARSE)
#define JWK_WRITE_ERROR_OSSL(issue) JWK_ERROR_OSSL(issue, WRITE)
namespace {
template <bool Required, class JsonValue>
bool getJwkStringMember(JsonValue const& value,
char const* memberName,
std::conditional_t<Required, StringRef, Optional<StringRef>>& out,
int keyIndex) {
auto itr = value.FindMember(memberName);
if (itr == value.MemberEnd()) {
if constexpr (Required) {
JWK_PARSE_ERROR("Missing required member").detail("Member", memberName);
return false;
} else {
return true;
}
}
auto const& member = itr->value;
if (!member.IsString()) {
JWK_PARSE_ERROR("Expected member is not a string").detail("MemberName", memberName);
return false;
}
out = StringRef(reinterpret_cast<uint8_t const*>(member.GetString()), member.GetStringLength());
return true;
}
#define DECLARE_JWK_REQUIRED_STRING_MEMBER(value, member) \
auto member = StringRef(); \
if (!getJwkStringMember<true>(value, #member, member, keyIndex)) \
return {}
#define DECLARE_JWK_OPTIONAL_STRING_MEMBER(value, member) \
auto member = Optional<StringRef>(); \
if (!getJwkStringMember<false>(value, #member, member, keyIndex)) \
return {}
template <bool Required, class AutoPtr>
bool getJwkBigNumMember(Arena& arena,
std::conditional_t<Required, StringRef, Optional<StringRef>> const& b64Member,
AutoPtr& ptr,
char const* memberName,
char const* algorithm,
int keyIndex) {
if constexpr (!Required) {
if (!b64Member.present())
return true;
}
auto data = StringRef();
if constexpr (Required) {
data = b64Member;
} else {
data = b64Member.get();
}
auto decoded = base64::url::decode(arena, data);
if (!decoded.present()) {
JWK_PARSE_ERROR("Base64URL decoding for parameter failed")
.detail("Algorithm", algorithm)
.detail("Parameter", memberName);
return false;
}
data = decoded.get();
auto bn = ::BN_bin2bn(data.begin(), data.size(), nullptr);
if (!bn) {
JWK_PARSE_ERROR_OSSL("BN_bin2bn");
return false;
}
ptr.reset(bn);
return true;
}
#define DECL_DECODED_BN_MEMBER_REQUIRED(member, algo) \
auto member = AutoCPointer(nullptr, &::BN_free); \
if (!getJwkBigNumMember<true /*Required*/>(arena, b64##member, member, #member, algo, keyIndex)) \
return {}
#define DECL_DECODED_BN_MEMBER_OPTIONAL(member, algo) \
auto member = AutoCPointer(nullptr, &::BN_clear_free); \
if (!getJwkBigNumMember<false /*Required*/>(arena, b64##member, member, #member, algo, keyIndex)) \
return {}
#define EC_DECLARE_DECODED_REQUIRED_BN_MEMBER(member) DECL_DECODED_BN_MEMBER_REQUIRED(member, "EC")
#define EC_DECLARE_DECODED_OPTIONAL_BN_MEMBER(member) DECL_DECODED_BN_MEMBER_OPTIONAL(member, "EC")
#define RSA_DECLARE_DECODED_REQUIRED_BN_MEMBER(member) DECL_DECODED_BN_MEMBER_REQUIRED(member, "RSA")
#define RSA_DECLARE_DECODED_OPTIONAL_BN_MEMBER(member) DECL_DECODED_BN_MEMBER_OPTIONAL(member, "RSA")
StringRef bigNumToBase64Url(Arena& arena, const BIGNUM* bn) {
auto len = BN_num_bytes(bn);
auto buf = new (arena) uint8_t[len];
::BN_bn2bin(bn, buf);
return base64::url::encode(arena, StringRef(buf, len));
}
Optional<PublicOrPrivateKey> parseEcP256Key(StringRef b64x, StringRef b64y, Optional<StringRef> b64d, int keyIndex) {
auto arena = Arena();
EC_DECLARE_DECODED_REQUIRED_BN_MEMBER(x);
EC_DECLARE_DECODED_REQUIRED_BN_MEMBER(y);
EC_DECLARE_DECODED_OPTIONAL_BN_MEMBER(d);
#if USE_V3_API
// avoid deprecated API
auto bld = AutoCPointer(::OSSL_PARAM_BLD_new(), &::OSSL_PARAM_BLD_free);
if (!bld) {
JWK_PARSE_ERROR_OSSL("OSSL_PARAM_BLD_new() for EC");
return {};
}
// since OSSL_PKEY_PARAM_EC_PUB_{X|Y} are not settable params, we'll need to build a EC_GROUP and serialize it
auto group = AutoCPointer(::EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1), &::EC_GROUP_free);
if (!group) {
JWK_PARSE_ERROR_OSSL("EC_GROUP_new_by_curve_name()");
return {};
}
auto point = AutoCPointer(::EC_POINT_new(group), &::EC_POINT_free);
if (!point) {
JWK_PARSE_ERROR_OSSL("EC_POINT_new()");
return {};
}
if (1 != ::EC_POINT_set_affine_coordinates(group, point, x, y, nullptr)) {
JWK_PARSE_ERROR_OSSL("EC_POINT_set_affine_coordinates()");
return {};
}
auto pointBufLen = ::EC_POINT_point2oct(group, point, POINT_CONVERSION_UNCOMPRESSED, nullptr, 0, nullptr);
if (!pointBufLen) {
JWK_PARSE_ERROR_OSSL("EC_POINT_point2oct() for length");
return {};
}
auto pointBuf = new (arena) uint8_t[pointBufLen];
::EC_POINT_point2oct(group, point, POINT_CONVERSION_UNCOMPRESSED, pointBuf, pointBufLen, nullptr);
if (!::OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_PKEY_PARAM_GROUP_NAME, "prime256v1", sizeof("prime256v1") - 1) ||
!::OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_PUB_KEY, pointBuf, pointBufLen)) {
JWK_PARSE_ERROR_OSSL("OSSL_PARAM_BLD_push_*() for EC (group, point)");
return {};
}
if (d && !::OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_PRIV_KEY, d)) {
JWK_PARSE_ERROR_OSSL("OSSL_PARAM_BLD_push_BN() for EC (d)");
return {};
}
auto params = AutoCPointer(::OSSL_PARAM_BLD_to_param(bld), &OSSL_PARAM_free);
if (!params) {
JWK_PARSE_ERROR_OSSL("OSSL_PARAM_BLD_to_param() for EC");
return {};
}
auto pctx = AutoCPointer(::EVP_PKEY_CTX_new_from_name(nullptr, "EC", nullptr), &::EVP_PKEY_CTX_free);
if (!pctx) {
JWK_PARSE_ERROR_OSSL("EVP_PKEY_CTX_new_from_name(EC)");
return {};
}
if (1 != ::EVP_PKEY_fromdata_init(pctx)) {
JWK_PARSE_ERROR_OSSL("EVP_PKEY_fromdata_init() for EC");
return {};
}
auto pkey = std::add_pointer_t<EVP_PKEY>();
if (1 != ::EVP_PKEY_fromdata(pctx, &pkey, (d ? EVP_PKEY_KEYPAIR : EVP_PKEY_PUBLIC_KEY), params) || !pkey) {
JWK_PARSE_ERROR_OSSL("EVP_PKEY_fromdata() for EC");
return {};
}
auto pkeyAutoPtr = AutoCPointer(pkey, &::EVP_PKEY_free);
#else // USE_V3_API
auto key = AutoCPointer(::EC_KEY_new_by_curve_name(NID_X9_62_prime256v1), &::EC_KEY_free);
if (!key) {
JWK_PARSE_ERROR_OSSL("EC_KEY_new()");
return {};
}
if (d) {
if (1 != ::EC_KEY_set_private_key(key, d)) {
JWK_PARSE_ERROR_OSSL("EC_KEY_set_private_key()");
return {};
}
}
if (1 != ::EC_KEY_set_public_key_affine_coordinates(key, x, y)) {
JWK_PARSE_ERROR_OSSL("EC_KEY_set_public_key_affine_coordinates(key, x, y)");
return {};
}
auto pkey = AutoCPointer(::EVP_PKEY_new(), &::EVP_PKEY_free);
if (!pkey) {
JWK_PARSE_ERROR_OSSL("EVP_PKEY_new() for EC");
return {};
}
if (1 != EVP_PKEY_set1_EC_KEY(pkey, key)) {
JWK_PARSE_ERROR_OSSL("EVP_PKEY_set1_EC_KEY()");
return {};
}
#endif // USE_V3_API
if (d) {
auto len = ::i2d_PrivateKey(pkey, nullptr);
if (len <= 0) {
JWK_PARSE_ERROR_OSSL("i2d_PrivateKey() for EC");
return {};
}
auto buf = new (arena) uint8_t[len];
auto out = std::add_pointer_t<uint8_t>(buf);
len = ::i2d_PrivateKey(pkey, &out);
// assign through public API, even if it means some parsing overhead
return PrivateKey(DerEncoded{}, StringRef(buf, len));
} else {
auto len = ::i2d_PUBKEY(pkey, nullptr);
if (len <= 0) {
JWK_PARSE_ERROR_OSSL("i2d_PUBKEY() for EC");
return {};
}
auto buf = new (arena) uint8_t[len];
auto out = std::add_pointer_t<uint8_t>(buf);
len = ::i2d_PUBKEY(pkey, &out);
// assign through public API, even if it means some parsing overhead
return PublicKey(DerEncoded{}, StringRef(buf, len));
}
}
Optional<PublicOrPrivateKey> parseRsaKey(StringRef b64n,
StringRef b64e,
Optional<StringRef> b64d,
Optional<StringRef> b64p,
Optional<StringRef> b64q,
Optional<StringRef> b64dp,
Optional<StringRef> b64dq,
Optional<StringRef> b64qi,
int keyIndex) {
auto arena = Arena();
RSA_DECLARE_DECODED_REQUIRED_BN_MEMBER(n);
RSA_DECLARE_DECODED_REQUIRED_BN_MEMBER(e);
RSA_DECLARE_DECODED_OPTIONAL_BN_MEMBER(d);
RSA_DECLARE_DECODED_OPTIONAL_BN_MEMBER(p);
RSA_DECLARE_DECODED_OPTIONAL_BN_MEMBER(q);
RSA_DECLARE_DECODED_OPTIONAL_BN_MEMBER(dp);
RSA_DECLARE_DECODED_OPTIONAL_BN_MEMBER(dq);
RSA_DECLARE_DECODED_OPTIONAL_BN_MEMBER(qi);
auto const isPublic = !d || !p || !q || !dp || !dq || !qi;
#if USE_V3_API
// avoid deprecated, algo-specific API
auto bld = AutoCPointer(::OSSL_PARAM_BLD_new(), &::OSSL_PARAM_BLD_free);
if (!bld) {
JWK_PARSE_ERROR_OSSL("OSSL_PARAM_BLD_new() for EC");
return {};
}
if (!::OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_N, n) ||
!::OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_E, e)) {
JWK_PARSE_ERROR_OSSL("OSSL_PARAM_BLD_push_BN() for RSA (n, e)");
return {};
}
if (!isPublic) {
if (!::OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_D, d) ||
!::OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_FACTOR1, p) ||
!::OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_FACTOR2, q) ||
!::OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_EXPONENT1, dp) ||
!::OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_EXPONENT2, dq) ||
!::OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_COEFFICIENT1, qi)) {
JWK_PARSE_ERROR_OSSL("OSSL_PARAM_BLD_push_BN() for RSA (d, p, q, dp, dq, qi)");
return {};
}
}
auto params = AutoCPointer(::OSSL_PARAM_BLD_to_param(bld), &::OSSL_PARAM_free);
if (!params) {
JWK_PARSE_ERROR_OSSL("OSSL_PARAM_BLD_to_param() for RSA");
return {};
}
auto pctx = AutoCPointer(::EVP_PKEY_CTX_new_from_name(nullptr, "RSA", nullptr), &::EVP_PKEY_CTX_free);
if (!pctx) {
JWK_PARSE_ERROR_OSSL("EVP_PKEY_CTX_new_from_name(RSA)");
return {};
}
if (1 != ::EVP_PKEY_fromdata_init(pctx)) {
JWK_PARSE_ERROR_OSSL("EVP_PKEY_fromdata_init() for RSA");
return {};
}
auto pkey = std::add_pointer_t<EVP_PKEY>();
if (1 != ::EVP_PKEY_fromdata(pctx, &pkey, (!isPublic ? EVP_PKEY_KEYPAIR : EVP_PKEY_PUBLIC_KEY), params)) {
JWK_PARSE_ERROR_OSSL("EVP_PKEY_fromdata() for EC");
return {};
}
auto pkeyAutoPtr = AutoCPointer(pkey, &::EVP_PKEY_free);
#else // USE_V3_API
auto rsa = AutoCPointer(RSA_new(), &::RSA_free);
if (!rsa) {
JWK_PARSE_ERROR_OSSL("RSA_new()");
return {};
}
if (1 != ::RSA_set0_key(rsa, n, e, d)) {
JWK_PARSE_ERROR_OSSL("RSA_set0_key()");
return {};
}
// set0 == ownership taken by rsa, no need to free
n.release();
e.release();
d.release();
if (!isPublic) {
if (1 != ::RSA_set0_factors(rsa, p, q)) {
JWK_PARSE_ERROR_OSSL("RSA_set0_factors()");
return {};
}
p.release();
q.release();
if (1 != ::RSA_set0_crt_params(rsa, dp, dq, qi)) {
JWK_PARSE_ERROR_OSSL("RSA_set0_crt_params()");
return {};
}
dp.release();
dq.release();
qi.release();
}
auto pkey = AutoCPointer(::EVP_PKEY_new(), &::EVP_PKEY_free);
if (!pkey) {
JWK_PARSE_ERROR_OSSL("EVP_PKEY_new() for RSA");
return {};
}
if (1 != ::EVP_PKEY_set1_RSA(pkey, rsa)) {
JWK_PARSE_ERROR_OSSL("EVP_PKEY_set1_RSA()");
return {};
}
#endif // USE_V3_API
if (!isPublic) {
auto len = ::i2d_PrivateKey(pkey, nullptr);
if (len <= 0) {
JWK_PARSE_ERROR_OSSL("i2d_PrivateKey() for RSA");
return {};
}
auto buf = new (arena) uint8_t[len];
auto out = std::add_pointer_t<uint8_t>(buf);
len = ::i2d_PrivateKey(pkey, &out);
// assign through public API, even if it means some parsing overhead
return PrivateKey(DerEncoded{}, StringRef(buf, len));
} else {
auto len = ::i2d_PUBKEY(pkey, nullptr);
if (len <= 0) {
JWK_PARSE_ERROR_OSSL("i2d_PUBKEY() for RSA");
return {};
}
auto buf = new (arena) uint8_t[len];
auto out = std::add_pointer_t<uint8_t>(buf);
len = ::i2d_PUBKEY(pkey, &out);
// assign through public API, even if it means some parsing overhead
return PublicKey(DerEncoded{}, StringRef(buf, len));
}
}
template <class Value>
Optional<PublicOrPrivateKey> parseKey(const Value& key, StringRef kty, int keyIndex) {
if (kty == "EC"_sr) {
DECLARE_JWK_REQUIRED_STRING_MEMBER(key, alg);
if (alg != "ES256"_sr) {
JWK_PARSE_ERROR("Unsupported EC algorithm").detail("Algorithm", alg.toString());
return {};
}
DECLARE_JWK_REQUIRED_STRING_MEMBER(key, crv);
if (crv != "P-256"_sr) {
JWK_PARSE_ERROR("Unsupported EC curve").detail("Curve", crv.toString());
return {};
}
DECLARE_JWK_REQUIRED_STRING_MEMBER(key, x);
DECLARE_JWK_REQUIRED_STRING_MEMBER(key, y);
DECLARE_JWK_OPTIONAL_STRING_MEMBER(key, d);
return parseEcP256Key(x, y, d, keyIndex);
} else if (kty == "RSA"_sr) {
DECLARE_JWK_REQUIRED_STRING_MEMBER(key, alg);
if (alg != "RS256"_sr) {
JWK_PARSE_ERROR("Unsupported RSA algorithm").detail("Algorithm", alg.toString());
return {};
}
DECLARE_JWK_REQUIRED_STRING_MEMBER(key, n);
DECLARE_JWK_REQUIRED_STRING_MEMBER(key, e);
DECLARE_JWK_OPTIONAL_STRING_MEMBER(key, d);
DECLARE_JWK_OPTIONAL_STRING_MEMBER(key, p);
DECLARE_JWK_OPTIONAL_STRING_MEMBER(key, q);
DECLARE_JWK_OPTIONAL_STRING_MEMBER(key, dp);
DECLARE_JWK_OPTIONAL_STRING_MEMBER(key, dq);
DECLARE_JWK_OPTIONAL_STRING_MEMBER(key, qi);
auto privKeyArgs = 0;
privKeyArgs += d.present();
privKeyArgs += p.present();
privKeyArgs += q.present();
privKeyArgs += dp.present();
privKeyArgs += dq.present();
privKeyArgs += qi.present();
if (privKeyArgs == 0 || privKeyArgs == 6) {
return parseRsaKey(n, e, d, p, q, dp, dq, qi, keyIndex);
} else {
JWK_PARSE_ERROR("Private key arguments partially exist").detail("NumMissingArgs", 6 - privKeyArgs);
return {};
}
} else {
JWK_PARSE_ERROR("Unsupported key type").detail("KeyType", kty.toString());
return {};
}
}
bool encodeEcKey(rapidjson::Writer<rapidjson::StringBuffer>& writer,
StringRef keyName,
EVP_PKEY* pKey,
const bool isPublic) {
auto arena = Arena();
writer.StartObject();
writer.Key("kty");
writer.String("EC");
writer.Key("alg");
writer.String("ES256");
writer.Key("kid");
writer.String(reinterpret_cast<char const*>(keyName.begin()), keyName.size());
#if USE_V3_API
auto curveNameBuf = std::array<char, 64>{};
auto curveNameLen = 0ul;
if (1 != EVP_PKEY_get_utf8_string_param(
pKey, OSSL_PKEY_PARAM_GROUP_NAME, curveNameBuf.begin(), sizeof(curveNameBuf), &curveNameLen)) {
JWK_WRITE_ERROR_OSSL("Get group name from EC PKey");
return false;
}
auto curveName = std::string_view(curveNameBuf.cbegin(), curveNameLen);
if (curveName != std::string_view("prime256v1")) {
JWK_WRITE_ERROR("Unsupported EC curve").detail("CurveName", curveName);
return false;
}
writer.Key("crv");
writer.String("P-256");
#define JWK_WRITE_BN_EC_PARAM(x, param) \
do { \
auto x = AutoCPointer(nullptr, &::BN_clear_free); \
auto rawX = std::add_pointer_t<BIGNUM>(); \
if (1 != ::EVP_PKEY_get_bn_param(pKey, param, &rawX)) { \
JWK_WRITE_ERROR_OSSL("EVP_PKEY_get_bn_param(" #param ")"); \
return false; \
} \
x.reset(rawX); \
auto b64##x = bigNumToBase64Url(arena, x); \
writer.Key(#x); \
writer.String(reinterpret_cast<char const*>(b64##x.begin()), b64##x.size()); \
} while (0)
// Get and write affine coordinates, X and Y
JWK_WRITE_BN_EC_PARAM(x, OSSL_PKEY_PARAM_EC_PUB_X);
JWK_WRITE_BN_EC_PARAM(y, OSSL_PKEY_PARAM_EC_PUB_Y);
if (!isPublic) {
JWK_WRITE_BN_EC_PARAM(d, OSSL_PKEY_PARAM_PRIV_KEY);
}
#undef JWK_WRITE_BN_EC_PARAM
#else // USE_V3_API
auto ecKey = ::EVP_PKEY_get0_EC_KEY(pKey); // get0 == no refcount, no need to free
if (!ecKey) {
JWK_WRITE_ERROR_OSSL("Could not extract EC_KEY from EVP_PKEY");
return false;
}
auto group = ::EC_KEY_get0_group(ecKey);
if (!group) {
JWK_WRITE_ERROR("Could not get EC_GROUP from EVP_PKEY");
return false;
}
auto curveName = ::EC_GROUP_get_curve_name(group);
if (curveName == NID_undef) {
JWK_WRITE_ERROR("Could not match EC_GROUP to known curve");
return false;
}
if (curveName != NID_X9_62_prime256v1) {
JWK_WRITE_ERROR("Unsupported curve, expected P-256 (prime256v1)").detail("curveName", ::OBJ_nid2sn(curveName));
return false;
}
writer.Key("crv");
writer.String("P-256");
auto point = ::EC_KEY_get0_public_key(ecKey);
if (!point) {
JWK_WRITE_ERROR_OSSL("EC_KEY_get0_public_key() returned null");
return false;
}
auto x = AutoCPointer(::BN_new(), &::BN_free);
if (!x) {
JWK_WRITE_ERROR_OSSL("x = BN_new()");
return false;
}
auto y = AutoCPointer(::BN_new(), &::BN_free);
if (!y) {
JWK_WRITE_ERROR_OSSL("y = BN_new()");
return false;
}
if (1 !=
#ifdef OPENSSL_IS_BORINGSSL
::EC_POINT_get_affine_coordinates_GFp(group, point, x, y, nullptr)
#else
::EC_POINT_get_affine_coordinates(group, point, x, y, nullptr)
#endif
) {
JWK_WRITE_ERROR_OSSL("EC_POINT_get_affine_coordinates()");
return false;
}
auto b64X = bigNumToBase64Url(arena, x);
auto b64Y = bigNumToBase64Url(arena, y);
writer.Key("x");
writer.String(reinterpret_cast<char const*>(b64X.begin()), b64X.size());
writer.Key("y");
writer.String(reinterpret_cast<char const*>(b64Y.begin()), b64Y.size());
if (!isPublic) {
auto d = ::EC_KEY_get0_private_key(ecKey);
if (!d) {
JWK_WRITE_ERROR("EC_KEY_get0_private_key()");
return false;
}
auto b64D = bigNumToBase64Url(arena, d);
writer.Key("d");
writer.String(reinterpret_cast<char const*>(b64D.begin()), b64D.size());
}
#endif // USE_V3_API
writer.EndObject();
return true;
}
bool encodeRsaKey(rapidjson::Writer<rapidjson::StringBuffer>& writer,
StringRef keyName,
EVP_PKEY* pKey,
const bool isPublic) {
auto arena = Arena();
writer.StartObject();
writer.Key("kty");
writer.String("RSA");
writer.Key("alg");
writer.String("RS256");
writer.Key("kid");
writer.String(reinterpret_cast<char const*>(keyName.begin()), keyName.size());
#if USE_V3_API
#define JWK_WRITE_BN_RSA_PARAM_V3(x, param) \
do { \
auto x = AutoCPointer(nullptr, &::BN_clear_free); \
auto rawX = std::add_pointer_t<BIGNUM>(); \
if (1 != ::EVP_PKEY_get_bn_param(pKey, param, &rawX)) { \
JWK_WRITE_ERROR_OSSL("EVP_PKEY_get_bn_param(" #x ")"); \
return false; \
} \
x.reset(rawX); \
auto b64##x = bigNumToBase64Url(arena, x); \
writer.Key(#x); \
writer.String(reinterpret_cast<char const*>(b64##x.begin()), b64##x.size()); \
} while (0)
JWK_WRITE_BN_RSA_PARAM_V3(n, OSSL_PKEY_PARAM_RSA_N);
JWK_WRITE_BN_RSA_PARAM_V3(e, OSSL_PKEY_PARAM_RSA_E);
if (!isPublic) {
JWK_WRITE_BN_RSA_PARAM_V3(d, OSSL_PKEY_PARAM_RSA_D);
JWK_WRITE_BN_RSA_PARAM_V3(p, OSSL_PKEY_PARAM_RSA_FACTOR1);
JWK_WRITE_BN_RSA_PARAM_V3(q, OSSL_PKEY_PARAM_RSA_FACTOR2);
JWK_WRITE_BN_RSA_PARAM_V3(dp, OSSL_PKEY_PARAM_RSA_EXPONENT1);
JWK_WRITE_BN_RSA_PARAM_V3(dq, OSSL_PKEY_PARAM_RSA_EXPONENT2);
JWK_WRITE_BN_RSA_PARAM_V3(qi, OSSL_PKEY_PARAM_RSA_COEFFICIENT1);
}
#undef JWK_WRITE_BN_RSA_PARAM_V3
#else // USE_V3_API
#define JWK_WRITE_BN_RSA_PARAM_V1(x) \
do { \
if (!x) { \
JWK_WRITE_ERROR_OSSL("RSA_get0_* returned null " #x); \
return false; \
} \
auto b64##x = bigNumToBase64Url(arena, x); \
writer.Key(#x); \
writer.String(reinterpret_cast<char const*>(b64##x.begin()), b64##x.size()); \
} while (0)
auto rsaKey = ::EVP_PKEY_get0_RSA(pKey); // get0 == no refcount, no need to free
if (!rsaKey) {
JWK_WRITE_ERROR_OSSL("Could not extract RSA key from EVP_PKEY");
return false;
}
auto n = std::add_pointer_t<const BIGNUM>();
auto e = std::add_pointer_t<const BIGNUM>();
auto d = std::add_pointer_t<const BIGNUM>();
auto p = std::add_pointer_t<const BIGNUM>();
auto q = std::add_pointer_t<const BIGNUM>();
auto dp = std::add_pointer_t<const BIGNUM>();
auto dq = std::add_pointer_t<const BIGNUM>();
auto qi = std::add_pointer_t<const BIGNUM>();
::RSA_get0_key(rsaKey, &n, &e, &d);
JWK_WRITE_BN_RSA_PARAM_V1(n);
JWK_WRITE_BN_RSA_PARAM_V1(e);
if (!isPublic) {
::RSA_get0_factors(rsaKey, &p, &q);
::RSA_get0_crt_params(rsaKey, &dp, &dq, &qi);
JWK_WRITE_BN_RSA_PARAM_V1(d);
JWK_WRITE_BN_RSA_PARAM_V1(p);
JWK_WRITE_BN_RSA_PARAM_V1(q);
JWK_WRITE_BN_RSA_PARAM_V1(dp);
JWK_WRITE_BN_RSA_PARAM_V1(dq);
JWK_WRITE_BN_RSA_PARAM_V1(qi);
}
#undef JWK_WRITE_BN_RSA_PARAM_V1
#endif // USE_V3_API
writer.EndObject();
return true;
}
// Add exactly one object to context of writer. Object shall contain JWK-encoded public or private key
bool encodeKey(rapidjson::Writer<rapidjson::StringBuffer>& writer, StringRef keyName, const PublicOrPrivateKey& key) {
auto const isPublic = key.isPublic();
auto pKey = std::add_pointer_t<EVP_PKEY>();
auto alg = PKeyAlgorithm{};
if (isPublic) {
auto const& keyObj = key.getPublic();
pKey = keyObj.nativeHandle();
alg = keyObj.algorithm();
} else {
auto const& keyObj = key.getPrivate();
pKey = key.getPrivate().nativeHandle();
alg = keyObj.algorithm();
}
if (!pKey) {
JWK_WRITE_ERROR("PKey object to encode is null");
return false;
}
if (alg == PKeyAlgorithm::EC) {
return encodeEcKey(writer, keyName, pKey, isPublic);
} else if (alg == PKeyAlgorithm::RSA) {
return encodeRsaKey(writer, keyName, pKey, isPublic);
} else {
JWK_WRITE_ERROR("Attempted to encode PKey with unsupported algorithm");
return false;
}
return true;
}
void testPublicKey(PrivateKey (*factory)()) {
// stringify-deserialize public key.
// sign some data using private key to see whether deserialized public key can verify it.
auto& rng = *deterministicRandom();
auto pubKeyName = Standalone<StringRef>("somePublicKey"_sr);
auto privKey = factory();
auto pubKey = privKey.toPublic();
auto jwks = JsonWebKeySet{};
jwks.keys.emplace(pubKeyName, pubKey);
auto arena = Arena();
auto jwksStr = jwks.toStringRef(arena).get();
fmt::print("Test JWKS: {}\n", jwksStr.toString());
auto jwksClone = JsonWebKeySet::parse(jwksStr, {});
ASSERT(jwksClone.present());
auto pubKeyClone = jwksClone.get().keys[pubKeyName].getPublic();
auto randByteStr = [&rng, &arena](int len) {
auto buf = new (arena) uint8_t[len];
for (auto i = 0; i < len; i++)
buf[i] = rng.randomUInt32() % 255u;
return StringRef(buf, len);
};
auto randData = randByteStr(rng.randomUInt32() % 128 + 16);
auto signature = privKey.sign(arena, randData, *::EVP_sha256());
ASSERT(pubKeyClone.verify(randData, signature, *::EVP_sha256()));
const_cast<uint8_t&>(*randData.begin())++;
ASSERT(!pubKeyClone.verify(randData, signature, *::EVP_sha256()));
fmt::print("TESTED OK FOR OPENSSL V{} API\n", (OPENSSL_VERSION_NUMBER >> 28));
}
void testPrivateKey(PrivateKey (*factory)()) {
// stringify-deserialize private key.
// sign some data using deserialized private key to see whether public key can verify it.
auto& rng = *deterministicRandom();
auto privKeyName = Standalone<StringRef>("somePrivateKey"_sr);
auto privKey = factory();
auto pubKey = privKey.toPublic();
auto jwks = JsonWebKeySet{};
jwks.keys.emplace(privKeyName, privKey);
auto arena = Arena();
auto jwksStr = jwks.toStringRef(arena).get();
fmt::print("Test JWKS: {}\n", jwksStr.toString());
auto jwksClone = JsonWebKeySet::parse(jwksStr, {});
ASSERT(jwksClone.present());
auto privKeyClone = jwksClone.get().keys[privKeyName].getPrivate();
auto randByteStr = [&rng, &arena](int len) {
auto buf = new (arena) uint8_t[len];
for (auto i = 0; i < len; i++)
buf[i] = rng.randomUInt32() % 255u;
return StringRef(buf, len);
};
auto randData = randByteStr(rng.randomUInt32() % 128 + 16);
auto signature = privKeyClone.sign(arena, randData, *::EVP_sha256());
ASSERT(pubKey.verify(randData, signature, *::EVP_sha256()));
const_cast<uint8_t&>(*randData.begin())++;
ASSERT(!pubKey.verify(randData, signature, *::EVP_sha256()));
fmt::print("TESTED OK FOR OPENSSL V{} API\n", (OPENSSL_VERSION_NUMBER >> 28));
}
} // anonymous namespace
Optional<JsonWebKeySet> JsonWebKeySet::parse(StringRef jwksString, VectorRef<StringRef> allowedUses) {
auto d = rapidjson::Document();
d.Parse(reinterpret_cast<const char*>(jwksString.begin()), jwksString.size());
if (d.HasParseError()) {
JWKS_PARSE_ERROR("ParseError")
.detail("Message", GetParseError_En(d.GetParseError()))
.detail("Offset", d.GetErrorOffset());
return {};
}
auto keysItr = d.FindMember("keys");
if (!d.IsObject() || keysItr == d.MemberEnd() || !keysItr->value.IsArray()) {
JWKS_PARSE_ERROR("JWKS must be an object and have 'keys' array member");
return {};
}
auto const& keys = keysItr->value;
auto ret = JsonWebKeySet{};
for (auto keyIndex = 0; keyIndex < keys.Size(); keyIndex++) {
if (!keys[keyIndex].IsObject()) {
JWKS_PARSE_ERROR("element of 'keys' array must be an object");
return {};
}
auto const& key = keys[keyIndex];
DECLARE_JWK_REQUIRED_STRING_MEMBER(key, kty);
DECLARE_JWK_REQUIRED_STRING_MEMBER(key, kid);
DECLARE_JWK_OPTIONAL_STRING_MEMBER(key, use);
if (use.present() && !allowedUses.empty()) {
auto allowed = false;
for (auto allowedUse : allowedUses) {
if (allowedUse == use.get()) {
allowed = true;
break;
}
}
if (!allowed) {
JWK_PARSE_ERROR("Illegal optional 'use' member found").detail("Use", use.get().toString());
return {};
}
}
auto parsedKey = parseKey(key, kty, keyIndex);
if (!parsedKey.present())
return {};
auto [iter, inserted] = ret.keys.insert({ Standalone<StringRef>(kid), parsedKey.get() });
if (!inserted) {
JWK_PARSE_ERROR("Duplicate key name").detail("KeyName", kid.toString());
return {};
}
}
return ret;
}
Optional<StringRef> JsonWebKeySet::toStringRef(Arena& arena) {
using Buffer = rapidjson::StringBuffer;
using Writer = rapidjson::Writer<Buffer>;
auto buffer = Buffer();
auto writer = Writer(buffer);
writer.StartObject();
writer.Key("keys");
writer.StartArray();
for (const auto& [keyName, key] : keys) {
if (!encodeKey(writer, keyName, key)) {
return {};
}
}
writer.EndArray();
writer.EndObject();
auto buf = new (arena) uint8_t[buffer.GetSize()];
::memcpy(buf, buffer.GetString(), buffer.GetSize());
return StringRef(buf, buffer.GetSize());
}
void forceLinkJsonWebKeySetTests() {}
TEST_CASE("/fdbrpc/JsonWebKeySet/EC/PublicKey") {
testPublicKey(&mkcert::makeEcP256);
return Void();
}
TEST_CASE("/fdbrpc/JsonWebKeySet/EC/PrivateKey") {
testPrivateKey(&mkcert::makeEcP256);
return Void();
}
TEST_CASE("/fdbrpc/JsonWebKeySet/RSA/PublicKey") {
testPublicKey(&mkcert::makeRsa4096Bit);
return Void();
}
TEST_CASE("/fdbrpc/JsonWebKeySet/RSA/PrivateKey") {
testPrivateKey(&mkcert::makeRsa4096Bit);
return Void();
}
TEST_CASE("/fdbrpc/JsonWebKeySet/Empty") {
auto keyset = JsonWebKeySet::parse("{\"keys\":[]}"_sr, {});
ASSERT(keyset.present());
ASSERT(keyset.get().keys.empty());
return Void();
}