ASN1_IMP_OPT(CMS_AuthEnvelopedData, originatorInfo, CMS_OriginatorInfo, 0),
ASN1_SET_OF(CMS_AuthEnvelopedData, recipientInfos, CMS_RecipientInfo),
ASN1_SIMPLE(CMS_AuthEnvelopedData, authEncryptedContentInfo, CMS_EncryptedContentInfo),
- ASN1_IMP_SET_OF_OPT(CMS_AuthEnvelopedData, authAttrs, X509_ALGOR, 2),
+ ASN1_IMP_SET_OF_OPT(CMS_AuthEnvelopedData, authAttrs, X509_ATTRIBUTE, 1),
ASN1_SIMPLE(CMS_AuthEnvelopedData, mac, ASN1_OCTET_STRING),
- ASN1_IMP_SET_OF_OPT(CMS_AuthEnvelopedData, unauthAttrs, X509_ALGOR, 3)
+ ASN1_IMP_SET_OF_OPT(CMS_AuthEnvelopedData, unauthAttrs, X509_ATTRIBUTE, 2)
} ASN1_NDEF_SEQUENCE_END(CMS_AuthEnvelopedData)
ASN1_NDEF_SEQUENCE(CMS_AuthenticatedData) = {
return cms_EnvelopedData_Decryption_init_bio(cms);
}
+/* The DER encoding of authAttrs, with the universal SET OF tag, is the AAD */
+static int cms_AuthEnvelopedData_set_aad(BIO *b,
+ STACK_OF(X509_ATTRIBUTE) *authAttrs)
+{
+ EVP_CIPHER_CTX *ctx;
+ unsigned char *aad = NULL;
+ int aadlen, outl, ok = 0;
+ const ASN1_ITEM *item;
+
+ if (!BIO_get_cipher_ctx(b, &ctx))
+ return 0;
+ item = EVP_CIPHER_CTX_is_encrypting(ctx)
+ ? ASN1_ITEM_rptr(CMS_Attributes_AadEncrypt)
+ : ASN1_ITEM_rptr(CMS_Attributes_AadDecrypt);
+ aadlen = ASN1_item_i2d((ASN1_VALUE *)authAttrs, &aad, item);
+ if (aadlen <= 0 || aad == NULL) {
+ ERR_raise(ERR_LIB_CMS, ERR_R_ASN1_LIB);
+ goto err;
+ }
+ if (EVP_CipherUpdate(ctx, NULL, &outl, aad, aadlen) <= 0) {
+ ERR_raise(ERR_LIB_CMS, CMS_R_CTRL_FAILURE);
+ goto err;
+ }
+ ok = 1;
+err:
+ OPENSSL_free(aad);
+ return ok;
+}
+
BIO *ossl_cms_AuthEnvelopedData_init_bio(CMS_ContentInfo *cms)
{
CMS_EncryptedContentInfo *ec;
ec->taglen = aenv->mac->length;
}
ret = ossl_cms_EncryptedContent_init_bio(ec, ossl_cms_get0_cmsctx(cms), 1);
+ if (ret == NULL)
+ return NULL;
+
+ /* authAttrs, if present, are the AEAD associated data */
+ if (aenv->authAttrs != NULL
+ && !cms_AuthEnvelopedData_set_aad(ret, aenv->authAttrs))
+ goto err;
- /* If error or no cipher end of processing */
- if (ret == NULL || ec->cipher == NULL)
+ /* If no cipher end of processing */
+ if (ec->cipher == NULL)
return ret;
/* Now encrypt content key according to each RecipientInfo type */
DECLARE_ASN1_ITEM(CMS_IssuerAndSerialNumber)
DECLARE_ASN1_ITEM(CMS_Attributes_Sign)
DECLARE_ASN1_ITEM(CMS_Attributes_Verify)
+/* The authAttrs AAD encoding matches the signed-attributes one */
+#define CMS_Attributes_AadEncrypt_it CMS_Attributes_Sign_it
+#define CMS_Attributes_AadDecrypt_it CMS_Attributes_Verify_it
DECLARE_ASN1_ITEM(CMS_RecipientInfo)
DECLARE_ASN1_ITEM(CMS_PasswordRecipientInfo)
DECLARE_ASN1_ALLOC_FUNCTIONS(CMS_IssuerAndSerialNumber)
unsigned char buf[4096];
int r = 0, i;
BIO *tmpout;
+ BIO *aeadbuf = NULL;
tmpout = cms_get_text_bio(out, flags);
goto err;
}
+ /*
+ * For AEAD content (AuthEnvelopedData) the integrity tag is only verified
+ * once all the ciphertext has been processed, by the
+ * BIO_get_cipher_status() call below. RFC 5083 requires that the plaintext
+ * is not released to the caller until that verification succeeds, so
+ * buffer it in memory and only forward it to the output BIO once the tag
+ * has been checked. When CMS_TEXT is set tmpout is already a memory BIO
+ * that is flushed only on success, so the extra buffering is not needed.
+ */
+ if (tmpout == out && BIO_method_type(in) == BIO_TYPE_CIPHER) {
+ EVP_CIPHER_CTX *ctx = NULL;
+
+ if (BIO_get_cipher_ctx(in, &ctx) > 0 && ctx != NULL
+ && (EVP_CIPHER_get_flags(EVP_CIPHER_CTX_get0_cipher(ctx))
+ & EVP_CIPH_FLAG_AEAD_CIPHER)
+ != 0) {
+ aeadbuf = BIO_new(BIO_s_mem());
+ if (aeadbuf == NULL) {
+ ERR_raise(ERR_LIB_CMS, ERR_R_BIO_LIB);
+ goto err;
+ }
+ /* Return 0 (EOF) rather than a retryable -1 once drained. */
+ BIO_set_mem_eof_return(aeadbuf, 0);
+ tmpout = aeadbuf;
+ }
+ }
+
/* Read all content through chain to process digest, decrypt etc */
for (;;) {
i = BIO_read(in, buf, sizeof(buf));
ERR_raise(ERR_LIB_CMS, CMS_R_SMIME_TEXT_ERROR);
goto err;
}
+ } else if (aeadbuf != NULL) {
+ /* Forward the AEAD BIO to out BIO as the tag has been verified. */
+ for (;;) {
+ i = BIO_read(aeadbuf, buf, sizeof(buf));
+ if (i < 0)
+ goto err;
+ if (i == 0)
+ break;
+ if (BIO_write(out, buf, i) != i)
+ goto err;
+ }
}
r = 1;
$no_rc2 = 1 if disabled("legacy");
-plan tests => 39;
+plan tests => 40;
ok(run(test(["pkcs7_test"])), "test pkcs7");
return (-e "$opts{output}.txt" && -z "$opts{output}.txt");
}
+sub read_file_text {
+ my ($file) = @_;
+ open(my $fh, "<", $file) or return undef;
+ binmode $fh;
+ local $/;
+ my $data = <$fh>;
+ close($fh);
+ # Normalise line endings as -out is written in text mode on Windows.
+ $data =~ s/\r\n/\n/g if defined $data;
+ return $data;
+}
+
subtest "CMS => PKCS#7 compatibility tests\n" => sub {
plan tests => scalar @smime_pkcs7_tests;
}
};
+subtest "CMS decrypt authEnvelopedData with authenticated attributes\n" => sub {
+ plan tests => 4;
+
+ # BouncyCastle AES-128-GCM authEnvelopedData (KEK) carrying authAttrs;
+ # a clean decrypt confirms the authAttrs are verified as the AEAD AAD.
+ 1 while unlink "authattrs.txt";
+ ok(run(app(["openssl", "cms", @defaultprov, "-decrypt", "-inform", "PEM",
+ "-secretkey", "000102030405060708090A0B0C0D0E0F",
+ "-secretkeyid", "C0FEE0",
+ "-in", catfile($datadir, "authenveloped_attrs.pem"),
+ "-out", "authattrs.txt" ])),
+ "decrypt authEnvelopedData with authAttrs");
+ is(read_file_text("authattrs.txt"), "Hello AuthEnvelopedData world\n",
+ "decrypted authEnvelopedData plaintext matches expected");
+
+ # A flipped authAttrs byte must fail the tag check and leave -out empty.
+ 1 while unlink "bad_authattrs.txt";
+ ok(!run(app(["openssl", "cms", @defaultprov, "-decrypt", "-inform", "PEM",
+ "-secretkey", "000102030405060708090A0B0C0D0E0F",
+ "-secretkeyid", "C0FEE0",
+ "-in", catfile($datadir, "bad_authenveloped_attrs.pem"),
+ "-out", "bad_authattrs.txt" ])),
+ "reject authEnvelopedData with tampered authAttrs");
+ ok(!-s "bad_authattrs.txt",
+ "tampered authEnvelopedData leaks no plaintext to -out");
+};
+
subtest "CAdES <=> CAdES consistency tests\n" => sub {
plan tests => (scalar @smime_cms_cades_tests);
--- /dev/null
+-----BEGIN CMS-----
+MIAGCyqGSIb3DQEJEAEXoIAwgAIBADEzojECAQQwBQQDwP7gMAsGCWCGSAFlAwQB
+BQQYknpV85muZoLZSPkwi5Ll1Z1HwzAeZThVMIAGCSqGSIb3DQEHATAeBglghkgB
+ZQMEAQYwEQQMkCQb305essfGO2nqAgEQoIAEHvYjM7EK9qZAHgoohdcbSHXe0lGJ
+/Hjk3nkK5VsHxgAAAAChFjAUBgkrBgEEAYaNHwExBwwFaGVsbG8EENeGq4IXAd1O
+iv8hMl+lHZOiFjAUBgkrBgEEAYaNHwIxBwwFd29ybGQAAAAAAAA=
+-----END CMS-----
--- /dev/null
+-----BEGIN CMS-----
+MIAGCyqGSIb3DQEJEAEXoIAwgAIBADEzojECAQQwBQQDwP7gMAsGCWCGSAFlAwQB
+BQQYknpV85muZoLZSPkwi5Ll1Z1HwzAeZThVMIAGCSqGSIb3DQEHATAeBglghkgB
+ZQMEAQYwEQQMkCQb305essfGO2nqAgEQoIAEHvYjM7EK9qZAHgoohdcbSHXe0lGJ
+/Hjk3nkK5VsHxgAAAAChFjAUBgkrBgEEAYaNHwExBwwFaWVsbG8EENeGq4IXAd1O
+iv8hMl+lHZOiFjAUBgkrBgEEAYaNHwIxBwwFd29ybGQAAAAAAAA=
+-----END CMS-----