summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/cms.c52
-rw-r--r--crypto/cms/cms_err.c4
-rw-r--r--crypto/cms/cms_lib.c9
-rw-r--r--crypto/cms/cms_local.h7
-rw-r--r--crypto/cms/cms_sd.c34
-rw-r--r--crypto/cms/cms_smime.c25
-rw-r--r--crypto/err/openssl.txt1
-rw-r--r--doc/man1/openssl-cms.pod.in13
-rw-r--r--doc/man3/CMS_final.pod23
-rw-r--r--include/crypto/cmserr.h2
-rw-r--r--include/openssl/cms.h.in3
-rw-r--r--include/openssl/cmserr.h3
-rw-r--r--test/recipes/80-test_cms.t44
-rw-r--r--util/libcrypto.num1
14 files changed, 191 insertions, 30 deletions
diff --git a/apps/cms.c b/apps/cms.c
index 575f8b3625..9c15081cd3 100644
--- a/apps/cms.c
+++ b/apps/cms.c
@@ -66,7 +66,7 @@ typedef enum OPTION_choice {
OPT_DECRYPT, OPT_SIGN, OPT_CADES, OPT_SIGN_RECEIPT, OPT_RESIGN,
OPT_VERIFY, OPT_VERIFY_RETCODE, OPT_VERIFY_RECEIPT,
OPT_CMSOUT, OPT_DATA_OUT, OPT_DATA_CREATE, OPT_DIGEST_VERIFY,
- OPT_DIGEST_CREATE, OPT_COMPRESS, OPT_UNCOMPRESS,
+ OPT_DIGEST, OPT_DIGEST_CREATE, OPT_COMPRESS, OPT_UNCOMPRESS,
OPT_ED_DECRYPT, OPT_ED_ENCRYPT, OPT_DEBUG_DECRYPT, OPT_TEXT,
OPT_ASCIICRLF, OPT_NOINTERN, OPT_NOVERIFY, OPT_NOCERTS,
OPT_NOATTR, OPT_NODETACH, OPT_NOSMIMECAP, OPT_BINARY, OPT_KEYID,
@@ -106,6 +106,7 @@ const OPTIONS cms_options[] = {
"Generate a signed receipt for a message"},
{"verify_receipt", OPT_VERIFY_RECEIPT, '<',
"Verify receipts; exit if receipt signatures do not verify"},
+ {"digest", OPT_DIGEST, 's', "Sign a pre-computed digest in hex notation"},
{"digest_create", OPT_DIGEST_CREATE, '-',
"Create a CMS \"DigestedData\" object"},
{"digest_verify", OPT_DIGEST_VERIFY, '-',
@@ -293,6 +294,9 @@ int cms_main(int argc, char **argv)
const char *CAfile = NULL, *CApath = NULL, *CAstore = NULL;
char *certsoutfile = NULL, *digestname = NULL, *wrapname = NULL;
int noCAfile = 0, noCApath = 0, noCAstore = 0;
+ char *digesthex = NULL;
+ unsigned char *digestbin = NULL;
+ long digestlen = 0;
char *infile = NULL, *outfile = NULL, *rctfile = NULL;
char *passinarg = NULL, *passin = NULL, *signerfile = NULL;
char *originatorfile = NULL, *recipfile = NULL, *ciphername = NULL;
@@ -367,6 +371,9 @@ int cms_main(int argc, char **argv)
case OPT_DIGEST_CREATE:
operation = SMIME_DIGEST_CREATE;
break;
+ case OPT_DIGEST:
+ digesthex = opt_arg();
+ break;
case OPT_DIGEST_VERIFY:
operation = SMIME_DIGEST_VERIFY;
break;
@@ -885,10 +892,31 @@ int cms_main(int argc, char **argv)
goto end;
}
- in = bio_open_default(infile, 'r',
- binary_files ? FORMAT_BINARY : informat);
- if (in == NULL)
- goto end;
+ if (digesthex != NULL) {
+ if (operation != SMIME_SIGN) {
+ BIO_printf(bio_err,
+ "Cannot use -digest for non-signing operation\n");
+ goto end;
+ }
+ if (infile != NULL
+ || (flags & CMS_DETACHED) == 0
+ || (flags & CMS_STREAM) != 0) {
+ BIO_printf(bio_err,
+ "Cannot use -digest when -in, -nodetach or streaming is used\n");
+ goto end;
+ }
+ digestbin = OPENSSL_hexstr2buf(digesthex, &digestlen);
+ if (digestbin == NULL) {
+ BIO_printf(bio_err,
+ "Invalid hex value after -digest\n");
+ goto end;
+ }
+ } else {
+ in = bio_open_default(infile, 'r',
+ binary_files ? FORMAT_BINARY : informat);
+ if (in == NULL)
+ goto end;
+ }
if (operation & SMIME_IP) {
cms = load_content_info(informat, in, flags, &indata, "SMIME");
@@ -1037,12 +1065,12 @@ int cms_main(int argc, char **argv)
} else if (operation & SMIME_SIGNERS) {
int i;
/*
- * If detached data content we enable streaming if S/MIME output
- * format.
+ * If detached data content and not signing pre-computed digest, we
+ * enable streaming if S/MIME output format.
*/
if (operation == SMIME_SIGN) {
- if (flags & CMS_DETACHED) {
+ if ((flags & CMS_DETACHED) != 0 && digestbin == NULL) {
if (outformat == FORMAT_SMIME)
flags |= CMS_STREAM;
}
@@ -1103,7 +1131,12 @@ int cms_main(int argc, char **argv)
key = NULL;
}
/* If not streaming or resigning finalize structure */
- if ((operation == SMIME_SIGN) && !(flags & CMS_STREAM)) {
+ if (operation == SMIME_SIGN && digestbin != NULL
+ && (flags & CMS_STREAM) == 0) {
+ /* Use pre-computed digest instead of content */
+ if (!CMS_final_digest(cms, digestbin, digestlen, NULL, flags))
+ goto end;
+ } else if (operation == SMIME_SIGN && (flags & CMS_STREAM) == 0) {
if (!CMS_final(cms, in, NULL, flags))
goto end;
}
@@ -1272,6 +1305,7 @@ int cms_main(int argc, char **argv)
BIO_free(in);
BIO_free(indata);
BIO_free_all(out);
+ OPENSSL_free(digestbin);
OPENSSL_free(passin);
NCONF_free(conf);
return ret;
diff --git a/crypto/cms/cms_err.c b/crypto/cms/cms_err.c
index 1fba9d8502..293b65a1f4 100644
--- a/crypto/cms/cms_err.c
+++ b/crypto/cms/cms_err.c
@@ -1,6 +1,6 @@
/*
* Generated by util/mkerr.pl DO NOT EDIT
- * Copyright 1995-2021 The OpenSSL Project Authors. All Rights Reserved.
+ * Copyright 1995-2022 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
@@ -109,6 +109,8 @@ static const ERR_STRING_DATA CMS_str_reasons[] = {
{ERR_PACK(ERR_LIB_CMS, 0, CMS_R_NO_PUBLIC_KEY), "no public key"},
{ERR_PACK(ERR_LIB_CMS, 0, CMS_R_NO_RECEIPT_REQUEST), "no receipt request"},
{ERR_PACK(ERR_LIB_CMS, 0, CMS_R_NO_SIGNERS), "no signers"},
+ {ERR_PACK(ERR_LIB_CMS, 0, CMS_R_OPERATION_UNSUPPORTED),
+ "operation unsupported"},
{ERR_PACK(ERR_LIB_CMS, 0, CMS_R_PEER_KEY_ERROR), "peer key error"},
{ERR_PACK(ERR_LIB_CMS, 0, CMS_R_PRIVATE_KEY_DOES_NOT_MATCH_CERTIFICATE),
"private key does not match certificate"},
diff --git a/crypto/cms/cms_lib.c b/crypto/cms/cms_lib.c
index 0b25bc234e..4d5a11624f 100644
--- a/crypto/cms/cms_lib.c
+++ b/crypto/cms/cms_lib.c
@@ -208,6 +208,13 @@ 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);
+}
+
+int ossl_cms_DataFinal(CMS_ContentInfo *cms, BIO *cmsbio,
+ const unsigned char *precomp_md,
+ unsigned int precomp_mdlen)
+{
ASN1_OCTET_STRING **pos = CMS_get0_content(cms);
if (pos == NULL)
@@ -245,7 +252,7 @@ int 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);
+ return ossl_cms_SignedData_final(cms, cmsbio, 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 e9fb43715a..d44bf3b0a1 100644
--- a/crypto/cms/cms_local.h
+++ b/crypto/cms/cms_local.h
@@ -395,6 +395,9 @@ 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,
+ const unsigned char *precomp_md,
+ unsigned int precomp_mdlen);
CMS_ContentInfo *ossl_cms_DigestedData_create(const EVP_MD *md,
OSSL_LIB_CTX *libctx,
@@ -404,7 +407,9 @@ 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,
+ const unsigned char *precomp_md,
+ unsigned int precomp_mdlen);
int ossl_cms_set1_SignerIdentifier(CMS_SignerIdentifier *sid, X509 *cert,
int type, const CMS_CTX *ctx);
int ossl_cms_SignerIdentifier_get0_signer_id(CMS_SignerIdentifier *sid,
diff --git a/crypto/cms/cms_sd.c b/crypto/cms/cms_sd.c
index d1e5ec8b4e..ee210eab29 100644
--- a/crypto/cms/cms_sd.c
+++ b/crypto/cms/cms_sd.c
@@ -694,7 +694,9 @@ ASN1_OCTET_STRING *CMS_SignerInfo_get0_signature(CMS_SignerInfo *si)
}
static int cms_SignerInfo_content_sign(CMS_ContentInfo *cms,
- CMS_SignerInfo *si, BIO *chain)
+ CMS_SignerInfo *si, BIO *chain,
+ const unsigned char *md,
+ unsigned int mdlen)
{
EVP_MD_CTX *mctx = EVP_MD_CTX_new();
int r = 0;
@@ -722,11 +724,13 @@ static int cms_SignerInfo_content_sign(CMS_ContentInfo *cms,
*/
if (CMS_signed_get_attr_count(si) >= 0) {
- unsigned char md[EVP_MAX_MD_SIZE];
- unsigned int mdlen;
+ unsigned char computed_md[EVP_MAX_MD_SIZE];
- if (!EVP_DigestFinal_ex(mctx, md, &mdlen))
- goto err;
+ if (md == NULL) {
+ if (!EVP_DigestFinal_ex(mctx, computed_md, &mdlen))
+ goto err;
+ md = computed_md;
+ }
if (!CMS_signed_add1_attr_by_NID(si, NID_pkcs9_messageDigest,
V_ASN1_OCTET_STRING, md, mdlen))
goto err;
@@ -739,12 +743,14 @@ static int cms_SignerInfo_content_sign(CMS_ContentInfo *cms,
} else if (si->pctx) {
unsigned char *sig;
size_t siglen;
- unsigned char md[EVP_MAX_MD_SIZE];
- unsigned int mdlen;
+ unsigned char computed_md[EVP_MAX_MD_SIZE];
pctx = si->pctx;
- if (!EVP_DigestFinal_ex(mctx, md, &mdlen))
- goto err;
+ if (md == NULL) {
+ if (!EVP_DigestFinal_ex(mctx, computed_md, &mdlen))
+ goto err;
+ md = computed_md;
+ }
siglen = EVP_PKEY_get_size(si->pkey);
sig = OPENSSL_malloc(siglen);
if (sig == NULL) {
@@ -760,6 +766,10 @@ static int cms_SignerInfo_content_sign(CMS_ContentInfo *cms,
unsigned char *sig;
unsigned int siglen;
+ if (md != NULL) {
+ ERR_raise(ERR_LIB_CMS, CMS_R_OPERATION_UNSUPPORTED);
+ goto err;
+ }
sig = OPENSSL_malloc(EVP_PKEY_get_size(si->pkey));
if (sig == NULL) {
ERR_raise(ERR_LIB_CMS, ERR_R_MALLOC_FAILURE);
@@ -784,7 +794,9 @@ static int cms_SignerInfo_content_sign(CMS_ContentInfo *cms,
}
-int ossl_cms_SignedData_final(CMS_ContentInfo *cms, BIO *chain)
+int ossl_cms_SignedData_final(CMS_ContentInfo *cms, BIO *chain,
+ const unsigned char *precomp_md,
+ unsigned int precomp_mdlen)
{
STACK_OF(CMS_SignerInfo) *sinfos;
CMS_SignerInfo *si;
@@ -793,7 +805,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, precomp_md, precomp_mdlen))
return 0;
}
cms->d.signedData->encapContentInfo->partial = 0;
diff --git a/crypto/cms/cms_smime.c b/crypto/cms/cms_smime.c
index b82eee32cb..690ea3291b 100644
--- a/crypto/cms/cms_smime.c
+++ b/crypto/cms/cms_smime.c
@@ -897,6 +897,31 @@ err:
}
+int CMS_final_digest(CMS_ContentInfo *cms,
+ const unsigned char *md, unsigned int mdlen,
+ BIO *dcont, unsigned int flags)
+{
+ BIO *cmsbio;
+ int ret = 0;
+
+ if ((cmsbio = CMS_dataInit(cms, dcont)) == NULL) {
+ ERR_raise(ERR_LIB_CMS, CMS_R_CMS_LIB);
+ return 0;
+ }
+
+ (void)BIO_flush(cmsbio);
+
+ if (!ossl_cms_DataFinal(cms, cmsbio, md, mdlen)) {
+ ERR_raise(ERR_LIB_CMS, CMS_R_CMS_DATAFINAL_ERROR);
+ goto err;
+ }
+ ret = 1;
+
+err:
+ do_free_upto(cmsbio, dcont);
+ return ret;
+}
+
#ifdef ZLIB
int CMS_uncompress(CMS_ContentInfo *cms, BIO *dcont, BIO *out,
diff --git a/crypto/err/openssl.txt b/crypto/err/openssl.txt
index c4a94f9559..27f7247235 100644
--- a/crypto/err/openssl.txt
+++ b/crypto/err/openssl.txt
@@ -343,6 +343,7 @@ CMS_R_NO_PRIVATE_KEY:133:no private key
CMS_R_NO_PUBLIC_KEY:134:no public key
CMS_R_NO_RECEIPT_REQUEST:168:no receipt request
CMS_R_NO_SIGNERS:135:no signers
+CMS_R_OPERATION_UNSUPPORTED:182:operation unsupported
CMS_R_PEER_KEY_ERROR:188:peer key error
CMS_R_PRIVATE_KEY_DOES_NOT_MATCH_CERTIFICATE:136:\
private key does not match certificate
diff --git a/doc/man1/openssl-cms.pod.in b/doc/man1/openssl-cms.pod.in
index c63a7f330b..632d7fe857 100644
--- a/doc/man1/openssl-cms.pod.in
+++ b/doc/man1/openssl-cms.pod.in
@@ -25,6 +25,7 @@ Operation options:
[B<-resign>]
[B<-sign_receipt>]
[B<-verify_receipt> I<receipt>]
+[B<-digest> I<digest>]
[B<-digest_create>]
[B<-digest_verify>]
[B<-compress>]
@@ -207,6 +208,16 @@ Verify a signed receipt in filename B<receipt>. The input message B<must>
contain the original receipt request. Functionality is otherwise similar
to the B<-verify> operation.
+=item B<-digest> I<digest>
+
+When used with B<-sign>, provides the digest in hexadecimal form instead of
+computing it from the original message content. Cannot be combined with B<-in>
+or B<-nodetach>.
+
+This operation is the CMS equivalent of L<openssl-pkeyutl(1)> signing.
+When signing a pre-computed digest, the security relies on the digest and its
+computation from the original message being trusted.
+
=item B<-digest_create>
Create a CMS B<DigestedData> type.
@@ -900,6 +911,8 @@ The B<-nameopt> option was added in OpenSSL 3.0.0.
The B<-engine> option was deprecated in OpenSSL 3.0.
+The B<-digest> option was added in OpenSSL 3.1.
+
=head1 COPYRIGHT
Copyright 2008-2021 The OpenSSL Project Authors. All Rights Reserved.
diff --git a/doc/man3/CMS_final.pod b/doc/man3/CMS_final.pod
index 8270d53bc7..1ca1d8b092 100644
--- a/doc/man3/CMS_final.pod
+++ b/doc/man3/CMS_final.pod
@@ -2,13 +2,15 @@
=head1 NAME
-CMS_final - finalise a CMS_ContentInfo structure
+CMS_final, CMS_final_digest - finalise a CMS_ContentInfo structure
=head1 SYNOPSIS
#include <openssl/cms.h>
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);
=head1 DESCRIPTION
@@ -19,24 +21,37 @@ processed. The B<dcont> parameter contains a BIO to write content to after
processing: this is only used with detached data and will usually be set to
NULL.
+CMS_final_digest() finalises the structure B<cms> using a pre-computed digest,
+rather than computing the digest from the original data.
+
=head1 NOTES
-This function will normally be called when the B<CMS_PARTIAL> flag is used. It
+These functions will normally be called when the B<CMS_PARTIAL> flag is used. It
should only be used when streaming is not performed because the streaming
I/O functions perform finalisation operations internally.
+To sign a pre-computed digest, L<CMS_sign(3)> or CMS_sign_ex() is called
+with the B<data> parameter set to NULL before the CMS structure is finalised
+with the digest provided to CMS_final_digest() in binary form.
+When signing a pre-computed digest, the security relies on the digest and its
+computation from the original message being trusted.
+
=head1 RETURN VALUES
-CMS_final() returns 1 for success or 0 for failure.
+CMS_final() and CMS_final_digest() return 1 for success or 0 for failure.
=head1 SEE ALSO
L<ERR_get_error(3)>, L<CMS_sign(3)>,
L<CMS_encrypt(3)>
+=head1 HISTORY
+
+CMS_final_digest() was added in OpenSSL 3.1.
+
=head1 COPYRIGHT
-Copyright 2008-2016 The OpenSSL Project Authors. All Rights Reserved.
+Copyright 2008-2021 The OpenSSL Project Authors. All Rights Reserved.
Licensed under the Apache License 2.0 (the "License"). You may not use
this file except in compliance with the License. You can obtain a copy
diff --git a/include/crypto/cmserr.h b/include/crypto/cmserr.h
index 1de2f9c7d5..a7fcf11fa9 100644
--- a/include/crypto/cmserr.h
+++ b/include/crypto/cmserr.h
@@ -1,6 +1,6 @@
/*
* Generated by util/mkerr.pl DO NOT EDIT
- * Copyright 2020-2021 The OpenSSL Project Authors. All Rights Reserved.
+ * Copyright 2020-2022 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
diff --git a/include/openssl/cms.h.in b/include/openssl/cms.h.in
index da20ddf2f4..e77a9ade6a 100644
--- a/include/openssl/cms.h.in
+++ b/include/openssl/cms.h.in
@@ -119,6 +119,9 @@ int SMIME_write_CMS(BIO *bio, CMS_ContentInfo *cms, BIO *data, int flags);
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);
CMS_ContentInfo *CMS_sign(X509 *signcert, EVP_PKEY *pkey,
STACK_OF(X509) *certs, BIO *data,
diff --git a/include/openssl/cmserr.h b/include/openssl/cmserr.h
index 1c4f4c799d..e8fe9d519d 100644
--- a/include/openssl/cmserr.h
+++ b/include/openssl/cmserr.h
@@ -1,6 +1,6 @@
/*
* Generated by util/mkerr.pl DO NOT EDIT
- * Copyright 1995-2021 The OpenSSL Project Authors. All Rights Reserved.
+ * Copyright 1995-2022 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the Apache License 2.0 (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
@@ -86,6 +86,7 @@
# define CMS_R_NO_PUBLIC_KEY 134
# define CMS_R_NO_RECEIPT_REQUEST 168
# define CMS_R_NO_SIGNERS 135
+# define CMS_R_OPERATION_UNSUPPORTED 182
# define CMS_R_PEER_KEY_ERROR 188
# define CMS_R_PRIVATE_KEY_DOES_NOT_MATCH_CERTIFICATE 136
# define CMS_R_RECEIPT_DECODE_ERROR 169
diff --git a/test/recipes/80-test_cms.t b/test/recipes/80-test_cms.t
index 48a92f735d..61d46a0e80 100644
--- a/test/recipes/80-test_cms.t
+++ b/test/recipes/80-test_cms.t
@@ -50,7 +50,7 @@ my ($no_des, $no_dh, $no_dsa, $no_ec, $no_ec2m, $no_rc2, $no_zlib)
$no_rc2 = 1 if disabled("legacy");
-plan tests => 12;
+plan tests => 14;
ok(run(test(["pkcs7_test"])), "test pkcs7");
@@ -847,6 +847,48 @@ subtest "CMS binary input tests\n" => sub {
"verify binary input with -binary missing -crlfeol");
};
+subtest "CMS signed digest, DER format" => sub {
+ plan tests => 2;
+
+ # Pre-computed SHA256 digest of $smcont in hexadecimal form
+ my $digest = "ff236ef61b396355f75a4cc6e1c306d4c309084ae271a9e2ad6888f10a101b32";
+
+ my $sig_file = "signature.der";
+ ok(run(app(["openssl", "cms", @prov, "-sign", "-digest", $digest,
+ "-outform", "DER",
+ "-certfile", catfile($smdir, "smroot.pem"),
+ "-signer", catfile($smdir, "smrsa1.pem"),
+ "-out", $sig_file])),
+ "CMS sign pre-computed digest, DER format");
+
+ ok(run(app(["openssl", "cms", @prov, "-verify", "-in", $sig_file,
+ "-inform", "DER",
+ "-CAfile", catfile($smdir, "smroot.pem"),
+ "-content", $smcont])),
+ "Verify CMS signed digest, DER format");
+};
+
+subtest "CMS signed digest, S/MIME format" => sub {
+ plan tests => 2;
+
+ # Pre-computed SHA256 digest of $smcont in hexadecimal form
+ my $digest = "ff236ef61b396355f75a4cc6e1c306d4c309084ae271a9e2ad6888f10a101b32";
+
+ my $sig_file = "signature.smime";
+ ok(run(app(["openssl", "cms", @prov, "-sign", "-digest", $digest,
+ "-outform", "SMIME",
+ "-certfile", catfile($smdir, "smroot.pem"),
+ "-signer", catfile($smdir, "smrsa1.pem"),
+ "-out", $sig_file])),
+ "CMS sign pre-computed digest, S/MIME format");
+
+ ok(run(app(["openssl", "cms", @prov, "-verify", "-in", $sig_file,
+ "-inform", "SMIME",
+ "-CAfile", catfile($smdir, "smroot.pem"),
+ "-content", $smcont])),
+ "Verify CMS signed digest, S/MIME format");
+};
+
sub check_availability {
my $tnam = shift;
diff --git a/util/libcrypto.num b/util/libcrypto.num
index 394f454732..98cc061f30 100644
--- a/util/libcrypto.num
+++ b/util/libcrypto.num
@@ -5437,3 +5437,4 @@ BN_signed_bn2native ? 3_1_0 EXIST::FUNCTION:
ASYNC_set_mem_functions ? 3_1_0 EXIST::FUNCTION:
ASYNC_get_mem_functions ? 3_1_0 EXIST::FUNCTION:
BIO_ADDR_dup ? 3_1_0 EXIST::FUNCTION:SOCK
+CMS_final_digest ? 3_1_0 EXIST::FUNCTION:CMS