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,
"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, '-',
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;
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;
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");
} 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;
}
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;
}
BIO_free(in);
BIO_free(indata);
BIO_free_all(out);
+ OPENSSL_free(digestbin);
OPENSSL_free(passin);
NCONF_free(conf);
return ret;
/*
* 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
{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"},
/* 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);
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);
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,
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,
}
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;
*/
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;
} 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) {
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);
}
-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;
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;
}
+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,
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
[B<-resign>]
[B<-sign_receipt>]
[B<-verify_receipt> I<receipt>]
+[B<-digest> I<digest>]
[B<-digest_create>]
[B<-digest_verify>]
[B<-compress>]
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.
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.
=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
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
/*
* 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
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,
/*
* 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
# 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
$no_rc2 = 1 if disabled("legacy");
-plan tests => 12;
+plan tests => 14;
ok(run(test(["pkcs7_test"])), "test pkcs7");
"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;
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