From f7cb6f23f0ed8b7e8436a11be7b86f6b1a39d822 Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Thu, 8 Oct 2026 15:50:03 +0300 Subject: [PATCH] Use OpenSSL provider to support RSA PSS backend IB-9208 Signed-off-by: Raul Metsma --- cdoc/CMakeLists.txt | 1 + cdoc/NetworkBackend.cpp | 102 +----------- cdoc/NetworkBackend.h | 3 + cdoc/PKCS11Backend.cpp | 38 ++--- cdoc/PKCS11Backend.h | 8 - cdoc/TLSKey.cpp | 349 ++++++++++++++++++++++++++++++++++++++++ cdoc/TLSKey.h | 29 ++++ cdoc/WinBackend.cpp | 6 +- cdoc/WinBackend.h | 8 - 9 files changed, 400 insertions(+), 144 deletions(-) create mode 100644 cdoc/TLSKey.cpp create mode 100644 cdoc/TLSKey.h diff --git a/cdoc/CMakeLists.txt b/cdoc/CMakeLists.txt index ef10cbc6..ea0dc6cb 100644 --- a/cdoc/CMakeLists.txt +++ b/cdoc/CMakeLists.txt @@ -41,6 +41,7 @@ add_library(cdoc Certificate.cpp Certificate.h Crypto.cpp Crypto.h Tar.cpp Tar.h + TLSKey.cpp TLSKey.h ZStream.h # Internal Utils.cpp Utils.h diff --git a/cdoc/NetworkBackend.cpp b/cdoc/NetworkBackend.cpp index 7ff8f5fb..d8640ff5 100644 --- a/cdoc/NetworkBackend.cpp +++ b/cdoc/NetworkBackend.cpp @@ -21,10 +21,9 @@ #include "Certificate.h" #include "Crypto.h" #include "CryptoBackend.h" +#include "TLSKey.h" #include "Utils.h" -#define OPENSSL_SUPPRESS_DEPRECATED - #include #include #include @@ -44,53 +43,14 @@ using namespace std::literals::chrono_literals; -using EC_KEY_sign = int (*)(int type, const unsigned char *dgst, int dlen, unsigned char *sig, unsigned int *siglen, const BIGNUM *kinv, const BIGNUM *r, EC_KEY *eckey); -using EC_KEY_sign_setup = int (*)(EC_KEY *eckey, BN_CTX *ctx_in, BIGNUM **kinvp, BIGNUM **rp); - -static ECDSA_SIG* ecdsa_do_sign(const unsigned char *dgst, int dgst_len, const BIGNUM *inv, const BIGNUM *rp, EC_KEY *eckey); -static int rsa_sign(int type, const unsigned char *m, unsigned int m_len, unsigned char *sigret, unsigned int *siglen, const ::RSA *rsa); - struct Private { libcdoc::Certificate x509; - EVP_PKEY *pkey = nullptr; - - RSA_METHOD *rsamethod = nullptr; - EC_KEY_METHOD *ecmethod = nullptr; + libcdoc::unique_ptr_t key = libcdoc::make_unique_ptr(nullptr); explicit Private(libcdoc::NetworkBackend *backend, const std::vector &client_cert) : x509(client_cert) { - if (!x509) return; - pkey = X509_get_pubkey(x509.handle()); - if (!pkey) return; - int id = EVP_PKEY_get_id(pkey); - if (id == EVP_PKEY_EC) { - ecmethod = EC_KEY_METHOD_new(EC_KEY_get_default_method()); - EC_KEY_sign sign = nullptr; - EC_KEY_sign_setup sign_setup = nullptr; - EC_KEY_METHOD_get_sign(ecmethod, &sign, &sign_setup, nullptr); - EC_KEY_METHOD_set_sign(ecmethod, sign, sign_setup, ecdsa_do_sign); - - auto *ec = (EC_KEY *) EVP_PKEY_get1_EC_KEY(pkey); - EC_KEY_set_method(ec, ecmethod); - EC_KEY_set_ex_data(ec, 0, backend); - EVP_PKEY_set1_EC_KEY(pkey, ec); - } else if (id == EVP_PKEY_RSA) { - rsamethod = RSA_meth_dup(RSA_get_default_method()); - RSA_meth_set1_name(rsamethod, "libcdoc"); - RSA_meth_set_sign(rsamethod, rsa_sign); - - RSA *rsa = (RSA *) EVP_PKEY_get1_RSA(pkey); - RSA_set_method(rsa, rsamethod); - RSA_set_ex_data(rsa, 0, backend); - EVP_PKEY_set1_RSA(pkey, rsa); - } - } - - ~Private() { - if (pkey) EVP_PKEY_free(pkey); - if (rsamethod) RSA_meth_free(rsamethod); - if (ecmethod) EC_KEY_METHOD_free(ecmethod); + if (x509) key = libcdoc::createTLSKey(backend, x509.handle()); } }; @@ -442,9 +402,10 @@ libcdoc::NetworkBackend::fetchKey (std::vector& dst, const std::string& result = getClientTLSCertificate(cert); if (result != OK) return result; std::unique_ptr d = std::make_unique(this, cert); - if (!cert.empty() && (!d->x509 || !d->pkey)) return CRYPTO_ERROR; + if (!cert.empty() && (!d->x509 || !d->key)) return CRYPTO_ERROR; - httplib::SSLClient cli(host, port, d->x509.handle(), d->pkey); + httplib::SSLClient cli(host, port, d->x509.handle(), d->key.get()); + SSL_CTX_set_min_proto_version(cli.ssl_context(), TLS1_3_VERSION); if (result = applySSLTimeout(cli, this); result != OK) return result; result = setPeerCertificates(cli, this, buildURL(host, port)); if (result != OK) return result; @@ -565,57 +526,6 @@ libcdoc::NetworkBackend::fetchShare(ShareInfo& share, const std::string& url, co } #endif -ECDSA_SIG * -ecdsa_do_sign(const unsigned char *dgst, int dgst_len, const BIGNUM * /*inv*/, const BIGNUM * /*rp*/, EC_KEY *eckey) -{ - auto *backend = (libcdoc::NetworkBackend *) EC_KEY_get_ex_data(eckey, 0); - std::vector dst; - std::vector digest(dgst, dgst + dgst_len); - int result = backend->signTLS(dst, libcdoc::CryptoBackend::SHA_512, digest); - if (result != libcdoc::OK) { - return nullptr; - } - int size_2 = (int) dst.size() / 2; - ECDSA_SIG *sig = ECDSA_SIG_new(); - ECDSA_SIG_set0(sig, - BN_bin2bn(dst.data(), size_2, nullptr), - BN_bin2bn(dst.data() + size_2, size_2, nullptr)); - return sig; -} - -int -rsa_sign(int type, const unsigned char *m, unsigned int m_len, unsigned char *sigret, unsigned int *siglen, const RSA *rsa) -{ - auto *backend = (libcdoc::NetworkBackend *) RSA_get_ex_data(rsa, 0); - auto algo = libcdoc::CryptoBackend::SHA_512; - switch (type) { - case NID_sha224: - algo = libcdoc::CryptoBackend::SHA_224; - break; - case NID_sha256: - algo = libcdoc::CryptoBackend::SHA_256; - break; - case NID_sha384: - algo = libcdoc::CryptoBackend::SHA_384; - break; - case NID_sha512: - break; - default: - return 0; - } - std::vector dst; - std::vector digest(m, m + m_len); - int result = backend->signTLS(dst, algo, digest); - if (result != libcdoc::OK) { - return 0; - } - if (sigret && (*siglen >= dst.size())) { - memcpy(sigret, dst.data(), dst.size()); - } - *siglen = (unsigned int) dst.size(); - return 1; -} - #ifdef HAS_KEYSHARES libcdoc::result_t libcdoc::NetworkBackend::showVerificationCode(unsigned int code) diff --git a/cdoc/NetworkBackend.h b/cdoc/NetworkBackend.h index 8a3b8621..5d1f2137 100644 --- a/cdoc/NetworkBackend.h +++ b/cdoc/NetworkBackend.h @@ -234,6 +234,9 @@ struct CDOC_EXPORT NetworkBackend { /** * @brief sign TLS digest with client's private key + * + * An ECDSA signature is returned as r || s, an RSA signature as is. RSA keys sign with RSASSA-PSS + * (salt as long as the digest, MGF1 with the same hash): the connection requires TLS 1.3, which allows only PSS. * @param dst a destination container for signature * @param algorithm signing algorithm * @param digest data to be signed diff --git a/cdoc/PKCS11Backend.cpp b/cdoc/PKCS11Backend.cpp index ac7894c1..4bed8e30 100644 --- a/cdoc/PKCS11Backend.cpp +++ b/cdoc/PKCS11Backend.cpp @@ -638,48 +638,32 @@ libcdoc::PKCS11Backend::sign(std::vector& dst, HashAlgorithm algorithm, CK_ATTRIBUTE attribute { CKA_KEY_TYPE, &keyType, sizeof(keyType) }; d->f->C_GetAttributeValue(d->session, d->key, &attribute, 1); - CK_RSA_PKCS_PSS_PARAMS pssParams { CKM_SHA256, CKG_MGF1_SHA256, 32 }; - CK_MECHANISM mech { keyType == CKK_ECDSA ? CKM_ECDSA : CKM_RSA_PKCS, nullptr, 0 }; - std::vector data; + CK_RSA_PKCS_PSS_PARAMS pssParams {}; + CK_MECHANISM mech { CKM_ECDSA, nullptr, 0 }; if(keyType == CKK_RSA) { switch(algorithm) { - case libcdoc::CryptoBackend::HashAlgorithm::SHA_224: - data = libcdoc::fromHex("302d300d06096086480165030402040500041c"); - pssParams = { CKM_SHA224, CKG_MGF1_SHA224, 24 }; - break; - case libcdoc::CryptoBackend::HashAlgorithm::SHA_256: - data = libcdoc::fromHex("3031300d060960864801650304020105000420"); - pssParams = { CKM_SHA256, CKG_MGF1_SHA256, 32 }; - break; - case libcdoc::CryptoBackend::HashAlgorithm::SHA_384: - data = libcdoc::fromHex("3041300d060960864801650304020205000430"); - pssParams = { CKM_SHA384, CKG_MGF1_SHA384, 48 }; - break; - case libcdoc::CryptoBackend::HashAlgorithm::SHA_512: - data = libcdoc::fromHex("3051300d060960864801650304020305000440"); - pssParams = { CKM_SHA512, CKG_MGF1_SHA512, 64 }; - break; + case libcdoc::CryptoBackend::HashAlgorithm::SHA_224: pssParams = { CKM_SHA224, CKG_MGF1_SHA224, 24 }; break; + case libcdoc::CryptoBackend::HashAlgorithm::SHA_256: pssParams = { CKM_SHA256, CKG_MGF1_SHA256, 32 }; break; + case libcdoc::CryptoBackend::HashAlgorithm::SHA_384: pssParams = { CKM_SHA384, CKG_MGF1_SHA384, 48 }; break; + case libcdoc::CryptoBackend::HashAlgorithm::SHA_512: pssParams = { CKM_SHA512, CKG_MGF1_SHA512, 64 }; break; default: - break; - } - if(usePSS(idx)) { - mech = { CKM_RSA_PKCS_PSS, &pssParams, sizeof(CK_RSA_PKCS_PSS_PARAMS) }; - data.clear(); + d->logout(); + return INVALID_PARAMS; } + mech = { CKM_RSA_PKCS_PSS, &pssParams, sizeof(pssParams) }; } - data.insert(data.end(), digest.begin(), digest.end()); if(d->f->C_SignInit(d->session, &mech, d->key) != CKR_OK) { d->logout(); return PKCS11_ERROR; } CK_ULONG size = 0; - if(d->f->C_Sign(d->session, CK_BYTE_PTR(data.data()), CK_ULONG(data.size()), nullptr, &size) != CKR_OK) { + if(d->f->C_Sign(d->session, CK_BYTE_PTR(digest.data()), CK_ULONG(digest.size()), nullptr, &size) != CKR_OK) { d->logout(); return PKCS11_ERROR; } dst.resize(int(size)); - if(d->f->C_Sign(d->session, CK_BYTE_PTR(data.data()), CK_ULONG(data.size()), CK_BYTE_PTR(dst.data()), &size) != CKR_OK) { + if(d->f->C_Sign(d->session, CK_BYTE_PTR(digest.data()), CK_ULONG(digest.size()), CK_BYTE_PTR(dst.data()), &size) != CKR_OK) { d->logout(); return PKCS11_ERROR; } diff --git a/cdoc/PKCS11Backend.h b/cdoc/PKCS11Backend.h index 4df96df6..014cb897 100644 --- a/cdoc/PKCS11Backend.h +++ b/cdoc/PKCS11Backend.h @@ -146,14 +146,6 @@ struct CDOC_EXPORT PKCS11Backend : public CryptoBackend { * @return error code or OK */ virtual result_t connectToKey(int idx, bool priv) { return NOT_IMPLEMENTED; }; - /** - * @brief whether to use PSS RSA padding - * - * A subclass should overwrite this to inform the backend about the correct padding. - * @param idx a lock idx - * @return true if PSS padding is used - */ - virtual result_t usePSS(int idx) {return true;} virtual result_t deriveECDH1(std::vector& dst, const std::vector &public_key, unsigned int idx) override; virtual result_t decryptRSA(std::vector& dst, const std::vector &data, bool oaep, unsigned int idxl) override; diff --git a/cdoc/TLSKey.cpp b/cdoc/TLSKey.cpp new file mode 100644 index 00000000..dc1836e9 --- /dev/null +++ b/cdoc/TLSKey.cpp @@ -0,0 +1,349 @@ +// SPDX-FileCopyrightText: Estonian Information System Authority +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "TLSKey.h" + +#include "CryptoBackend.h" +#include "NetworkBackend.h" +#include "Utils.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace std::literals::string_view_literals; + +namespace { + +constexpr const char *PROVIDER_PROPS = "provider=libcdoc-tls"; +// Carries the NetworkBackend pointer from createTLSKey() to the key import +constexpr const char *NETWORK_PARAM = "libcdoc-network"; +constexpr char RSA_NAME[] = "RSA"; +constexpr char EC_NAME[] = "EC"; +constexpr char ECDSA_NAME[] = "ECDSA"; + +// The provider lives in a library context of its own, so that its RSA and EC +// implementations are only ever used for the keys made here +OSSL_LIB_CTX *libctx = nullptr; + +template +OSSL_DISPATCH fn(int id, F f) { return {id, reinterpret_cast(f)}; } + +// +// Key management: the public key is kept as the default provider's key, the private +// key operations go to the network backend +// + +struct Key { + const char *type; + EVP_PKEY *pub = nullptr; + // nullptr for a key imported from elsewhere, e.g. a certificate's to compare with + libcdoc::NetworkBackend *network = nullptr; +}; + +template +void *keyNew(void *) { return new Key{Type}; } + +void keyFree(void *vkey) +{ + if (auto *key = static_cast(vkey)) { + EVP_PKEY_free(key->pub); + delete key; + } +} + +int keyHas(const void *vkey, int selection) +{ + const auto *key = static_cast(vkey); + return key && key->pub && (!(selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) || key->network); +} + +int keyImport(void *vkey, int /*selection*/, const OSSL_PARAM params[]) +{ + auto *key = static_cast(vkey); + if (!key || key->pub) return 0; + if (const OSSL_PARAM *p = OSSL_PARAM_locate_const(params, NETWORK_PARAM)) { + const void *ptr = nullptr; + size_t len = 0; + if (!OSSL_PARAM_get_octet_string_ptr(p, &ptr, &len) || len != sizeof(key->network)) return 0; + std::memcpy(&key->network, ptr, len); + } + // The default provider ignores NETWORK_PARAM + auto ctx = libcdoc::make_unique_ptr(EVP_PKEY_CTX_new_from_name(libctx, key->type, "provider=default")); + return ctx && EVP_PKEY_fromdata_init(ctx.get()) == 1 && + EVP_PKEY_fromdata(ctx.get(), &key->pub, EVP_PKEY_PUBLIC_KEY, const_cast(params)) == 1; +} + +int keyExport(void *vkey, int selection, OSSL_CALLBACK *cb, void *cbarg) +{ + auto *key = static_cast(vkey); + // Signing first tries the key in whichever provider the name resolves to in the + // caller's library context, exporting it there with everything selected. Only when + // that export fails does it come back to this provider, so a key with a private half + // must not leave. Comparing it with the certificate asks for the public part only. + if (!key || !key->pub || ((selection & OSSL_KEYMGMT_SELECT_PRIVATE_KEY) && key->network)) return 0; + OSSL_PARAM *params = nullptr; + if (EVP_PKEY_todata(key->pub, EVP_PKEY_PUBLIC_KEY, ¶ms) != 1) return 0; + int ok = cb(params, cbarg); + OSSL_PARAM_free(params); + return ok; +} + +int keyMatch(const void *vkey1, const void *vkey2, int /*selection*/) +{ + const auto *key1 = static_cast(vkey1); + const auto *key2 = static_cast(vkey2); + return key1 && key2 && key1->pub && key2->pub && EVP_PKEY_eq(key1->pub, key2->pub) == 1; +} + +int keyGetParams(void *vkey, OSSL_PARAM params[]) +{ + // Size, security bits, group and the like are those of the public key + auto *key = static_cast(vkey); + return key && key->pub && EVP_PKEY_get_params(key->pub, params) == 1; +} + +// OpenSSL accepts import, export and get_params only together with these descriptors +// (keymgmt_meth.c); neither key creation nor TLS consults them, so they are empty +const OSSL_PARAM *noTypes() { static const OSSL_PARAM end[] { OSSL_PARAM_END }; return end; } +const OSSL_PARAM *keyTypes(int /*selection*/) { return noTypes(); } +const OSSL_PARAM *keyGettableParams(void * /*provctx*/) { return noTypes(); } + +template +const char *keyOperationName(int operation_id) { return operation_id == OSSL_OP_SIGNATURE ? SignatureName : nullptr; } + +template +const OSSL_DISPATCH keymgmt[] { + fn(OSSL_FUNC_KEYMGMT_NEW, keyNew), + fn(OSSL_FUNC_KEYMGMT_FREE, keyFree), + fn(OSSL_FUNC_KEYMGMT_HAS, keyHas), + fn(OSSL_FUNC_KEYMGMT_MATCH, keyMatch), + fn(OSSL_FUNC_KEYMGMT_IMPORT, keyImport), + fn(OSSL_FUNC_KEYMGMT_IMPORT_TYPES, keyTypes), + fn(OSSL_FUNC_KEYMGMT_EXPORT, keyExport), + fn(OSSL_FUNC_KEYMGMT_EXPORT_TYPES, keyTypes), + fn(OSSL_FUNC_KEYMGMT_GET_PARAMS, keyGetParams), + fn(OSSL_FUNC_KEYMGMT_GETTABLE_PARAMS, keyGettableParams), + fn(OSSL_FUNC_KEYMGMT_QUERY_OPERATION_NAME, keyOperationName), + {0, nullptr} +}; + +// +// Signature: hash here, sign the digest with the network backend +// + +struct SignCtx { + Key *key = nullptr; + libcdoc::unique_ptr_t md = libcdoc::make_unique_ptr(nullptr); + libcdoc::unique_ptr_t mgf1 = libcdoc::make_unique_ptr(nullptr); + libcdoc::unique_ptr_t mdctx = libcdoc::make_unique_ptr(EVP_MD_CTX_new()); + int padding = RSA_PKCS1_PADDING; + int saltlen = RSA_PSS_SALTLEN_DIGEST; +}; + +bool fetchDigest(libcdoc::unique_ptr_t &md, const OSSL_PARAM *p) +{ + const char *name = nullptr; + if (OSSL_PARAM_get_utf8_string_ptr(p, &name) != 1) return false; + md.reset(EVP_MD_fetch(libctx, name, "provider=default")); + return bool(md); +} + +void *sigNew(void * /*provctx*/, const char * /*propq*/) { return new SignCtx; } +void sigFree(void *vctx) { delete static_cast(vctx); } + +int sigSetParams(void *vctx, const OSSL_PARAM params[]) +{ + auto *ctx = static_cast(vctx); + // EVP_PKEY_CTX_set_rsa_padding() and _pss_saltlen() arrive as names, OSSL_PARAM users may pass numbers + const char *str = nullptr; + if (const OSSL_PARAM *p = OSSL_PARAM_locate_const(params, OSSL_SIGNATURE_PARAM_DIGEST); p && !fetchDigest(ctx->md, p)) + return 0; + if (const OSSL_PARAM *p = OSSL_PARAM_locate_const(params, OSSL_SIGNATURE_PARAM_MGF1_DIGEST); p && !fetchDigest(ctx->mgf1, p)) + return 0; + if (const OSSL_PARAM *p = OSSL_PARAM_locate_const(params, OSSL_SIGNATURE_PARAM_PAD_MODE)) { + if (OSSL_PARAM_get_utf8_string_ptr(p, &str) == 1) + ctx->padding = str == OSSL_PKEY_RSA_PAD_MODE_PSS ""sv ? RSA_PKCS1_PSS_PADDING : + str == OSSL_PKEY_RSA_PAD_MODE_PKCSV15 ""sv ? RSA_PKCS1_PADDING : -1; + else if (OSSL_PARAM_get_int(p, &ctx->padding) != 1) + return 0; + } + if (const OSSL_PARAM *p = OSSL_PARAM_locate_const(params, OSSL_SIGNATURE_PARAM_PSS_SALTLEN)) { + if (OSSL_PARAM_get_utf8_string_ptr(p, &str) == 1) + ctx->saltlen = str == OSSL_PKEY_RSA_PSS_SALT_LEN_DIGEST ""sv ? RSA_PSS_SALTLEN_DIGEST : std::atoi(str); + else if (OSSL_PARAM_get_int(p, &ctx->saltlen) != 1) + return 0; + } + return 1; +} + +const OSSL_PARAM *sigSettableParams(void * /*vctx*/, void * /*provctx*/) +{ + static const OSSL_PARAM types[] { + OSSL_PARAM_utf8_string(OSSL_SIGNATURE_PARAM_DIGEST, nullptr, 0), + OSSL_PARAM_utf8_string(OSSL_SIGNATURE_PARAM_PAD_MODE, nullptr, 0), + OSSL_PARAM_utf8_string(OSSL_SIGNATURE_PARAM_MGF1_DIGEST, nullptr, 0), + OSSL_PARAM_utf8_string(OSSL_SIGNATURE_PARAM_PSS_SALTLEN, nullptr, 0), + OSSL_PARAM_END + }; + return types; +} + +int sigDigestSignInit(void *vctx, const char *mdname, void *vkey, const OSSL_PARAM params[]) +{ + auto *ctx = static_cast(vctx); + ctx->key = static_cast(vkey); + if (!ctx->key || !ctx->key->network || !sigSetParams(ctx, params)) return 0; + if (mdname && *mdname) + ctx->md.reset(EVP_MD_fetch(libctx, mdname, "provider=default")); + return ctx->md && ctx->mdctx && EVP_DigestInit_ex2(ctx->mdctx.get(), ctx->md.get(), nullptr) == 1; +} + +int sigDigestSignUpdate(void *vctx, const unsigned char *data, size_t datalen) +{ + return EVP_DigestUpdate(static_cast(vctx)->mdctx.get(), data, datalen); +} + +int sigDigestSignFinal(void *vctx, unsigned char *sig, size_t *siglen, size_t sigsize) +{ + auto *ctx = static_cast(vctx); + if (!sig) { + *siglen = size_t(EVP_PKEY_get_size(ctx->key->pub)); + return 1; + } + std::vector digest(EVP_MAX_MD_SIZE); + unsigned int len = 0; + if (EVP_DigestFinal_ex(ctx->mdctx.get(), digest.data(), &len) != 1) return 0; + digest.resize(len); + + libcdoc::CryptoBackend::HashAlgorithm algo; + switch (EVP_MD_get_type(ctx->md.get())) { + case NID_sha224: algo = libcdoc::CryptoBackend::SHA_224; break; + case NID_sha256: algo = libcdoc::CryptoBackend::SHA_256; break; + case NID_sha384: algo = libcdoc::CryptoBackend::SHA_384; break; + case NID_sha512: algo = libcdoc::CryptoBackend::SHA_512; break; + default: + LOG_ERROR("Unsupported TLS client signature digest {}", EVP_MD_get0_name(ctx->md.get())); + return 0; + } + const bool isRSA = ctx->key->type == RSA_NAME; + // What tokens implement and TLS 1.3 asks for: PSS salt as long as the hash, MGF1 with the same hash + const bool supported = !isRSA || (ctx->padding == RSA_PKCS1_PSS_PADDING && + (!ctx->mgf1 || EVP_MD_get_type(ctx->mgf1.get()) == EVP_MD_get_type(ctx->md.get())) && + (ctx->saltlen == RSA_PSS_SALTLEN_DIGEST || ctx->saltlen == int(len))); + if (!supported) { + LOG_ERROR("Unsupported RSA padding {} for TLS client signature", ctx->padding); + return 0; + } + + std::vector dst; + if (ctx->key->network->signTLS(dst, algo, digest) != libcdoc::OK || dst.empty() || (!isRSA && dst.size() % 2)) + return 0; + if (isRSA) { + if (dst.size() > sigsize) return 0; + std::memcpy(sig, dst.data(), dst.size()); + *siglen = dst.size(); + return 1; + } + // Tokens give ECDSA signatures as r || s, TLS wants them DER encoded + auto ecsig = libcdoc::make_unique_ptr(ECDSA_SIG_new()); + BIGNUM *r = BN_bin2bn(dst.data(), int(dst.size() / 2), nullptr); + BIGNUM *s = BN_bin2bn(dst.data() + dst.size() / 2, int(dst.size() / 2), nullptr); + if (!ecsig || !r || !s || ECDSA_SIG_set0(ecsig.get(), r, s) != 1) { + BN_free(r); + BN_free(s); + return 0; + } + if (int derlen = i2d_ECDSA_SIG(ecsig.get(), nullptr); derlen <= 0 || size_t(derlen) > sigsize) return 0; + *siglen = size_t(i2d_ECDSA_SIG(ecsig.get(), &sig)); + return 1; +} + +const OSSL_DISPATCH signature[] { + fn(OSSL_FUNC_SIGNATURE_NEWCTX, sigNew), + fn(OSSL_FUNC_SIGNATURE_FREECTX, sigFree), + fn(OSSL_FUNC_SIGNATURE_DIGEST_SIGN_INIT, sigDigestSignInit), + fn(OSSL_FUNC_SIGNATURE_DIGEST_SIGN_UPDATE, sigDigestSignUpdate), + fn(OSSL_FUNC_SIGNATURE_DIGEST_SIGN_FINAL, sigDigestSignFinal), + fn(OSSL_FUNC_SIGNATURE_SET_CTX_PARAMS, sigSetParams), + fn(OSSL_FUNC_SIGNATURE_SETTABLE_CTX_PARAMS, sigSettableParams), + {0, nullptr} +}; + +// +// Provider +// + +const OSSL_ALGORITHM *providerQuery(void * /*provctx*/, int operation_id, int *no_cache) +{ + static const OSSL_ALGORITHM keymgmts[] { + {"RSA:rsaEncryption:1.2.840.113549.1.1.1", PROVIDER_PROPS, keymgmt, nullptr}, + {"EC:id-ecPublicKey:1.2.840.10045.2.1", PROVIDER_PROPS, keymgmt, nullptr}, + {nullptr, nullptr, nullptr, nullptr} + }; + static const OSSL_ALGORITHM signatures[] { + {"RSA:rsaEncryption:1.2.840.113549.1.1.1", PROVIDER_PROPS, signature, nullptr}, + {ECDSA_NAME, PROVIDER_PROPS, signature, nullptr}, + {nullptr, nullptr, nullptr, nullptr} + }; + *no_cache = 0; + return operation_id == OSSL_OP_KEYMGMT ? keymgmts : operation_id == OSSL_OP_SIGNATURE ? signatures : nullptr; +} + +int providerInit(const OSSL_CORE_HANDLE *handle, const OSSL_DISPATCH * /*in*/, const OSSL_DISPATCH **out, void **provctx) +{ + static const OSSL_DISPATCH dispatch[] { + fn(OSSL_FUNC_PROVIDER_QUERY_OPERATION, providerQuery), + {0, nullptr} + }; + *out = dispatch; + *provctx = const_cast(handle); + return 1; +} + +} // namespace + +libcdoc::unique_ptr_t +libcdoc::createTLSKey(NetworkBackend *network, X509 *cert) +{ + // Set up once and kept for the life of the process. Public keys and digests come + // from the default provider, private key operations from ours. + static OSSL_LIB_CTX *ctx = [] { + libctx = OSSL_LIB_CTX_new(); + if (libctx && (!OSSL_PROVIDER_load(libctx, "default") || + !OSSL_PROVIDER_add_builtin(libctx, "libcdoc-tls", providerInit) || + !OSSL_PROVIDER_load(libctx, "libcdoc-tls"))) { + LOG_ERROR("Failed to load the TLS client key provider"); + OSSL_LIB_CTX_free(libctx); + libctx = nullptr; + } + return libctx; + }(); + EVP_PKEY *pub = cert ? X509_get0_pubkey(cert) : nullptr; + const char *type = !pub ? nullptr : EVP_PKEY_is_a(pub, RSA_NAME) ? RSA_NAME : EVP_PKEY_is_a(pub, EC_NAME) ? EC_NAME : nullptr; + OSSL_PARAM *pubData = nullptr; + if (!ctx || !network || !type || EVP_PKEY_todata(pub, EVP_PKEY_PUBLIC_KEY, &pubData) != 1) { + LOG_ERROR("Unsupported TLS client key"); + return make_unique_ptr(nullptr); + } + auto pubParams = make_unique_ptr(pubData); + OSSL_PARAM networkParam[] { + OSSL_PARAM_construct_octet_string(NETWORK_PARAM, &network, sizeof(network)), + OSSL_PARAM_construct_end() + }; + // The merged list points into pubParams, which must outlive it + auto params = make_unique_ptr(OSSL_PARAM_merge(pubParams.get(), networkParam)); + auto kctx = make_unique_ptr(EVP_PKEY_CTX_new_from_name(ctx, type, PROVIDER_PROPS)); + EVP_PKEY *key = nullptr; + if (!params || !kctx || EVP_PKEY_fromdata_init(kctx.get()) != 1 || + EVP_PKEY_fromdata(kctx.get(), &key, EVP_PKEY_KEYPAIR, params.get()) != 1) + LOG_ERROR("Failed to create the TLS client key"); + return make_unique_ptr(key); +} diff --git a/cdoc/TLSKey.h b/cdoc/TLSKey.h new file mode 100644 index 00000000..76d5f62a --- /dev/null +++ b/cdoc/TLSKey.h @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: Estonian Information System Authority +// SPDX-License-Identifier: LGPL-2.1-or-later + +#ifndef __TLSKEY_H__ +#define __TLSKEY_H__ + +#include "utils/memory.h" + +#include + +namespace libcdoc { + +class NetworkBackend; + +/** + * @brief Client TLS key whose private operations go to NetworkBackend::signTLS + * + * The key belongs to an OpenSSL provider of libcdoc's own, so OpenSSL hands it the digest + * and, for RSA, the padding, including the RSASSA-PSS that TLS 1.3 requires. + * + * @param network the backend to sign with; it must outlive the key + * @param cert the client certificate, which gives the public key + * @return the key, empty for an unsupported key type or on error + */ +unique_ptr_t createTLSKey(NetworkBackend *network, X509 *cert); + +} // namespace libcdoc + +#endif // __TLSKEY_H__ diff --git a/cdoc/WinBackend.cpp b/cdoc/WinBackend.cpp index 5aa62c42..37df2a0b 100644 --- a/cdoc/WinBackend.cpp +++ b/cdoc/WinBackend.cpp @@ -399,7 +399,6 @@ libcdoc::WinBackend::sign(std::vector& dst, HashAlgorithm algorithm, co default: return INVALID_PARAMS; } - BCRYPT_PKCS1_PADDING_INFO rsaPKCS1 { rsaPSS.pszAlgId }; DWORD size; NCryptGetProperty(d->key, NCRYPT_ALGORITHM_GROUP_PROPERTY, nullptr, 0, &size, 0); std::wstring algo(5, 0); @@ -408,12 +407,9 @@ libcdoc::WinBackend::sign(std::vector& dst, HashAlgorithm algorithm, co bool isRSA = (algo == L"RSA"); DWORD padding {}; PVOID paddingInfo {}; - if(isRSA && usePSS(idx)) { + if(isRSA) { padding = BCRYPT_PAD_PSS; paddingInfo = &rsaPSS; - } else if(isRSA) { - padding = BCRYPT_PAD_PKCS1; - paddingInfo = &rsaPKCS1; } SECURITY_STATUS err = NCryptSignHash(d->key, paddingInfo, PBYTE(digest.data()), DWORD(digest.size()), nullptr, 0, &size, padding); if (err != ERROR_SUCCESS) return CRYPTO_ERROR; diff --git a/cdoc/WinBackend.h b/cdoc/WinBackend.h index 5cd5851c..019ab689 100644 --- a/cdoc/WinBackend.h +++ b/cdoc/WinBackend.h @@ -53,14 +53,6 @@ struct CDOC_EXPORT WinBackend : public CryptoBackend { * @return error code or OK */ virtual result_t connectToKey(int idx, bool priv) = 0; - /** - * @brief whether to use PSS RSA padding - * - * A subclass should overwrite this to inform the backend about the correct padding. - * @param idx a lock idx - * @return true if PSS padding is sued - */ - virtual result_t usePSS(int idx) {return true;} virtual result_t decryptRSA(std::vector& dst, const std::vector& data, bool oaep, unsigned int idx); virtual result_t deriveConcatKDF(std::vector& dst, const std::vector &public_key, const std::string &digest,