]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
cms: Enable signing with hashless signing for no-attributes case
authorStefan Berger <stefanb@linux.ibm.com>
Mon, 13 Oct 2025 16:00:38 +0000 (11:00 -0500)
committerTomas Mraz <tomas@openssl.org>
Tue, 18 Nov 2025 17:03:24 +0000 (18:03 +0100)
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 <stefanb@linux.ibm.com>
Reviewed-by: Dmitry Belyavskiy <beldmit@gmail.com>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/28923)

crypto/cms/cms_lib.c
crypto/cms/cms_local.h
crypto/cms/cms_sd.c
crypto/cms/cms_smime.c
doc/man3/CMS_final.pod
include/openssl/cms.h.in
util/libcrypto.num
util/missingcrypto.txt

index 2bf043103078de1e0dc3b2cdb0f5de0e2af72ced..9b87d16565b281306cc8bf831ab2dccf64481a08 100644 (file)
@@ -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);
index 7cbc3fd06873656247a4c491ab43b55b6ffd2bba..168609bbe819750e6ff202e5cd4b4cfab06c4822 100644 (file)
@@ -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,
index 7f7059df28983a8dfe8b52507fd3d4a04fdfb362..f9ec57b26fff491a00062cd9a34efb2ca2aeb232 100644 (file)
@@ -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;
     }
index 13601892b8179fbaef14bf3b931351673ea9d8cc..5be757d2fa9cc93e547025784ace613df4d946d3 100644 (file)
@@ -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;
     }
index eec4cf31616bc732e6ecf53eb3d848689fbabf83..7a6e8f976c833ce26f191257b965d4c591d5331c 100644 (file)
@@ -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<cms> using a pre-computed digest,
 rather than computing the digest from the original data.
 
+CMS_dataFinal() finalises the structure B<cms> using the data provided by
+the B<cmsbio> 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<cms>. 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<data> 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<CMS_PARTIAL> 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
 
index c47b2ca845846519262bc2a591739182c7f4369f..04d9325873a7d2c01283fdd0da953e3ed4099480 100644 (file)
@@ -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);
index 3e4b53c0250243e0f35019d2f372981322d07017..0b497859e15365945973bef5cd9174de589be1b6 100644 (file)
@@ -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
index 9e6ce68e8028cfb2534ad9e450b5029820c3efdc..3aaab8bc47185c48b8919acc247faefad7ce38d5 100644 (file)
@@ -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)