From: Stefan Berger Date: Mon, 13 Oct 2025 16:00:38 +0000 (-0500) Subject: cms: Enable signing with hashless signing for no-attributes case X-Git-Tag: 4.0-PRE-CLANG-FORMAT-WEBKIT~182 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3ee16555ee768f722a86b492b4cec7e013e667e5;p=thirdparty%2Fopenssl.git cms: Enable signing with hashless signing for no-attributes case Enable the ability to sign with a hashless signing schemes, such as ML-DSA in pure mode, in case no attributes are used in CMS. To support this, pass the BIO with the plain data through to the signing function so that key's pure mode signing scheme can hash the data itself. The current implementation relies on a seek'able BIO so that the data stream can be read multiple times for support of multiple keys. Some signing schemes, such as ML-DSA, support the message_update function when signing data, others, such as EdDSA keys do not support it. The former allows for reading data in smaller chunks and calling EVP_PKEY_sign_message_update with the data, while the latter requires that all data are all read into memory and then passed for signing. This latter method could run into out-of-memory issue when signing very large files. Fixes: #28279 Signed-off-by: Stefan Berger Reviewed-by: Dmitry Belyavskiy Reviewed-by: Tomas Mraz (Merged from https://github.com/openssl/openssl/pull/28923) --- diff --git a/crypto/cms/cms_lib.c b/crypto/cms/cms_lib.c index 2bf04310307..9b87d16565b 100644 --- a/crypto/cms/cms_lib.c +++ b/crypto/cms/cms_lib.c @@ -200,10 +200,15 @@ err: /* unfortunately cannot constify SMIME_write_ASN1() due to this function */ int CMS_dataFinal(CMS_ContentInfo *cms, BIO *cmsbio) { - return ossl_cms_DataFinal(cms, cmsbio, NULL, 0); + return ossl_cms_DataFinal(cms, cmsbio, NULL, NULL, 0); } -int ossl_cms_DataFinal(CMS_ContentInfo *cms, BIO *cmsbio, +int CMS_dataFinal_ex(CMS_ContentInfo *cms, BIO *cmsbio, BIO *data) +{ + return ossl_cms_DataFinal(cms, cmsbio, data, NULL, 0); +} + +int ossl_cms_DataFinal(CMS_ContentInfo *cms, BIO *cmsbio, BIO *data, const unsigned char *precomp_md, unsigned int precomp_mdlen) { @@ -244,7 +249,8 @@ int ossl_cms_DataFinal(CMS_ContentInfo *cms, BIO *cmsbio, return ossl_cms_AuthEnvelopedData_final(cms, cmsbio); case NID_pkcs7_signed: - return ossl_cms_SignedData_final(cms, cmsbio, precomp_md, precomp_mdlen); + return ossl_cms_SignedData_final(cms, cmsbio, data, + precomp_md, precomp_mdlen); case NID_pkcs7_digest: return ossl_cms_DigestedData_do_final(cms, cmsbio, 0); diff --git a/crypto/cms/cms_local.h b/crypto/cms/cms_local.h index 7cbc3fd0687..168609bbe81 100644 --- a/crypto/cms/cms_local.h +++ b/crypto/cms/cms_local.h @@ -425,7 +425,7 @@ const char *ossl_cms_ctx_get0_propq(const CMS_CTX *ctx); void ossl_cms_resolve_libctx(CMS_ContentInfo *ci); CMS_ContentInfo *ossl_cms_Data_create(OSSL_LIB_CTX *ctx, const char *propq); -int ossl_cms_DataFinal(CMS_ContentInfo *cms, BIO *cmsbio, +int ossl_cms_DataFinal(CMS_ContentInfo *cms, BIO *cmsbio, BIO *data, const unsigned char *precomp_md, unsigned int precomp_mdlen); @@ -437,7 +437,7 @@ int ossl_cms_DigestedData_do_final(const CMS_ContentInfo *cms, BIO *chain, int verify); BIO *ossl_cms_SignedData_init_bio(CMS_ContentInfo *cms); -int ossl_cms_SignedData_final(CMS_ContentInfo *cms, BIO *chain, +int ossl_cms_SignedData_final(CMS_ContentInfo *cms, BIO *chain, BIO *data, const unsigned char *precomp_md, unsigned int precomp_mdlen); int ossl_cms_set1_SignerIdentifier(CMS_SignerIdentifier *sid, X509 *cert, diff --git a/crypto/cms/cms_sd.c b/crypto/cms/cms_sd.c index 7f7059df289..f9ec57b26ff 100644 --- a/crypto/cms/cms_sd.c +++ b/crypto/cms/cms_sd.c @@ -347,28 +347,43 @@ static const struct { int md_a_must; /* md is a 'MUST' */ int noattr_md_nid; /* in case of 'without signed attributes' */ int noattr_md_a_must; /* noattr_md is a 'MUST' not a 'SHOULD' */ + int has_msg_update; } key2data[] = { - { "ML-DSA-44", NID_shake256, 0, NID_sha512, 0, }, - { "ML-DSA-65", NID_shake256, 0, NID_sha512, 0, }, - { "ML-DSA-87", NID_shake256, 0, NID_sha512, 0, }, - { "ED25519", NID_sha512, 0, NID_sha512, 1, }, /* RFC 8419 */ + { "ML-DSA-44", NID_shake256, 0, NID_sha512, 0, 1, }, + { "ML-DSA-65", NID_shake256, 0, NID_sha512, 0, 1, }, + { "ML-DSA-87", NID_shake256, 0, NID_sha512, 0, 1, }, + { "ED25519", NID_sha512, 0, NID_sha512, 1, 0, }, /* RFC 8419 */ /* RFC 8419 3.1 ED448: id-shake256-len MUST be used => NID_undef for now */ - { "ED448", NID_undef, 1, NID_shake256, 1, }, - { "SLH-DSA-SHA2-128f", NID_sha256, 0, NID_sha256, 0, }, /* RFC 9814 */ - { "SLH-DSA-SHA2-128s", NID_sha256, 0, NID_sha256, 0, }, - { "SLH-DSA-SHA2-192f", NID_sha512, 0, NID_sha512, 0, }, - { "SLH-DSA-SHA2-192s", NID_sha512, 0, NID_sha512, 0, }, - { "SLH-DSA-SHA2-256f", NID_sha512, 0, NID_sha512, 0, }, - { "SLH-DSA-SHA2-256s", NID_sha512, 0, NID_sha512, 0, }, - { "SLH-DSA-SHAKE-128f", NID_shake128, 0, NID_shake128, 0, }, - { "SLH-DSA-SHAKE-128s", NID_shake128, 0, NID_shake128, 0, }, - { "SLH-DSA-SHAKE-192f", NID_shake256, 0, NID_shake256, 0, }, - { "SLH-DSA-SHAKE-192s", NID_shake256, 0, NID_shake256, 0, }, - { "SLH-DSA-SHAKE-256f", NID_shake256, 0, NID_shake256, 0, }, - { "SLH-DSA-SHAKE-256s", NID_shake256, 0, NID_shake256, 0, }, - { NULL, NID_undef, NID_undef, 0, } /* last */ + { "ED448", NID_undef, 1, NID_shake256, 1, 0, }, + { "SLH-DSA-SHA2-128f", NID_sha256, 0, NID_sha256, 0, 0, }, /* RFC 9814 */ + { "SLH-DSA-SHA2-128s", NID_sha256, 0, NID_sha256, 0, 0, }, + { "SLH-DSA-SHA2-192f", NID_sha512, 0, NID_sha512, 0, 0, }, + { "SLH-DSA-SHA2-192s", NID_sha512, 0, NID_sha512, 0, 0, }, + { "SLH-DSA-SHA2-256f", NID_sha512, 0, NID_sha512, 0, 0, }, + { "SLH-DSA-SHA2-256s", NID_sha512, 0, NID_sha512, 0, 0, }, + { "SLH-DSA-SHAKE-128f", NID_shake128, 0, NID_shake128, 0, 0, }, + { "SLH-DSA-SHAKE-128s", NID_shake128, 0, NID_shake128, 0, 0, }, + { "SLH-DSA-SHAKE-192f", NID_shake256, 0, NID_shake256, 0, 0, }, + { "SLH-DSA-SHAKE-192s", NID_shake256, 0, NID_shake256, 0, 0, }, + { "SLH-DSA-SHAKE-256f", NID_shake256, 0, NID_shake256, 0, 0, }, + { "SLH-DSA-SHAKE-256s", NID_shake256, 0, NID_shake256, 0, 0, }, + { NULL, NID_undef, 0, NID_undef, 0, 0, } /* last */ }; +static const char *cms_mdless_signing(EVP_PKEY *pkey, + int *has_msg_update) +{ + unsigned int i; + + for (i = 0; key2data[i].name; i++) { + if (EVP_PKEY_is_a(pkey, key2data[i].name)) { + *has_msg_update = key2data[i].has_msg_update; + return key2data[i].name; + } + } + return NULL; +} + static const EVP_MD *ossl_cms_get_default_md(EVP_PKEY *pk, int *md_a_must) { const EVP_MD *md; @@ -820,8 +835,78 @@ ASN1_OCTET_STRING *CMS_SignerInfo_get0_signature(CMS_SignerInfo *si) return si->signature; } +static int cms_bio_read(BIO *in, + unsigned char **buffer, size_t *buffer_len) +{ + unsigned char *tmp; + size_t offset = 0; + int n; + + if (BIO_seek(in, 0) < 0) + return -1; + + *buffer = NULL; + *buffer_len = 0; + + /* read data from BIO into memory */ + do { + *buffer_len += 10240; + tmp = OPENSSL_realloc(*buffer, *buffer_len); + if (!tmp) + goto err; + *buffer = tmp; + + if ((n = BIO_read(in, &(*buffer)[offset], 10240)) <= 0) + goto err; + offset += n; + } while (BIO_eof(in) != 1); + + *buffer_len = offset; + + return 1; + +err: + OPENSSL_free(*buffer); + *buffer = NULL; + return -1; +} + +static int cms_EVP_PKEY_sign(EVP_PKEY_CTX *pctx, BIO *in, + unsigned char *sig, size_t *sig_len, + int has_msg_update) +{ + size_t buffer_len; + int ret; + + if (!has_msg_update) { + unsigned char *buffer = NULL; + + if (cms_bio_read(in, &buffer, &buffer_len) != 1) + return 0; + ret = EVP_PKEY_sign(pctx, sig, sig_len, buffer, buffer_len); + OPENSSL_free(buffer); + } else { + unsigned char buffer[1024]; + int n; + + if (BIO_seek(in, 0) < 0) + return 0; + + do { + n = BIO_read(in, buffer, sizeof(buffer)); + if (n <= 0) + break; + if (EVP_PKEY_sign_message_update(pctx, buffer, n) != 1) + return 0; + } while (!BIO_eof(in)); + ret = EVP_PKEY_sign_message_final(pctx, sig, sig_len); + } + return ret; +} + static int cms_SignerInfo_content_sign(CMS_ContentInfo *cms, CMS_SignerInfo *si, BIO *chain, + BIO *data, const unsigned char *md, unsigned int mdlen) { @@ -829,6 +914,8 @@ static int cms_SignerInfo_content_sign(CMS_ContentInfo *cms, int r = 0; EVP_PKEY_CTX *pctx = NULL; const CMS_CTX *ctx = ossl_cms_get0_cmsctx(cms); + EVP_SIGNATURE *sig_alg = NULL; + unsigned char *sig = NULL; if (mctx == NULL) { ERR_raise(ERR_LIB_CMS, ERR_R_CMS_LIB); @@ -867,7 +954,6 @@ static int cms_SignerInfo_content_sign(CMS_ContentInfo *cms, if (!CMS_SignerInfo_sign(si)) goto err; } else if (si->pctx) { - unsigned char *sig; size_t siglen; unsigned char computed_md[EVP_MAX_MD_SIZE]; @@ -881,14 +967,15 @@ static int cms_SignerInfo_content_sign(CMS_ContentInfo *cms, siglen = EVP_PKEY_get_size(si->pkey); if (siglen == 0 || (sig = OPENSSL_malloc(siglen)) == NULL) goto err; - if (EVP_PKEY_sign(pctx, sig, &siglen, md, mdlen) <= 0) { - OPENSSL_free(sig); + if (EVP_PKEY_sign(pctx, sig, &siglen, md, mdlen) <= 0) goto err; - } ASN1_STRING_set0(si->signature, sig, (int)siglen); + sig = NULL; } else { - unsigned char *sig; + OSSL_LIB_CTX *libctx = ossl_cms_ctx_get0_libctx(ctx); + const char *algorithm; unsigned int siglen; + int has_msg_update; if (md != NULL) { ERR_raise(ERR_LIB_CMS, CMS_R_OPERATION_UNSUPPORTED); @@ -897,26 +984,54 @@ static int cms_SignerInfo_content_sign(CMS_ContentInfo *cms, siglen = EVP_PKEY_get_size(si->pkey); if (siglen == 0 || (sig = OPENSSL_malloc(siglen)) == NULL) goto err; - if (!EVP_SignFinal_ex(mctx, sig, &siglen, si->pkey, - ossl_cms_ctx_get0_libctx(ctx), - ossl_cms_ctx_get0_propq(ctx))) { - ERR_raise(ERR_LIB_CMS, CMS_R_SIGNFINAL_ERROR); - OPENSSL_free(sig); - goto err; + + if ((algorithm = cms_mdless_signing(si->pkey, + &has_msg_update)) != NULL) { + size_t sig_len; + + if (!data) { + ERR_raise(ERR_LIB_CMS, CMS_R_NO_CONTENT); + goto err; + } + + pctx = EVP_PKEY_CTX_new(si->pkey, NULL); + if (!pctx) + goto err; + + sig_alg = EVP_SIGNATURE_fetch(libctx, algorithm, NULL); + if (!sig_alg) + goto err; + + if (EVP_PKEY_sign_message_init(pctx, sig_alg, NULL) != 1) + goto err; + + sig_len = siglen; + if (cms_EVP_PKEY_sign(pctx, data, sig, &sig_len, + has_msg_update) != 1) + goto err; + siglen = (unsigned int)sig_len; + } else { + if (!EVP_SignFinal_ex(mctx, sig, &siglen, si->pkey, libctx, + ossl_cms_ctx_get0_propq(ctx))) { + ERR_raise(ERR_LIB_CMS, CMS_R_SIGNFINAL_ERROR); + goto err; + } } ASN1_STRING_set0(si->signature, sig, siglen); + sig = NULL; } r = 1; err: + OPENSSL_free(sig); EVP_MD_CTX_free(mctx); EVP_PKEY_CTX_free(pctx); + EVP_SIGNATURE_free(sig_alg); return r; - } -int ossl_cms_SignedData_final(CMS_ContentInfo *cms, BIO *chain, +int ossl_cms_SignedData_final(CMS_ContentInfo *cms, BIO *chain, BIO *data, const unsigned char *precomp_md, unsigned int precomp_mdlen) { @@ -927,7 +1042,7 @@ int ossl_cms_SignedData_final(CMS_ContentInfo *cms, BIO *chain, sinfos = CMS_get0_SignerInfos(cms); for (i = 0; i < sk_CMS_SignerInfo_num(sinfos); i++) { si = sk_CMS_SignerInfo_value(sinfos, i); - if (!cms_SignerInfo_content_sign(cms, si, chain, + if (!cms_SignerInfo_content_sign(cms, si, chain, data, precomp_md, precomp_mdlen)) return 0; } diff --git a/crypto/cms/cms_smime.c b/crypto/cms/cms_smime.c index 13601892b81..5be757d2fa9 100644 --- a/crypto/cms/cms_smime.c +++ b/crypto/cms/cms_smime.c @@ -922,7 +922,7 @@ int CMS_final(CMS_ContentInfo *cms, BIO *data, BIO *dcont, unsigned int flags) (void)BIO_flush(cmsbio); - if (!CMS_dataFinal(cms, cmsbio)) { + if (!CMS_dataFinal_ex(cms, cmsbio, data)) { ERR_raise(ERR_LIB_CMS, CMS_R_CMS_DATAFINAL_ERROR); goto err; } @@ -950,7 +950,7 @@ int CMS_final_digest(CMS_ContentInfo *cms, (void)BIO_flush(cmsbio); - if (!ossl_cms_DataFinal(cms, cmsbio, md, mdlen)) { + if (!ossl_cms_DataFinal(cms, cmsbio, NULL, md, mdlen)) { ERR_raise(ERR_LIB_CMS, CMS_R_CMS_DATAFINAL_ERROR); goto err; } diff --git a/doc/man3/CMS_final.pod b/doc/man3/CMS_final.pod index eec4cf31616..7a6e8f976c8 100644 --- a/doc/man3/CMS_final.pod +++ b/doc/man3/CMS_final.pod @@ -2,7 +2,8 @@ =head1 NAME -CMS_final, CMS_final_digest - finalise a CMS_ContentInfo structure +CMS_final, CMS_final_digest, CMS_dataFinal, CMS_dataFinal_ex +- finalise a CMS_ContentInfo structure =head1 SYNOPSIS @@ -11,6 +12,8 @@ CMS_final, CMS_final_digest - finalise a CMS_ContentInfo structure int CMS_final(CMS_ContentInfo *cms, BIO *data, BIO *dcont, unsigned int flags); int CMS_final_digest(CMS_ContentInfo *cms, const unsigned char *md, unsigned int mdlen, BIO *dcont, unsigned int flags); + int CMS_dataFinal(CMS_ContentInfo *cms, BIO *cmsbio); + int CMS_dataFinal_ex(CMS_ContentInfo *cms, BIO *cmsbio, BIO *data); =head1 DESCRIPTION @@ -24,6 +27,17 @@ NULL. CMS_final_digest() finalises the structure B using a pre-computed digest, rather than computing the digest from the original data. +CMS_dataFinal() finalises the structure B using the data provided by +the B BIO for hash-based signing schemes. This BIO can be set up using +CMS_dataInit() and SMIME_ctrl_copy(). + +CMS_dataFinal_ex() finalises the structure B. This function must +be used if hash-less signing schemes, such as ML-DSA, SLH-DSA, or EdDSA, are +used since they require access to the raw (non-hashed) data. The raw data must +be provided by the B BIO. Note that this BIO must support the seek() +function so that its data stream can be read multiple times, once for each +signature created by a hash-less signing scheme. + =head1 NOTES These functions will normally be called when the B flag is used. It @@ -38,7 +52,8 @@ computation from the original message being trusted. =head1 RETURN VALUES -CMS_final() and CMS_final_digest() return 1 for success or 0 for failure. +CMS_final(), CMS_final_digest(), CMS_dataFinal(), and +CMS_dataFinal_ex() return 1 for success or 0 for failure. =head1 SEE ALSO diff --git a/include/openssl/cms.h.in b/include/openssl/cms.h.in index c47b2ca8458..04d9325873a 100644 --- a/include/openssl/cms.h.in +++ b/include/openssl/cms.h.in @@ -105,6 +105,7 @@ const ASN1_OBJECT *CMS_get0_type(const CMS_ContentInfo *cms); BIO *CMS_dataInit(CMS_ContentInfo *cms, BIO *icont); int CMS_dataFinal(CMS_ContentInfo *cms, BIO *bio); +int CMS_dataFinal_ex(CMS_ContentInfo *cms, BIO *bio, BIO *data); ASN1_OCTET_STRING **CMS_get0_content(CMS_ContentInfo *cms); int CMS_is_detached(CMS_ContentInfo *cms); diff --git a/util/libcrypto.num b/util/libcrypto.num index 3e4b53c0250..0b497859e15 100644 --- a/util/libcrypto.num +++ b/util/libcrypto.num @@ -5943,3 +5943,4 @@ OPENSSL_posix_to_tm ? 4_0_0 EXIST::FUNCTION: OPENSSL_tm_to_posix ? 4_0_0 EXIST::FUNCTION: OPENSSL_timegm ? 4_0_0 EXIST::FUNCTION: OSSL_PARAM_clear_free ? 4_0_0 EXIST::FUNCTION: +CMS_dataFinal_ex ? 4_0_0 EXIST::FUNCTION:CMS diff --git a/util/missingcrypto.txt b/util/missingcrypto.txt index 9e6ce68e802..3aaab8bc471 100644 --- a/util/missingcrypto.txt +++ b/util/missingcrypto.txt @@ -308,7 +308,6 @@ CMS_add_simple_smimecap(3) CMS_add_smimecap(3) CMS_add_standard_smimecap(3) CMS_data(3) -CMS_dataFinal(3) CMS_dataInit(3) CMS_decrypt_set1_key(3) CMS_digest_verify(3)