diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ecda33b7edb..571b177de735 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Updated Open Enclave to [0.19.2](https://github.com/openenclave/openenclave/releases/tag/v0.19.2). - Updated Open Enclave to [0.19.3](https://github.com/openenclave/openenclave/releases/tag/v0.19.3). - Expose COSESign1 `content` for `user_cose_sign1` authenticated endpoints in JavaScript/TypeScript apps (#5465). +- SGX builds now use OpenSSL 3.1.1 by default (#5481). - Add HMAC support to JS API. Call with `ccf.crypto.sign({"name": "HMAC", "hash": "SHA-256"}, key, data)`. ## [4.0.5] diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ab634da1dcc..e2a77081a231 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -247,9 +247,6 @@ set(CCF_ENDPOINTS_SOURCES ${CCF_DIR}/src/node/receipt.cpp ) -find_library(CRYPTO_LIBRARY crypto) -find_library(TLS_LIBRARY ssl) - include(${CCF_DIR}/cmake/crypto.cmake) include(${CCF_DIR}/cmake/quickjs.cmake) include(${CCF_DIR}/cmake/sss.cmake) diff --git a/cmake/crypto.cmake b/cmake/crypto.cmake index b3a6409f7fde..35f0f21b7541 100644 --- a/cmake/crypto.cmake +++ b/cmake/crypto.cmake @@ -54,10 +54,14 @@ elseif(COMPILE_TARGET STREQUAL "snp") ) endif() +find_library(CRYPTO_LIBRARY crypto) +find_library(TLS_LIBRARY ssl) + add_library(ccfcrypto.host STATIC ${CCFCRYPTO_SRC}) add_san(ccfcrypto.host) target_compile_options(ccfcrypto.host PUBLIC ${COMPILE_LIBCXX}) target_link_options(ccfcrypto.host PUBLIC ${LINK_LIBCXX}) + target_link_libraries(ccfcrypto.host PUBLIC qcbor.host) target_link_libraries(ccfcrypto.host PUBLIC t_cose.host) target_link_libraries(ccfcrypto.host PUBLIC crypto) diff --git a/cmake/open_enclave.cmake b/cmake/open_enclave.cmake index 9e1a99cd1249..b6e5a84bf3ec 100644 --- a/cmake/open_enclave.cmake +++ b/cmake/open_enclave.cmake @@ -15,15 +15,20 @@ if(REQUIRE_OPENENCLAVE) # Find OpenEnclave package find_package(OpenEnclave 0.19.3 CONFIG REQUIRED) + option(USE_OPENSSL_3 "Use OpenSSL 3.x for Open Enclave builds" ON) + if(USE_OPENSSL_3) + set(OE_OPENSSL_LIBRARY openenclave::oecryptoopenssl_3) + else() + set(OE_OPENSSL_LIBRARY openenclave::oecryptoopenssl) + endif() # As well as pulling in openenclave:: targets, this sets variables which can # be used for our edge cases (eg - for virtual libraries). These do not follow # the standard naming patterns, for example use OE_INCLUDEDIR rather than # OpenEnclave_INCLUDE_DIRS if(COMPILE_TARGET STREQUAL "sgx") set(OE_TARGET_LIBC openenclave::oelibc) - set(OE_TARGET_ENCLAVE_AND_STD - openenclave::oeenclave openenclave::oelibcxx openenclave::oelibc - openenclave::oecryptoopenssl + set(OE_TARGET_ENCLAVE_AND_STD openenclave::oeenclave openenclave::oelibcxx + openenclave::oelibc ${OE_OPENSSL_LIBRARY} ) # These oe libraries must be linked in specific order diff --git a/cmake/t_cose.cmake b/cmake/t_cose.cmake index 18dfc2a94c30..b103164388fb 100644 --- a/cmake/t_cose.cmake +++ b/cmake/t_cose.cmake @@ -26,7 +26,7 @@ if(COMPILE_TARGET STREQUAL "sgx") target_link_libraries(t_cose.enclave PUBLIC qcbor.enclave) # This is needed to get the OpenSSL includes from Open Enclave - target_link_libraries(t_cose.enclave PRIVATE openenclave::oecryptoopenssl) + target_link_libraries(t_cose.enclave PRIVATE ${OE_OPENSSL_LIBRARY}) install( TARGETS t_cose.enclave diff --git a/src/clients/tls_client.h b/src/clients/tls_client.h index 19dbf12daa50..2972b25dcc9b 100644 --- a/src/clients/tls_client.h +++ b/src/clients/tls_client.h @@ -142,7 +142,12 @@ namespace client init(); } - virtual ~TlsClient() {} + virtual ~TlsClient() + { + SSL* ssl; + BIO_get_ssl(bio, &ssl); + SSL_shutdown(ssl); + } auto get_ciphersuite_name() { @@ -207,28 +212,7 @@ namespace client std::vector read_all() { constexpr auto read_size = 4096; - std::vector buf(read_size); - auto ret = 0; - do - { - ret = BIO_read(bio, buf.data(), buf.size()); - } while (ret < 0 && BIO_should_retry(bio)); - - if (ret > 0) - { - buf.resize(ret); - } - else if (ret == 0) - { - connected = false; - throw std::logic_error("Underlying transport closed"); - } - else - { - throw std::logic_error(error_string(ERR_get_error())); - } - - return buf; + return read(read_size); } void set_tcp_nodelay(bool on) diff --git a/src/crypto/key_wrap.cpp b/src/crypto/key_wrap.cpp index b7c7b9963238..9868a96308ac 100644 --- a/src/crypto/key_wrap.cpp +++ b/src/crypto/key_wrap.cpp @@ -9,6 +9,7 @@ #include "openssl/symmetric_key.h" #include +#include #include #include diff --git a/src/crypto/openssl/cose_verifier.cpp b/src/crypto/openssl/cose_verifier.cpp index 662295544929..5c5666655f3e 100644 --- a/src/crypto/openssl/cose_verifier.cpp +++ b/src/crypto/openssl/cose_verifier.cpp @@ -39,7 +39,11 @@ namespace crypto EVP_PKEY* pk = X509_get_pubkey(cert); +#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 + if (EVP_PKEY_get_base_id(pk) == EVP_PKEY_EC) +#else if (EVP_PKEY_get0_EC_KEY(pk)) +#endif { public_key = std::make_shared(pk); } diff --git a/src/crypto/openssl/key_pair.cpp b/src/crypto/openssl/key_pair.cpp index fd509540927c..371da7ae17f3 100644 --- a/src/crypto/openssl/key_pair.cpp +++ b/src/crypto/openssl/key_pair.cpp @@ -17,10 +17,15 @@ #include #include #include +#include #include #include #include +#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 +# include +#endif + namespace crypto { using namespace OpenSSL; @@ -67,16 +72,36 @@ namespace crypto KeyPair_OpenSSL::KeyPair_OpenSSL(const JsonWebKeyECPrivate& jwk) { - auto ec_key = PublicKey_OpenSSL::ec_key_public_from_jwk(jwk); - + key = EVP_PKEY_new(); Unique_BIGNUM d; auto d_raw = raw_from_b64url(jwk.d); OpenSSL::CHECKNULL(BN_bin2bn(d_raw.data(), d_raw.size(), d)); - +#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 + auto nid = get_openssl_group_id(jwk_curve_to_curve_id(jwk.crv)); + // Note: d_raw is big endian while OSSL_PARAM_construct_BN expects native + // endianness + std::vector d_raw_native(d_raw.size()); + CHECKPOSITIVE(BN_bn2nativepad(d, d_raw_native.data(), d_raw_native.size())); + + auto pub_buf = PublicKey_OpenSSL::ec_point_public_from_jwk(jwk); + + OSSL_PARAM params[4]; + params[0] = OSSL_PARAM_construct_utf8_string( + OSSL_PKEY_PARAM_GROUP_NAME, (char*)OSSL_EC_curve_nid2name(nid), 0); + params[1] = OSSL_PARAM_construct_octet_string( + OSSL_PKEY_PARAM_PUB_KEY, pub_buf.data(), pub_buf.size()); + params[2] = OSSL_PARAM_construct_BN( + OSSL_PKEY_PARAM_PRIV_KEY, d_raw_native.data(), d_raw_native.size()); + params[3] = OSSL_PARAM_construct_end(); + + Unique_EVP_PKEY_CTX pctx("EC"); + CHECK1(EVP_PKEY_fromdata_init(pctx)); + CHECK1(EVP_PKEY_fromdata(pctx, &key, EVP_PKEY_KEYPAIR, params)); +#else + auto ec_key = PublicKey_OpenSSL::ec_key_public_from_jwk(jwk); CHECK1(EC_KEY_set_private_key(ec_key, d)); - - key = EVP_PKEY_new(); CHECK1(EVP_PKEY_set1_EC_KEY(key, ec_key)); +#endif } Pem KeyPair_OpenSSL::private_key_pem() const @@ -458,10 +483,16 @@ namespace crypto // As per https://www.openssl.org/docs/man1.0.2/man3/BN_num_bytes.html, size // should not be calculated with BN_num_bytes(d)! size_t size = EVP_PKEY_bits(key) / 8; - Unique_EC_KEY eckey(EVP_PKEY_get1_EC_KEY(key)); - const BIGNUM* d = EC_KEY_get0_private_key(eckey); - std::vector bytes(size); + Unique_BIGNUM d; +#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 + BIGNUM* bn_d = NULL; + CHECK1(EVP_PKEY_get_bn_param(key, OSSL_PKEY_PARAM_PRIV_KEY, &bn_d)); + d.reset(bn_d); +#else + Unique_EC_KEY eckey(EVP_PKEY_get1_EC_KEY(key)); + d = EC_KEY_get0_private_key(eckey); +#endif auto rc = BN_bn2binpad(d, bytes.data(), size); if (rc != size) { diff --git a/src/crypto/openssl/openssl_wrappers.h b/src/crypto/openssl/openssl_wrappers.h index c6bcbbb9adb7..54b21e31d3f3 100644 --- a/src/crypto/openssl/openssl_wrappers.h +++ b/src/crypto/openssl/openssl_wrappers.h @@ -5,20 +5,27 @@ #include "ccf/crypto/pem.h" #define FMT_HEADER_ONLY + #include #include #include #include #include +#include #include #include #include #include #include +#include #include #include #include +#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 +# include +#endif + namespace crypto { namespace OpenSSL @@ -193,6 +200,12 @@ namespace crypto Unique_SSL_OBJECT( PEM_read_bio_PUBKEY(mem, NULL, NULL, NULL), EVP_PKEY_free) {} + +#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 + Unique_PKEY(EVP_PKEY* pkey) : + Unique_SSL_OBJECT(EVP_PKEY_dup(pkey), EVP_PKEY_free) + {} +#endif }; struct Unique_EVP_PKEY_CTX @@ -205,6 +218,14 @@ namespace crypto Unique_SSL_OBJECT( EVP_PKEY_CTX_new_id(key_type, NULL), EVP_PKEY_CTX_free) {} + +#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 + Unique_EVP_PKEY_CTX(const std::string& name) : + Unique_SSL_OBJECT( + EVP_PKEY_CTX_new_from_name(NULL, name.c_str(), NULL), + EVP_PKEY_CTX_free) + {} +#endif }; struct Unique_EVP_MD_CTX @@ -309,6 +330,8 @@ namespace crypto struct Unique_BIGNUM : public Unique_SSL_OBJECT { using Unique_SSL_OBJECT::Unique_SSL_OBJECT; + + Unique_BIGNUM(const BIGNUM* n) : Unique_BIGNUM(BN_dup(n), BN_free) {} }; struct Unique_X509_TIME @@ -357,6 +380,7 @@ namespace crypto {} }; +#if !(defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3) struct Unique_EC_KEY : public Unique_SSL_OBJECT { Unique_EC_KEY(int nid) : @@ -372,6 +396,7 @@ namespace crypto { using Unique_SSL_OBJECT::Unique_SSL_OBJECT; }; +#endif struct Unique_EVP_ENCODE_CTX : public Unique_SSL_OBJECT< EVP_ENCODE_CTX, diff --git a/src/crypto/openssl/public_key.cpp b/src/crypto/openssl/public_key.cpp index 265eae3d8144..20fb5d0db89e 100644 --- a/src/crypto/openssl/public_key.cpp +++ b/src/crypto/openssl/public_key.cpp @@ -18,6 +18,11 @@ #include #include +#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 +# include +# include +#endif + namespace crypto { using namespace OpenSSL; @@ -44,7 +49,7 @@ namespace crypto } } - Unique_EC_KEY PublicKey_OpenSSL::ec_key_public_from_jwk( + std::pair get_components( const JsonWebKeyECPublic& jwk) { if (jwk.kty != JsonWebKeyType::EC) @@ -52,24 +57,68 @@ namespace crypto throw std::logic_error("Cannot construct public key from non-EC JWK"); } - auto nid = get_openssl_group_id(jwk_curve_to_curve_id(jwk.crv)); - - Unique_BIGNUM x, y; + std::pair xy; auto x_raw = raw_from_b64url(jwk.x); auto y_raw = raw_from_b64url(jwk.y); - OpenSSL::CHECKNULL(BN_bin2bn(x_raw.data(), x_raw.size(), x)); - OpenSSL::CHECKNULL(BN_bin2bn(y_raw.data(), y_raw.size(), y)); + OpenSSL::CHECKNULL(BN_bin2bn(x_raw.data(), x_raw.size(), xy.first)); + OpenSSL::CHECKNULL(BN_bin2bn(y_raw.data(), y_raw.size(), xy.second)); + + return xy; + } + +#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 + std::vector PublicKey_OpenSSL::ec_point_public_from_jwk( + const JsonWebKeyECPublic& jwk) + { + auto nid = get_openssl_group_id(jwk_curve_to_curve_id(jwk.crv)); + auto [x, y] = get_components(jwk); + + Unique_BN_CTX bn_ctx; + Unique_EC_GROUP group(nid); + Unique_EC_POINT p(group); + CHECK1(EC_POINT_set_affine_coordinates(group, p, x, y, bn_ctx)); + size_t buf_size = EC_POINT_point2oct( + group, p, POINT_CONVERSION_UNCOMPRESSED, nullptr, 0, bn_ctx); + std::vector buf(buf_size); + CHECKPOSITIVE(EC_POINT_point2oct( + group, p, POINT_CONVERSION_UNCOMPRESSED, buf.data(), buf.size(), bn_ctx)); + return buf; + } +#else + Unique_EC_KEY PublicKey_OpenSSL::ec_key_public_from_jwk( + const JsonWebKeyECPublic& jwk) + { + auto nid = get_openssl_group_id(jwk_curve_to_curve_id(jwk.crv)); + auto [x, y] = get_components(jwk); Unique_EC_KEY ec_key(nid); CHECK1(EC_KEY_set_public_key_affine_coordinates(ec_key, x, y)); return ec_key; } +#endif PublicKey_OpenSSL::PublicKey_OpenSSL(const JsonWebKeyECPublic& jwk) { key = EVP_PKEY_new(); +#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 + auto nid = get_openssl_group_id(jwk_curve_to_curve_id(jwk.crv)); + auto buf = ec_point_public_from_jwk(jwk); + + OSSL_PARAM params[3]; + params[0] = OSSL_PARAM_construct_utf8_string( + OSSL_PKEY_PARAM_GROUP_NAME, (char*)OSSL_EC_curve_nid2name(nid), 0); + params[1] = OSSL_PARAM_construct_octet_string( + OSSL_PKEY_PARAM_PUB_KEY, buf.data(), buf.size()); + params[2] = OSSL_PARAM_construct_end(); + + Unique_EVP_PKEY_CTX pctx("EC"); + CHECK1(EVP_PKEY_fromdata_init(pctx)); + CHECK1(EVP_PKEY_fromdata(pctx, &key, EVP_PKEY_PUBLIC_KEY, params)); +#else CHECK1(EVP_PKEY_set1_EC_KEY(key, ec_key_public_from_jwk(jwk))); +#endif } + PublicKey_OpenSSL::PublicKey_OpenSSL(EVP_PKEY* key) : key(key) {} PublicKey_OpenSSL::~PublicKey_OpenSSL() @@ -82,8 +131,7 @@ namespace crypto CurveID PublicKey_OpenSSL::get_curve_id() const { - int nid = - EC_GROUP_get_curve_name(EC_KEY_get0_group(EVP_PKEY_get0_EC_KEY(key))); + int nid = get_openssl_group_id(); switch (nid) { case NID_secp384r1: @@ -100,8 +148,33 @@ namespace crypto int PublicKey_OpenSSL::get_openssl_group_id() const { +#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 + size_t gname_len = 0; + CHECK1(EVP_PKEY_get_group_name(key, NULL, 0, &gname_len)); + std::string gname(gname_len + 1, 0); + CHECK1(EVP_PKEY_get_group_name( + key, (char*)gname.data(), gname.size(), &gname_len)); + gname.resize(gname_len); + if (gname == SN_secp384r1) + { + return NID_secp384r1; + } + else if (gname == SN_X9_62_prime256v1) + { + return NID_X9_62_prime256v1; + } + else if (gname == SN_secp256k1) + { + return NID_secp256k1; + } + else + { + throw std::runtime_error(fmt::format("Unknown OpenSSL group {}", gname)); + } +#else return EC_GROUP_get_curve_name( EC_KEY_get0_group(EVP_PKEY_get0_EC_KEY(key))); +#endif } int PublicKey_OpenSSL::get_openssl_group_id(CurveID gid) @@ -213,32 +286,63 @@ namespace crypto Unique_PKEY key_from_raw_ec_point(const std::vector& raw, int nid) { - // To extract a raw encoding of the EC point, OpenSSL has i2d_PublicKey, - // but the converse in d2i_PublicKey is useless until we switch to 3.0 - // (see also https://github.com/openssl/openssl/issues/16989). - // So, instead we reconstruct the key the long way round. +#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 + const unsigned char* pp = raw.data(); + EVP_PKEY* pkey = NULL; + OSSL_PARAM params[2]; + params[0] = OSSL_PARAM_construct_utf8_string( + OSSL_PKEY_PARAM_GROUP_NAME, (char*)OSSL_EC_curve_nid2name(nid), 0); + params[1] = OSSL_PARAM_construct_end(); + + Unique_EVP_PKEY_CTX pctx("EC"); + EVP_PKEY_fromdata_init(pctx); + EVP_PKEY_fromdata( + pctx, &pkey, OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS, params); + + pkey = d2i_PublicKey(EVP_PKEY_EC, &pkey, &pp, raw.size()); + if (pkey == NULL) + { + EVP_PKEY_free(pkey); + throw std::logic_error("Error loading public key"); + } + Unique_PKEY pk(pkey); + EVP_PKEY_up_ref(pk); + EVP_PKEY_free(pkey); + return pk; +#else Unique_BN_CTX bn_ctx; Unique_EC_GROUP group(nid); Unique_EC_POINT p(group); CHECK1(EC_POINT_oct2point(group, p, raw.data(), raw.size(), bn_ctx)); Unique_EC_KEY ec_key(nid); - CHECK1(EC_KEY_set_public_key(ec_key, p)); + Unique_PKEY pk; + CHECK1(EC_KEY_set_public_key(ec_key, p)); CHECK1(EVP_PKEY_set1_EC_KEY(pk, ec_key)); EVP_PKEY_up_ref(pk); return pk; +#endif } PublicKey::Coordinates PublicKey_OpenSSL::coordinates() const { + Coordinates r; + Unique_BIGNUM x, y; + Unique_EC_GROUP group(get_openssl_group_id()); +#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 + BIGNUM* bn_x = NULL; + BIGNUM* bn_y = NULL; + CHECK1(EVP_PKEY_get_bn_param(key, OSSL_PKEY_PARAM_EC_PUB_X, &bn_x)); + x.reset(bn_x); + CHECK1(EVP_PKEY_get_bn_param(key, OSSL_PKEY_PARAM_EC_PUB_Y, &bn_y)); + y.reset(bn_y); +#else Unique_EC_KEY eckey(EVP_PKEY_get1_EC_KEY(key)); const EC_POINT* p = EC_KEY_get0_public_key(eckey); - Unique_EC_GROUP group(get_openssl_group_id()); Unique_BN_CTX bn_ctx; - Unique_BIGNUM x, y; CHECK1(EC_POINT_get_affine_coordinates(group, p, x, y, bn_ctx)); - Coordinates r; +#endif int sz = EC_GROUP_get_degree(group) / 8; r.x.resize(sz); r.y.resize(sz); diff --git a/src/crypto/openssl/public_key.h b/src/crypto/openssl/public_key.h index a42dbf86b078..bc77b80f2f6f 100644 --- a/src/crypto/openssl/public_key.h +++ b/src/crypto/openssl/public_key.h @@ -18,8 +18,13 @@ namespace crypto EVP_PKEY* key = nullptr; PublicKey_OpenSSL(); +#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 + std::vector ec_point_public_from_jwk( + const JsonWebKeyECPublic& jwk); +#else OpenSSL::Unique_EC_KEY ec_key_public_from_jwk( const JsonWebKeyECPublic& jwk); +#endif public: PublicKey_OpenSSL(PublicKey_OpenSSL&& key) = default; diff --git a/src/crypto/openssl/rsa_key_pair.cpp b/src/crypto/openssl/rsa_key_pair.cpp index 903115d53788..affb5f1056de 100644 --- a/src/crypto/openssl/rsa_key_pair.cpp +++ b/src/crypto/openssl/rsa_key_pair.cpp @@ -6,6 +6,10 @@ #include "crypto/openssl/hash.h" #include "openssl_wrappers.h" +#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 +# include +#endif + namespace crypto { using namespace OpenSSL; @@ -13,16 +17,21 @@ namespace crypto RSAKeyPair_OpenSSL::RSAKeyPair_OpenSSL( size_t public_key_size, size_t public_exponent) { - RSA* rsa; - BIGNUM* big_exp = NULL; - OpenSSL::CHECKNULL(big_exp = BN_new()); - OpenSSL::CHECK1(BN_set_word(big_exp, public_exponent)); - OpenSSL::CHECKNULL(rsa = RSA_new()); - OpenSSL::CHECK1(RSA_generate_key_ex(rsa, public_key_size, big_exp, NULL)); - OpenSSL::CHECKNULL(key = EVP_PKEY_new()); - OpenSSL::CHECK1(EVP_PKEY_set1_RSA(key, rsa)); - BN_free(big_exp); - RSA_free(rsa); + CHECKNULL(key = EVP_PKEY_new()); + Unique_BIGNUM big_exp; + CHECK1(BN_set_word(big_exp, public_exponent)); + +#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 + Unique_EVP_PKEY_CTX pctx("RSA"); + CHECK1(EVP_PKEY_keygen_init(pctx)); + CHECKPOSITIVE(EVP_PKEY_CTX_set_rsa_keygen_bits(pctx, public_key_size)); + CHECKPOSITIVE(EVP_PKEY_CTX_set1_rsa_keygen_pubexp(pctx, big_exp)); + CHECK1(EVP_PKEY_generate(pctx, &key)); +#else + Unique_RSA rsa; + CHECK1(RSA_generate_key_ex(rsa, public_key_size, big_exp, NULL)); + CHECK1(EVP_PKEY_set1_RSA(key, rsa)); +#endif } RSAKeyPair_OpenSSL::RSAKeyPair_OpenSSL(EVP_PKEY* k) : @@ -41,7 +50,7 @@ namespace crypto RSAKeyPair_OpenSSL::RSAKeyPair_OpenSSL(const JsonWebKeyRSAPrivate& jwk) { - auto rsa = RSAPublicKey_OpenSSL::rsa_public_from_jwk(jwk); + key = EVP_PKEY_new(); Unique_BIGNUM d, p, q, dp, dq, qi; auto d_raw = raw_from_b64url(jwk.d); @@ -51,13 +60,64 @@ namespace crypto auto dq_raw = raw_from_b64url(jwk.dq); auto qi_raw = raw_from_b64url(jwk.qi); - OpenSSL::CHECKNULL(BN_bin2bn(d_raw.data(), d_raw.size(), d)); - OpenSSL::CHECKNULL(BN_bin2bn(p_raw.data(), p_raw.size(), p)); - OpenSSL::CHECKNULL(BN_bin2bn(q_raw.data(), q_raw.size(), q)); - OpenSSL::CHECKNULL(BN_bin2bn(dp_raw.data(), dp_raw.size(), dp)); - OpenSSL::CHECKNULL(BN_bin2bn(dq_raw.data(), dq_raw.size(), dq)); - OpenSSL::CHECKNULL(BN_bin2bn(qi_raw.data(), qi_raw.size(), qi)); + CHECKNULL(BN_bin2bn(d_raw.data(), d_raw.size(), d)); + CHECKNULL(BN_bin2bn(p_raw.data(), p_raw.size(), p)); + CHECKNULL(BN_bin2bn(q_raw.data(), q_raw.size(), q)); + CHECKNULL(BN_bin2bn(dp_raw.data(), dp_raw.size(), dp)); + CHECKNULL(BN_bin2bn(dq_raw.data(), dq_raw.size(), dq)); + CHECKNULL(BN_bin2bn(qi_raw.data(), qi_raw.size(), qi)); + +#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 + // Note: raw vectors are big endians while OSSL_PARAM_construct_BN expects + // native endianness + std::vector d_raw_native(d_raw.size()); + std::vector p_raw_native(p_raw.size()); + std::vector q_raw_native(q_raw.size()); + std::vector dp_raw_native(dp_raw.size()); + std::vector dq_raw_native(dq_raw.size()); + std::vector qi_raw_native(qi_raw.size()); + CHECKPOSITIVE(BN_bn2nativepad(d, d_raw_native.data(), d_raw_native.size())); + CHECKPOSITIVE(BN_bn2nativepad(p, p_raw_native.data(), p_raw_native.size())); + CHECKPOSITIVE(BN_bn2nativepad(q, q_raw_native.data(), q_raw_native.size())); + CHECKPOSITIVE( + BN_bn2nativepad(dp, dp_raw_native.data(), dp_raw_native.size())); + CHECKPOSITIVE( + BN_bn2nativepad(dq, dq_raw_native.data(), dq_raw_native.size())); + CHECKPOSITIVE( + BN_bn2nativepad(qi, qi_raw_native.data(), qi_raw_native.size())); + + auto [n_raw, e_raw] = RSAPublicKey_OpenSSL::rsa_public_raw_from_jwk(jwk); + OSSL_PARAM params[9]; + params[0] = OSSL_PARAM_construct_BN( + OSSL_PKEY_PARAM_RSA_N, n_raw.data(), n_raw.size()); + params[1] = OSSL_PARAM_construct_BN( + OSSL_PKEY_PARAM_RSA_E, e_raw.data(), e_raw.size()); + params[2] = OSSL_PARAM_construct_BN( + OSSL_PKEY_PARAM_RSA_D, d_raw_native.data(), d_raw_native.size()); + params[3] = OSSL_PARAM_construct_BN( + OSSL_PKEY_PARAM_RSA_FACTOR1, p_raw_native.data(), p_raw_native.size()); + params[4] = OSSL_PARAM_construct_BN( + OSSL_PKEY_PARAM_RSA_FACTOR2, q_raw_native.data(), q_raw_native.size()); + params[5] = OSSL_PARAM_construct_BN( + OSSL_PKEY_PARAM_RSA_EXPONENT1, + dp_raw_native.data(), + dp_raw_native.size()); + params[6] = OSSL_PARAM_construct_BN( + OSSL_PKEY_PARAM_RSA_EXPONENT2, + dq_raw_native.data(), + dq_raw_native.size()); + params[7] = OSSL_PARAM_construct_BN( + OSSL_PKEY_PARAM_RSA_COEFFICIENT1, + qi_raw_native.data(), + qi_raw_native.size()); + params[8] = OSSL_PARAM_construct_end(); + + Unique_EVP_PKEY_CTX pctx("RSA"); + CHECK1(EVP_PKEY_fromdata_init(pctx)); + CHECK1(EVP_PKEY_fromdata(pctx, &key, EVP_PKEY_KEYPAIR, params)); +#else + auto rsa = RSAPublicKey_OpenSSL::rsa_public_from_jwk(jwk); CHECK1(RSA_set0_key(rsa, nullptr, nullptr, d)); d.release(); @@ -70,8 +130,8 @@ namespace crypto dq.release(); qi.release(); - key = EVP_PKEY_new(); CHECK1(EVP_PKEY_set1_RSA(key, rsa)); +#endif } size_t RSAKeyPair_OpenSSL::key_size() const @@ -96,7 +156,7 @@ namespace crypto } Unique_EVP_PKEY_CTX ctx(key); - OpenSSL::CHECK1(EVP_PKEY_decrypt_init(ctx)); + CHECK1(EVP_PKEY_decrypt_init(ctx)); EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING); EVP_PKEY_CTX_set_rsa_oaep_md(ctx, EVP_sha256()); EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, EVP_sha256()); @@ -113,11 +173,10 @@ namespace crypto } size_t olen; - OpenSSL::CHECK1( - EVP_PKEY_decrypt(ctx, NULL, &olen, input.data(), input.size())); + CHECK1(EVP_PKEY_decrypt(ctx, NULL, &olen, input.data(), input.size())); std::vector output(olen); - OpenSSL::CHECK1( + CHECK1( EVP_PKEY_decrypt(ctx, output.data(), &olen, input.data(), input.size())); output.resize(olen); @@ -128,8 +187,7 @@ namespace crypto { Unique_BIO buf; - OpenSSL::CHECK1( - PEM_write_bio_PrivateKey(buf, key, NULL, NULL, 0, NULL, NULL)); + CHECK1(PEM_write_bio_PrivateKey(buf, key, NULL, NULL, 0, NULL, NULL)); BUF_MEM* bptr; BIO_get_mem_ptr(buf, &bptr); @@ -152,11 +210,10 @@ namespace crypto std::vector r(2048); auto hash = OpenSSLHashProvider().Hash(d.data(), d.size(), md_type); Unique_EVP_PKEY_CTX pctx(key); - OpenSSL::CHECK1(EVP_PKEY_sign_init(pctx)); - OpenSSL::CHECK1(EVP_PKEY_CTX_set_signature_md(pctx, get_md_type(md_type))); + CHECK1(EVP_PKEY_sign_init(pctx)); + CHECK1(EVP_PKEY_CTX_set_signature_md(pctx, get_md_type(md_type))); size_t olen = r.size(); - OpenSSL::CHECK1( - EVP_PKEY_sign(pctx, r.data(), &olen, hash.data(), hash.size())); + CHECK1(EVP_PKEY_sign(pctx, r.data(), &olen, hash.data(), hash.size())); r.resize(olen); return r; } @@ -177,24 +234,36 @@ namespace crypto { JsonWebKeyRSAPrivate jwk = {RSAPublicKey_OpenSSL::public_key_jwk_rsa(kid)}; + Unique_BIGNUM d, p, q, dp, dq, qi; + +#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 + d = RSAPublicKey_OpenSSL::get_bn_param(OSSL_PKEY_PARAM_RSA_D); + p = RSAPublicKey_OpenSSL::get_bn_param(OSSL_PKEY_PARAM_RSA_FACTOR1); + q = RSAPublicKey_OpenSSL::get_bn_param(OSSL_PKEY_PARAM_RSA_FACTOR2); + dp = RSAPublicKey_OpenSSL::get_bn_param(OSSL_PKEY_PARAM_RSA_EXPONENT1); + dq = RSAPublicKey_OpenSSL::get_bn_param(OSSL_PKEY_PARAM_RSA_EXPONENT2); + qi = RSAPublicKey_OpenSSL::get_bn_param(OSSL_PKEY_PARAM_RSA_COEFFICIENT1); +#else const RSA* rsa = EVP_PKEY_get0_RSA(key); if (!rsa) { throw std::logic_error("invalid RSA key"); } - jwk.d = - b64url_from_raw(RSAPublicKey_OpenSSL::bn_bytes(RSA_get0_d(rsa)), false); - jwk.p = - b64url_from_raw(RSAPublicKey_OpenSSL::bn_bytes(RSA_get0_p(rsa)), false); - jwk.q = - b64url_from_raw(RSAPublicKey_OpenSSL::bn_bytes(RSA_get0_q(rsa)), false); - jwk.dp = b64url_from_raw( - RSAPublicKey_OpenSSL::bn_bytes(RSA_get0_dmp1(rsa)), false); - jwk.dq = b64url_from_raw( - RSAPublicKey_OpenSSL::bn_bytes(RSA_get0_dmq1(rsa)), false); - jwk.qi = b64url_from_raw( - RSAPublicKey_OpenSSL::bn_bytes(RSA_get0_iqmp(rsa)), false); + d = RSA_get0_d(rsa); + p = RSA_get0_p(rsa); + q = RSA_get0_q(rsa); + dp = RSA_get0_dmp1(rsa); + dq = RSA_get0_dmq1(rsa); + qi = RSA_get0_iqmp(rsa); +#endif + + jwk.d = b64url_from_raw(RSAPublicKey_OpenSSL::bn_bytes(d), false); + jwk.p = b64url_from_raw(RSAPublicKey_OpenSSL::bn_bytes(p), false); + jwk.q = b64url_from_raw(RSAPublicKey_OpenSSL::bn_bytes(q), false); + jwk.dp = b64url_from_raw(RSAPublicKey_OpenSSL::bn_bytes(dp), false); + jwk.dq = b64url_from_raw(RSAPublicKey_OpenSSL::bn_bytes(dq), false); + jwk.qi = b64url_from_raw(RSAPublicKey_OpenSSL::bn_bytes(qi), false); return jwk; } diff --git a/src/crypto/openssl/rsa_public_key.cpp b/src/crypto/openssl/rsa_public_key.cpp index 926986bdab41..ca5d1bc5ab72 100644 --- a/src/crypto/openssl/rsa_public_key.cpp +++ b/src/crypto/openssl/rsa_public_key.cpp @@ -5,13 +5,22 @@ #include "crypto/openssl/rsa_key_pair.h" #include "openssl_wrappers.h" +#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 +# include +# include +#endif + namespace crypto { using namespace OpenSSL; RSAPublicKey_OpenSSL::RSAPublicKey_OpenSSL(EVP_PKEY* c) : PublicKey_OpenSSL(c) { +#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 + if (EVP_PKEY_get_base_id(key) != EVP_PKEY_RSA) +#else if (!EVP_PKEY_get0_RSA(key)) +#endif { throw std::logic_error("invalid RSA key"); } @@ -21,7 +30,11 @@ namespace crypto { Unique_BIO mem(pem); key = PEM_read_bio_PUBKEY(mem, NULL, NULL, NULL); +#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 + if (!key || EVP_PKEY_get_base_id(key) != EVP_PKEY_RSA) +#else if (!key || !EVP_PKEY_get0_RSA(key)) +#endif { throw std::logic_error("invalid RSA key"); } @@ -30,37 +43,54 @@ namespace crypto RSAPublicKey_OpenSSL::RSAPublicKey_OpenSSL(const std::vector& der) { const unsigned char* pp = der.data(); - RSA* rsa = nullptr; + key = EVP_PKEY_new(); if ( - ((rsa = d2i_RSA_PUBKEY(NULL, &pp, der.size())) == + ((key = d2i_PUBKEY(&key, &pp, der.size())) == NULL) && // "SubjectPublicKeyInfo structure" format - ((rsa = d2i_RSAPublicKey(NULL, &pp, der.size())) == + ((key = d2i_PublicKey(EVP_PKEY_RSA, &key, &pp, der.size())) == NULL)) // PKCS#1 structure format { unsigned long ec = ERR_get_error(); auto msg = OpenSSL::error_string(ec); throw std::runtime_error(fmt::format("OpenSSL error: {}", msg)); } - - key = EVP_PKEY_new(); - OpenSSL::CHECK1(EVP_PKEY_set1_RSA(key, rsa)); - RSA_free(rsa); } - OpenSSL::Unique_RSA RSAPublicKey_OpenSSL::rsa_public_from_jwk( + std::pair get_modulus_and_exponent( const JsonWebKeyRSAPublic& jwk) { if (jwk.kty != JsonWebKeyType::RSA) { - throw std::logic_error( - "Cannot construct RSA public key from non-RSA JWK"); + throw std::logic_error("Cannot construct public key from non-RSA JWK"); } - Unique_BIGNUM e, n; - auto e_raw = raw_from_b64url(jwk.e); + std::pair ne; auto n_raw = raw_from_b64url(jwk.n); - OpenSSL::CHECKNULL(BN_bin2bn(e_raw.data(), e_raw.size(), e)); - OpenSSL::CHECKNULL(BN_bin2bn(n_raw.data(), n_raw.size(), n)); + auto e_raw = raw_from_b64url(jwk.e); + OpenSSL::CHECKNULL(BN_bin2bn(n_raw.data(), n_raw.size(), ne.first)); + OpenSSL::CHECKNULL(BN_bin2bn(e_raw.data(), e_raw.size(), ne.second)); + + return ne; + } + +#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 + std::pair, std::vector> RSAPublicKey_OpenSSL:: + rsa_public_raw_from_jwk(const JsonWebKeyRSAPublic& jwk) + { + auto [n, e] = get_modulus_and_exponent(jwk); + std::pair, std::vector> r( + BN_num_bytes(n), BN_num_bytes(e)); + + CHECKPOSITIVE(BN_bn2nativepad(n, r.first.data(), r.first.size())); + CHECKPOSITIVE(BN_bn2nativepad(e, r.second.data(), r.second.size())); + + return r; + } +#else + OpenSSL::Unique_RSA RSAPublicKey_OpenSSL::rsa_public_from_jwk( + const JsonWebKeyRSAPublic& jwk) + { + auto [n, e] = get_modulus_and_exponent(jwk); Unique_RSA rsa; CHECK1(RSA_set0_key(rsa, n, e, nullptr)); @@ -69,11 +99,27 @@ namespace crypto return rsa; } +#endif RSAPublicKey_OpenSSL::RSAPublicKey_OpenSSL(const JsonWebKeyRSAPublic& jwk) { key = EVP_PKEY_new(); +#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 + auto [n_raw, e_raw] = rsa_public_raw_from_jwk(jwk); + + OSSL_PARAM params[3]; + params[0] = OSSL_PARAM_construct_BN( + OSSL_PKEY_PARAM_RSA_N, n_raw.data(), n_raw.size()); + params[1] = OSSL_PARAM_construct_BN( + OSSL_PKEY_PARAM_RSA_E, e_raw.data(), e_raw.size()); + params[2] = OSSL_PARAM_construct_end(); + + Unique_EVP_PKEY_CTX pctx("RSA"); + CHECK1(EVP_PKEY_fromdata_init(pctx)); + CHECK1(EVP_PKEY_fromdata(pctx, &key, EVP_PKEY_PUBLIC_KEY, params)); +#else CHECK1(EVP_PKEY_set1_RSA(key, rsa_public_from_jwk(jwk))); +#endif } size_t RSAPublicKey_OpenSSL::key_size() const @@ -166,17 +212,33 @@ namespace crypto return r; } +#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 + Unique_BIGNUM RSAPublicKey_OpenSSL::get_bn_param(const char* key_name) const + { + Unique_BIGNUM r; + BIGNUM* bn = NULL; + CHECK1(EVP_PKEY_get_bn_param(key, key_name, &bn)); + r.reset(bn); + return r; + } +#endif + RSAPublicKey::Components RSAPublicKey_OpenSSL::components() const { + Components r; +#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 + r.n = bn_bytes(get_bn_param(OSSL_PKEY_PARAM_RSA_N)); + r.e = bn_bytes(get_bn_param(OSSL_PKEY_PARAM_RSA_E)); +#else const RSA* rsa = EVP_PKEY_get0_RSA(key); if (!rsa) { throw std::logic_error("invalid RSA key"); } - Components r; r.n = bn_bytes(RSA_get0_n(rsa)); r.e = bn_bytes(RSA_get0_e(rsa)); +#endif return r; } diff --git a/src/crypto/openssl/rsa_public_key.h b/src/crypto/openssl/rsa_public_key.h index 063a5725bd94..afe3164fa8aa 100644 --- a/src/crypto/openssl/rsa_public_key.h +++ b/src/crypto/openssl/rsa_public_key.h @@ -16,7 +16,12 @@ namespace crypto class RSAPublicKey_OpenSSL : public PublicKey_OpenSSL, public RSAPublicKey { protected: +#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 + std::pair, std::vector> + rsa_public_raw_from_jwk(const JsonWebKeyRSAPublic& jwk); +#else OpenSSL::Unique_RSA rsa_public_from_jwk(const JsonWebKeyRSAPublic& jwk); +#endif public: RSAPublicKey_OpenSSL() = default; @@ -53,6 +58,10 @@ namespace crypto static std::vector bn_bytes(const BIGNUM* bn); +#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 + OpenSSL::Unique_BIGNUM get_bn_param(const char* key_name) const; +#endif + virtual JsonWebKeyRSAPublic public_key_jwk_rsa( const std::optional& kid = std::nullopt) const override; }; diff --git a/src/crypto/openssl/verifier.cpp b/src/crypto/openssl/verifier.cpp index acc9ca88db43..b11a42d12455 100644 --- a/src/crypto/openssl/verifier.cpp +++ b/src/crypto/openssl/verifier.cpp @@ -56,6 +56,17 @@ namespace crypto EVP_PKEY* pk = X509_get_pubkey(cert); +#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 + auto base_id = EVP_PKEY_get_base_id(pk); + if (base_id == EVP_PKEY_EC) + { + public_key = std::make_unique(pk); + } + else if (base_id == EVP_PKEY_RSA) + { + public_key = std::make_unique(pk); + } +#else if (EVP_PKEY_get0_EC_KEY(pk)) { public_key = std::make_unique(pk); @@ -64,6 +75,7 @@ namespace crypto { public_key = std::make_unique(pk); } +#endif else { throw std::logic_error("unsupported public key type"); diff --git a/src/crypto/test/crypto.cpp b/src/crypto/test/crypto.cpp index 296357ea44f9..c37e46ac1777 100644 --- a/src/crypto/test/crypto.cpp +++ b/src/crypto/test/crypto.cpp @@ -821,8 +821,15 @@ TEST_CASE("PEM to JWK and back") INFO("RSA"); { auto kp = make_rsa_key_pair(); + auto pubk = make_rsa_public_key(kp->public_key_pem()); + INFO("DER"); + { + auto pubk_der = make_rsa_public_key(kp->public_key_der()); + REQUIRE(pubk_der->public_key_pem() == kp->public_key_pem()); + } + INFO("Public"); { auto jwk = pubk->public_key_jwk_rsa(); @@ -844,6 +851,7 @@ TEST_CASE("PEM to JWK and back") auto kp2 = make_rsa_key_pair(jwk); auto jwk2 = kp2->private_key_jwk_rsa(kid); + REQUIRE(jwk == jwk2); } } diff --git a/src/enclave/enclave.h b/src/enclave/enclave.h index ad1bea2ec0c4..370af53dabb8 100644 --- a/src/enclave/enclave.h +++ b/src/enclave/enclave.h @@ -51,7 +51,9 @@ namespace ccf std::unique_ptr node; ringbuffer::WriterPtr to_host = nullptr; std::chrono::microseconds last_tick_time; +#if !(defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3) ENGINE* rdrand_engine = nullptr; +#endif StartType start_type; @@ -97,6 +99,11 @@ namespace ccf ccf::initialize_verifiers(); crypto::openssl_sha256_init(); + // https://github.com/microsoft/CCF/issues/5569 + // Open Enclave with OpenSSL 3.x (default for SGX) is built with RDCPU + // (https://github.com/openenclave/openenclave/blob/master/docs/OpenSSLSupport.md#how-to-use-rand-apis) + // and so does not need to make use of the (deprecated) ENGINE_x API. +#if !(defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3) // From // https://software.intel.com/content/www/us/en/develop/articles/how-to-use-the-rdrand-engine-in-openssl-for-random-number-generation.html if ( @@ -110,6 +117,7 @@ namespace ccf throw ccf::ccf_openssl_rdrand_init_error( "could not initialize RDRAND engine for OpenSSL"); } +#endif to_host = writer_factory->create_writer_to_outside(); @@ -188,12 +196,14 @@ namespace ccf ~Enclave() { +#if !(defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3) if (rdrand_engine) { LOG_TRACE_FMT("Finishing RDRAND engine"); ENGINE_finish(rdrand_engine); ENGINE_free(rdrand_engine); } +#endif LOG_TRACE_FMT("Shutting down enclave"); ccf::shutdown_verifiers(); ccf::pal::shutdown_enclave(); diff --git a/src/enclave/tls_session.h b/src/enclave/tls_session.h index 58b502e66f1f..b4250631e556 100644 --- a/src/enclave/tls_session.h +++ b/src/enclave/tls_session.h @@ -673,7 +673,15 @@ namespace ccf (void)argi; (void)argl; - if (ret && oper == (BIO_CB_READ | BIO_CB_RETURN)) + if (ret == 1 && oper == (BIO_CB_CTRL | BIO_CB_RETURN)) + { + // This callback may be fired at the end of large batches of TLS frames + // on OpenSSL 3.x. Note that processed == nullptr in this case, hence + // the early exit. + return 0; + } + + if (ret && (oper == (BIO_CB_READ | BIO_CB_RETURN))) { // Pipe object void* ctx = (BIO_get_callback_arg(b)); diff --git a/src/node/snapshot_serdes.h b/src/node/snapshot_serdes.h index 24238d5bd0c6..1e9cb706a4f2 100644 --- a/src/node/snapshot_serdes.h +++ b/src/node/snapshot_serdes.h @@ -85,7 +85,8 @@ namespace ccf root.h.data(), root.h.size(), receipt->signature.data(), - receipt->signature.size())) + receipt->signature.size(), + crypto::MDType::SHA256)) { throw std::logic_error( "Signature verification failed for snapshot receipt");