#include <openssl/err.h>
#include <openssl/cms.h>
#include "cms_lcl.h"
+#include "internal/nelem.h"
+
+/*-
+ * Attribute flags.
+ * CMS attribute restrictions are discussed in
+ * - RFC 5652 Section 11.
+ * ESS attribute restrictions are discussed in
+ * - RFC 2634 Section 1.3.4 AND
+ * - RFC 5035 Section 5.4
+ */
+/* This is a signed attribute */
+#define CMS_ATTR_F_SIGNED 0x01
+/* This is an unsigned attribute */
+#define CMS_ATTR_F_UNSIGNED 0x02
+/* Must be present if there are any other attributes of the same type */
+#define CMS_ATTR_F_REQUIRED_COND 0x10
+/* There can only be one instance of this attribute */
+#define CMS_ATTR_F_ONLY_ONE 0x20
+/* The Attribute's value must have exactly one entry */
+#define CMS_ATTR_F_ONE_ATTR_VALUE 0x40
+
+/* Attributes rules for different attributes */
+static const struct {
+ int nid; /* The attribute id */
+ int flags;
+} cms_attribute_properties[] = {
+ /* See RFC Section 11 */
+ { NID_pkcs9_contentType, CMS_ATTR_F_SIGNED
+ | CMS_ATTR_F_ONLY_ONE
+ | CMS_ATTR_F_ONE_ATTR_VALUE
+ | CMS_ATTR_F_REQUIRED_COND },
+ { NID_pkcs9_messageDigest, CMS_ATTR_F_SIGNED
+ | CMS_ATTR_F_ONLY_ONE
+ | CMS_ATTR_F_ONE_ATTR_VALUE
+ | CMS_ATTR_F_REQUIRED_COND },
+ { NID_pkcs9_signingTime, CMS_ATTR_F_SIGNED
+ | CMS_ATTR_F_ONLY_ONE
+ | CMS_ATTR_F_ONE_ATTR_VALUE },
+ { NID_pkcs9_countersignature, CMS_ATTR_F_UNSIGNED },
+ /* ESS */
+ { NID_id_smime_aa_signingCertificate, CMS_ATTR_F_SIGNED
+ | CMS_ATTR_F_ONLY_ONE
+ | CMS_ATTR_F_ONE_ATTR_VALUE },
+ { NID_id_smime_aa_signingCertificateV2, CMS_ATTR_F_SIGNED
+ | CMS_ATTR_F_ONLY_ONE
+ | CMS_ATTR_F_ONE_ATTR_VALUE },
+ { NID_id_smime_aa_receiptRequest, CMS_ATTR_F_SIGNED
+ | CMS_ATTR_F_ONLY_ONE
+ | CMS_ATTR_F_ONE_ATTR_VALUE }
+};
/* CMS SignedData Attribute utilities */
return X509at_get0_data_by_OBJ(si->unsignedAttrs, oid, lastpos, type);
}
-/* Specific attribute cases */
+/*
+ * Retrieve an attribute by nid from a stack of attributes starting at index
+ * *lastpos + 1.
+ * Returns the attribute or NULL if there is no attribute.
+ * If an attribute was found *lastpos returns the index of the found attribute.
+ */
+static X509_ATTRIBUTE *cms_attrib_get(int nid,
+ const STACK_OF(X509_ATTRIBUTE) *attrs,
+ int *lastpos)
+{
+ X509_ATTRIBUTE *at;
+ int loc;
+
+ loc = X509at_get_attr_by_NID(attrs, nid, *lastpos);
+ if (loc < 0)
+ return NULL;
+
+ at = X509at_get_attr(attrs, loc);
+ *lastpos = loc;
+ return at;
+}
+
+static int cms_check_attribute(int nid, int flags, int type,
+ const STACK_OF(X509_ATTRIBUTE) *attrs,
+ int have_attrs)
+{
+ int lastpos = -1;
+ X509_ATTRIBUTE *at = cms_attrib_get(nid, attrs, &lastpos);
+
+ if (at != NULL) {
+ int count = X509_ATTRIBUTE_count(at);
+
+ /* Is this attribute allowed? */
+ if (((flags & type) == 0)
+ /* check if multiple attributes of the same type are allowed */
+ || (((flags & CMS_ATTR_F_ONLY_ONE) != 0)
+ && cms_attrib_get(nid, attrs, &lastpos) != NULL)
+ /* Check if attribute should have exactly one value in its set */
+ || (((flags & CMS_ATTR_F_ONE_ATTR_VALUE) != 0)
+ && count != 1)
+ /* There should be at least one value */
+ || count == 0)
+ return 0;
+ } else {
+ /* fail if a required attribute is missing */
+ if (have_attrs
+ && ((flags & CMS_ATTR_F_REQUIRED_COND) != 0)
+ && (flags & type) != 0)
+ return 0;
+ }
+ return 1;
+}
+
+/*
+ * Check that the signerinfo attributes obey the attribute rules which includes
+ * the following checks
+ * - If any signed attributes exist then there must be a Content Type
+ * and Message Digest attribute in the signed attributes.
+ * - The countersignature attribute is an optional unsigned attribute only.
+ * - Content Type, Message Digest, and Signing time attributes are signed
+ * attributes. Only one instance of each is allowed, with each of these
+ * attributes containing a single attribute value in its set.
+ */
+int CMS_si_check_attributes(const CMS_SignerInfo *si)
+{
+ int i;
+ int have_signed_attrs = (CMS_signed_get_attr_count(si) > 0);
+ int have_unsigned_attrs = (CMS_unsigned_get_attr_count(si) > 0);
+
+ for (i = 0; i < (int)OSSL_NELEM(cms_attribute_properties); ++i) {
+ int nid = cms_attribute_properties[i].nid;
+ int flags = cms_attribute_properties[i].flags;
+
+ if (!cms_check_attribute(nid, flags, CMS_ATTR_F_SIGNED,
+ si->signedAttrs, have_signed_attrs)
+ || !cms_check_attribute(nid, flags, CMS_ATTR_F_UNSIGNED,
+ si->unsignedAttrs, have_unsigned_attrs)) {
+ CMSerr(CMS_F_CMS_SI_CHECK_ATTRIBUTES, CMS_R_ATTRIBUTE_ERROR);
+ return 0;
+ }
+ }
+ return 1;
+}
/*
* Generated by util/mkerr.pl DO NOT EDIT
- * Copyright 1995-2018 The OpenSSL Project Authors. All Rights Reserved.
+ * Copyright 1995-2019 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the OpenSSL license (the "License"). You may not use
* this file except in compliance with the License. You can obtain a copy
{ERR_PACK(ERR_LIB_CMS, CMS_F_CMS_SIGNERINFO_VERIFY_CONTENT, 0),
"CMS_SignerInfo_verify_content"},
{ERR_PACK(ERR_LIB_CMS, CMS_F_CMS_SIGN_RECEIPT, 0), "CMS_sign_receipt"},
+ {ERR_PACK(ERR_LIB_CMS, CMS_F_CMS_SI_CHECK_ATTRIBUTES, 0),
+ "CMS_si_check_attributes"},
{ERR_PACK(ERR_LIB_CMS, CMS_F_CMS_STREAM, 0), "CMS_stream"},
{ERR_PACK(ERR_LIB_CMS, CMS_F_CMS_UNCOMPRESS, 0), "CMS_uncompress"},
{ERR_PACK(ERR_LIB_CMS, CMS_F_CMS_VERIFY, 0), "CMS_verify"},
static const ERR_STRING_DATA CMS_str_reasons[] = {
{ERR_PACK(ERR_LIB_CMS, 0, CMS_R_ADD_SIGNER_ERROR), "add signer error"},
+ {ERR_PACK(ERR_LIB_CMS, 0, CMS_R_ATTRIBUTE_ERROR), "attribute error"},
{ERR_PACK(ERR_LIB_CMS, 0, CMS_R_CERTIFICATE_ALREADY_PRESENT),
"certificate already present"},
{ERR_PACK(ERR_LIB_CMS, 0, CMS_R_CERTIFICATE_HAS_NO_KEYID),
/* PWRI routines */
int cms_RecipientInfo_pwri_crypt(CMS_ContentInfo *cms, CMS_RecipientInfo *ri,
int en_de);
+/* SignerInfo routines */
+int CMS_si_check_attributes(const CMS_SignerInfo *si);
DECLARE_ASN1_ITEM(CMS_CertificateChoices)
DECLARE_ASN1_ITEM(CMS_DigestedData)
}
+/*
+ * RFC 5652 Section 11.1 Content Type
+ * The content-type attribute within signed-data MUST
+ * 1) be present if there are signed attributes
+ * 2) match the content type in the signed-data,
+ * 3) be a signed attribute.
+ * 4) not have more than one copy of the attribute.
+ *
+ * Note that since the CMS_SignerInfo_sign() always adds the "signing time"
+ * attribute, the content type attribute MUST be added also.
+ * Assumptions: This assumes that the attribute does not already exist.
+ */
+static int cms_set_si_contentType_attr(CMS_ContentInfo *cms, CMS_SignerInfo *si)
+{
+ ASN1_OBJECT *ctype = cms->d.signedData->encapContentInfo->eContentType;
+
+ /* Add the contentType attribute */
+ return CMS_signed_add1_attr_by_NID(si, NID_pkcs9_contentType,
+ V_ASN1_OBJECT, ctype, -1) > 0;
+}
+
/* Copy an existing messageDigest value */
static int cms_copy_messageDigest(CMS_ContentInfo *cms, CMS_SignerInfo *si)
if (flags & CMS_REUSE_DIGEST) {
if (!cms_copy_messageDigest(cms, si))
goto err;
+ if (!cms_set_si_contentType_attr(cms, si))
+ goto err;
if (!(flags & (CMS_PARTIAL | CMS_KEY_PARAM)) &&
!CMS_SignerInfo_sign(si))
goto err;
*/
if (CMS_signed_get_attr_count(si) >= 0) {
- ASN1_OBJECT *ctype =
- cms->d.signedData->encapContentInfo->eContentType;
unsigned char md[EVP_MAX_MD_SIZE];
unsigned int mdlen;
if (!EVP_DigestFinal_ex(mctx, md, &mdlen))
V_ASN1_OCTET_STRING, md, mdlen))
goto err;
/* Copy content type across */
- if (CMS_signed_add1_attr_by_NID(si, NID_pkcs9_contentType,
- V_ASN1_OBJECT, ctype, -1) <= 0)
+ if (!cms_set_si_contentType_attr(cms, si))
goto err;
+
if (!CMS_SignerInfo_sign(si))
goto err;
} else if (si->pctx) {
goto err;
}
+ if (!CMS_si_check_attributes(si))
+ goto err;
+
if (si->pctx)
pctx = si->pctx;
else {
OPENSSL_free(abuf);
EVP_MD_CTX_reset(mctx);
return 0;
-
}
int CMS_SignerInfo_verify(CMS_SignerInfo *si)
return -1;
}
+ if (!CMS_si_check_attributes(si))
+ return -1;
+
md = EVP_get_digestbyobj(si->digestAlgorithm->algorithm);
if (md == NULL)
return -1;
CMS_F_CMS_SIGNERINFO_VERIFY_CERT:153:cms_signerinfo_verify_cert
CMS_F_CMS_SIGNERINFO_VERIFY_CONTENT:154:CMS_SignerInfo_verify_content
CMS_F_CMS_SIGN_RECEIPT:163:CMS_sign_receipt
+CMS_F_CMS_SI_CHECK_ATTRIBUTES:183:CMS_si_check_attributes
CMS_F_CMS_STREAM:155:CMS_stream
CMS_F_CMS_UNCOMPRESS:156:CMS_uncompress
CMS_F_CMS_VERIFY:157:CMS_verify
BN_R_TOO_MANY_ITERATIONS:113:too many iterations
BN_R_TOO_MANY_TEMPORARY_VARIABLES:109:too many temporary variables
CMS_R_ADD_SIGNER_ERROR:99:add signer error
+CMS_R_ATTRIBUTE_ERROR:161:attribute error
CMS_R_CERTIFICATE_ALREADY_PRESENT:175:certificate already present
CMS_R_CERTIFICATE_HAS_NO_KEYID:160:certificate has no keyid
CMS_R_CERTIFICATE_VERIFY_ERROR:100:certificate verify error
# define CMS_F_CMS_SIGNERINFO_VERIFY_CERT 153
# define CMS_F_CMS_SIGNERINFO_VERIFY_CONTENT 154
# define CMS_F_CMS_SIGN_RECEIPT 163
+# define CMS_F_CMS_SI_CHECK_ATTRIBUTES 183
# define CMS_F_CMS_STREAM 155
# define CMS_F_CMS_UNCOMPRESS 156
# define CMS_F_CMS_VERIFY 157
* CMS reason codes.
*/
# define CMS_R_ADD_SIGNER_ERROR 99
+# define CMS_R_ATTRIBUTE_ERROR 161
# define CMS_R_CERTIFICATE_ALREADY_PRESENT 175
# define CMS_R_CERTIFICATE_HAS_NO_KEYID 160
# define CMS_R_CERTIFICATE_VERIFY_ERROR 100
plan skip_all => "CMS is not supported by this OpenSSL build"
if disabled("cms");
+my $datadir = srctop_dir("test", "recipes", "80-test_cms_data");
my $smdir = srctop_dir("test", "smime-certs");
my $smcont = srctop_file("test", "smcont.txt");
my ($no_des, $no_dh, $no_dsa, $no_ec, $no_ec2m, $no_rc2, $no_zlib)
= disabled qw/des dh dsa ec ec2m rc2 zlib/;
-plan tests => 4;
+plan tests => 6;
my @smime_pkcs7_tests = (
]
);
+my @contenttype_cms_test = (
+ [ "signed content test - check that content type is added to additional signerinfo, RSA keys",
+ [ "-sign", "-binary", "-nodetach", "-stream", "-in", $smcont, "-outform", "DER",
+ "-signer", catfile($smdir, "smrsa1.pem"), "-md", "SHA256",
+ "-out", "test.cms" ],
+ [ "-resign", "-binary", "-nodetach", "-in", "test.cms", "-inform", "DER", "-outform", "DER",
+ "-signer", catfile($smdir, "smrsa2.pem"), "-md", "SHA256",
+ "-out", "test2.cms" ],
+ [ "-verify", "-in", "test2.cms", "-inform", "DER",
+ "-CAfile", catfile($smdir, "smroot.pem"), "-out", "smtst.txt" ]
+ ],
+);
+
+my @incorrect_attribute_cms_test = (
+ "bad_signtime_attr.cms",
+ "no_ct_attr.cms",
+ "no_md_attr.cms",
+ "ct_multiple_attr.cms"
+);
+
subtest "CMS => PKCS#7 compatibility tests\n" => sub {
plan tests => scalar @smime_pkcs7_tests;
}
};
+# Returns the number of matches of a Content Type Attribute in a binary file.
+sub contentType_matches {
+ # Read in a binary file
+ my ($in) = @_;
+ open (HEX_IN, "$in") or die("open failed for $in : $!");
+ binmode(HEX_IN);
+ local $/;
+ my $str = <HEX_IN>;
+
+ # Find ASN1 data for a Content Type Attribute (with a OID of PKCS7 data)
+ my @c = $str =~ /\x30\x18\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x09\x03\x31\x0B\x06\x09\x2A\x86\x48\x86\xF7\x0D\x01\x07\x01/gs;
+
+ close(HEX_IN);
+ return scalar(@c);
+}
+
+subtest "CMS Check the content type attribute is added for additional signers\n" => sub {
+ plan tests =>
+ (scalar @contenttype_cms_test);
+
+ foreach (@contenttype_cms_test) {
+ SKIP: {
+ my $skip_reason = check_availability($$_[0]);
+ skip $skip_reason, 1 if $skip_reason;
+
+ ok(run(app(["openssl", "cms", @{$$_[1]}]))
+ && run(app(["openssl", "cms", @{$$_[2]}]))
+ && contentType_matches("test2.cms") == 2
+ && run(app(["openssl", "cms", @{$$_[3]}])),
+ $$_[0]);
+ }
+ }
+};
+
+subtest "CMS Check that bad attributes fail when verifying signers\n" => sub {
+ plan tests =>
+ (scalar @incorrect_attribute_cms_test);
+
+ foreach my $name (@incorrect_attribute_cms_test) {
+ ok(!run(app(["openssl", "cms", "-verify", "-in",
+ catfile($datadir, $name), "-inform", "DER", "-CAfile",
+ catfile($smdir, "smroot.pem"), "-out", "smtst.txt" ])),
+ $name);
+ }
+};
+
unlink "test.cms";
unlink "test2.cms";
unlink "smtst.txt";