/** @file Unit tests for the Pkcs7Encrypt() API in BaseCryptLib. These tests exercise both the OpenSSL- and mbedtls-backed implementations. They build envelopedData ContentInfo blobs for one or more recipients with each supported AES-CBC cipher and verify the result structurally (DER walk), plus the parameter-validation contract documented in BaseCryptLib.h. Proving that the content can be recovered is covered by Pkcs7DecryptTests.c. Copyright (c) Microsoft Corporation. All rights reserved. SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include "TestBaseCryptLib.h" #include "Pkcs7EncryptTestCert.h" // // Sample plaintext to encrypt. Length is intentionally not a multiple of the // AES block size to exercise CBC padding within the implementation. // STATIC CONST UINT8 mPkcs7EncryptPlaintext[] = { 'P', 'k', 'c', 's', '7', 'E', 'n', 'c', 'r', 'y', 'p', 't', ' ', 'u', 'n', 'i', 't', '-', 't', 'e', 's', 't', ' ', 'p', 'a', 'y', 'l', 'o', 'a', 'd' }; // // DER OIDs (full tag-length-value form) of the values we expect to see inside // a well-formed envelopedData ContentInfo produced by Pkcs7Encrypt(). // // id-envelopedData = 1.2.840.113549.1.7.3 // STATIC CONST UINT8 mOidEnvelopedData[] = { 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x03 }; // aes-128-cbc = 2.16.840.1.101.3.4.1.2 STATIC CONST UINT8 mOidAes128Cbc[] = { 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x02 }; // aes-192-cbc = 2.16.840.1.101.3.4.1.22 STATIC CONST UINT8 mOidAes192Cbc[] = { 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x16 }; // aes-256-cbc = 2.16.840.1.101.3.4.1.42 STATIC CONST UINT8 mOidAes256Cbc[] = { 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x2A }; // // Minimal ASN.1 / DER walker. Just enough to validate Pkcs7Encrypt output // without pulling in OpenSSL/mbedtls headers from the test. // typedef struct { UINT8 Tag; ///< Identifier octet. UINTN Length; ///< Decoded content length (excludes T+L bytes). CONST UINT8 *Value; ///< Pointer to the first content octet. UINTN Total; ///< Total bytes consumed (T + L + V). } DER_TLV; /** Decode a single ASN.1 BER/DER TLV starting at Buf. @param[in] Buf Input buffer. @param[in] BufLen Length of Buf in bytes. @param[out] Tlv Decoded TLV on success. @retval TRUE Decode succeeded. @retval FALSE Truncated or malformed input. **/ STATIC BOOLEAN DerParseTlv ( IN CONST UINT8 *Buf, IN UINTN BufLen, OUT DER_TLV *Tlv ) { UINTN Pos; UINTN Len; UINTN LenOctets; UINTN Index; if ((Buf == NULL) || (Tlv == NULL) || (BufLen < 2)) { return FALSE; } Tlv->Tag = Buf[0]; Pos = 1; if ((Buf[Pos] & 0x80) == 0) { Len = Buf[Pos]; Pos += 1; } else { LenOctets = Buf[Pos] & 0x7Fu; Pos += 1; if ((LenOctets == 0) || (LenOctets > sizeof (UINTN)) || (BufLen < Pos + LenOctets)) { return FALSE; } Len = 0; for (Index = 0; Index < LenOctets; Index++) { Len = (Len << 8) | Buf[Pos]; Pos += 1; } } if (Len > BufLen - Pos) { return FALSE; } Tlv->Length = Len; Tlv->Value = Buf + Pos; Tlv->Total = Pos + Len; return TRUE; } /** Compare the leading bytes of the TLV's encoded form (tag+length+value) against a reference encoded OID. @param[in] Buf Buffer containing the encoded OID TLV. @param[in] BufLen Bytes available at Buf. @param[in] ExpectedOid Reference DER-encoded OID (T+L+V). @param[in] ExpectedOidLen Length of ExpectedOid. @retval TRUE The first ExpectedOidLen bytes of Buf match ExpectedOid. @retval FALSE Mismatch, or Buf too short. **/ STATIC BOOLEAN DerOidEquals ( IN CONST UINT8 *Buf, IN UINTN BufLen, IN CONST UINT8 *ExpectedOid, IN UINTN ExpectedOidLen ) { if (BufLen < ExpectedOidLen) { return FALSE; } return (BOOLEAN)(CompareMem (Buf, ExpectedOid, ExpectedOidLen) == 0); } /** Walk a PKCS#7 envelopedData ContentInfo produced by Pkcs7Encrypt() and verify it carries the expected content-encryption algorithm and recipient count. Reference: RFC 5652, Sections 3 and 6. ContentInfo ::= SEQUENCE { contentType ContentType, -- id-envelopedData content [0] EXPLICIT EnvelopedData } EnvelopedData ::= SEQUENCE { version CMSVersion, [originatorInfo [0] IMPLICIT OriginatorInfo OPTIONAL,] -- v>=2 recipientInfos RecipientInfos, -- SET OF encryptedContentInfo EncryptedContentInfo [, unprotectedAttrs [1] IMPLICIT UnprotectedAttributes OPTIONAL] } EncryptedContentInfo ::= SEQUENCE { contentType ContentType, contentEncryptionAlgorithm ContentEncryptionAlgorithmIdentifier, encryptedContent [0] IMPLICIT EncryptedContent OPTIONAL } @param[in] Buf Encoded ContentInfo. @param[in] BufLen Length of Buf. @param[in] ExpectedCipherOid DER-encoded OID for the expected content-encryption algorithm. @param[in] ExpectedCipherOidLen Length of ExpectedCipherOid. @param[in] ExpectedRecipientCount Number of RecipientInfo entries expected. @retval TRUE The structure parses and matches expectations. @retval FALSE Parse error or unexpected content. **/ STATIC BOOLEAN ValidatePkcs7EnvelopedData ( IN CONST UINT8 *Buf, IN UINTN BufLen, IN CONST UINT8 *ExpectedCipherOid, IN UINTN ExpectedCipherOidLen, IN UINTN ExpectedRecipientCount ) { DER_TLV Outer; DER_TLV ContentTypeOid; DER_TLV Explicit0; DER_TLV EnvelopedData; DER_TLV Version; DER_TLV Recipients; DER_TLV EncryptedContentInfo; DER_TLV EncContentTypeOid; DER_TLV AlgorithmId; DER_TLV AlgorithmOid; DER_TLV RecipientInfo; CONST UINT8 *Cursor; UINTN Remaining; UINTN Count; UINT8 VersionValue; // // Outer SEQUENCE (ContentInfo). // if (!DerParseTlv (Buf, BufLen, &Outer) || (Outer.Tag != 0x30)) { return FALSE; } Cursor = Outer.Value; Remaining = Outer.Length; // // contentType OID. // if (!DerParseTlv (Cursor, Remaining, &ContentTypeOid) || (ContentTypeOid.Tag != 0x06)) { return FALSE; } if (!DerOidEquals (Cursor, Remaining, mOidEnvelopedData, sizeof (mOidEnvelopedData))) { return FALSE; } Cursor += ContentTypeOid.Total; Remaining -= ContentTypeOid.Total; // // content [0] EXPLICIT EnvelopedData. // if (!DerParseTlv (Cursor, Remaining, &Explicit0) || (Explicit0.Tag != 0xA0)) { return FALSE; } if (!DerParseTlv (Explicit0.Value, Explicit0.Length, &EnvelopedData) || (EnvelopedData.Tag != 0x30)) { return FALSE; } Cursor = EnvelopedData.Value; Remaining = EnvelopedData.Length; // // version INTEGER. // if (!DerParseTlv (Cursor, Remaining, &Version) || (Version.Tag != 0x02)) { return FALSE; } if ((Version.Length != 1) || (Version.Value == NULL)) { return FALSE; } VersionValue = Version.Value[0]; if ((VersionValue != 0) && (VersionValue != 2)) { return FALSE; } Cursor += Version.Total; Remaining -= Version.Total; // // Optional originatorInfo [0] IMPLICIT -- skip if present (v2 producers). // if ((Remaining > 0) && (Cursor[0] == 0xA0)) { DER_TLV Skip; if (!DerParseTlv (Cursor, Remaining, &Skip)) { return FALSE; } Cursor += Skip.Total; Remaining -= Skip.Total; } // // recipientInfos SET OF RecipientInfo. // if (!DerParseTlv (Cursor, Remaining, &Recipients) || (Recipients.Tag != 0x31)) { return FALSE; } // // Count top-level TLVs inside the SET. // { CONST UINT8 *RcptCursor; UINTN RcptRemaining; RcptCursor = Recipients.Value; RcptRemaining = Recipients.Length; Count = 0; while (RcptRemaining > 0) { if (!DerParseTlv (RcptCursor, RcptRemaining, &RecipientInfo)) { return FALSE; } Count += 1; RcptCursor += RecipientInfo.Total; RcptRemaining -= RecipientInfo.Total; } } if (Count != ExpectedRecipientCount) { return FALSE; } Cursor += Recipients.Total; Remaining -= Recipients.Total; // // encryptedContentInfo SEQUENCE { contentType, contentEncryptionAlgorithm, ... }. // if (!DerParseTlv (Cursor, Remaining, &EncryptedContentInfo) || (EncryptedContentInfo.Tag != 0x30)) { return FALSE; } Cursor = EncryptedContentInfo.Value; Remaining = EncryptedContentInfo.Length; if (!DerParseTlv (Cursor, Remaining, &EncContentTypeOid) || (EncContentTypeOid.Tag != 0x06)) { return FALSE; } Cursor += EncContentTypeOid.Total; Remaining -= EncContentTypeOid.Total; // // AlgorithmIdentifier SEQUENCE { OID, parameters OPTIONAL }. // if (!DerParseTlv (Cursor, Remaining, &AlgorithmId) || (AlgorithmId.Tag != 0x30)) { return FALSE; } if (!DerParseTlv (AlgorithmId.Value, AlgorithmId.Length, &AlgorithmOid) || (AlgorithmOid.Tag != 0x06)) { return FALSE; } if (!DerOidEquals ( AlgorithmId.Value, AlgorithmId.Length, ExpectedCipherOid, ExpectedCipherOidLen )) { return FALSE; } return TRUE; } /** Run an end-to-end encryption with a single recipient and validate the output. The resulting envelopedData is checked structurally. Recovering the plaintext is covered separately by the Pkcs7Decrypt() tests. @param[in] CipherNid Symmetric cipher NID to request. @param[in] ExpectedCipherOid Reference DER-encoded OID. @param[in] ExpectedCipherOidLen Length of ExpectedCipherOid. @retval UNIT_TEST_PASSED All assertions held. @retval UNIT_TEST_ERROR_TEST_FAILED Stack build, encrypt, or validation failed. **/ STATIC UNIT_TEST_STATUS RunPkcs7EncryptSingleRecipient ( IN UINT32 CipherNid, IN CONST UINT8 *ExpectedCipherOid, IN UINTN ExpectedCipherOidLen ) { UINT8 *X509Stack; UINT8 *ContentInfo; UINTN ContentInfoSize; BOOLEAN Result; X509Stack = NULL; ContentInfo = NULL; ContentInfoSize = 0; Result = X509ConstructCertificateStack ( &X509Stack, mPkcs7TestRecipient1Cert, (UINTN)sizeof (mPkcs7TestRecipient1Cert), NULL ); UT_ASSERT_TRUE (Result); UT_ASSERT_NOT_NULL (X509Stack); Result = Pkcs7Encrypt ( X509Stack, (UINT8 *)mPkcs7EncryptPlaintext, sizeof (mPkcs7EncryptPlaintext), CipherNid, CRYPTO_PKCS7_DEFAULT, &ContentInfo, &ContentInfoSize ); X509StackFree (X509Stack); UT_ASSERT_TRUE (Result); UT_ASSERT_NOT_NULL (ContentInfo); UT_ASSERT_NOT_EQUAL (ContentInfoSize, 0); Result = ValidatePkcs7EnvelopedData ( ContentInfo, ContentInfoSize, ExpectedCipherOid, ExpectedCipherOidLen, 1 ); FreePool (ContentInfo); UT_ASSERT_TRUE (Result); return UNIT_TEST_PASSED; } /** Happy-path test for AES-128-CBC content encryption. **/ STATIC UNIT_TEST_STATUS EFIAPI TestPkcs7EncryptAes128Cbc ( IN UNIT_TEST_CONTEXT Context ) { return RunPkcs7EncryptSingleRecipient ( CRYPTO_NID_AES128CBC, mOidAes128Cbc, sizeof (mOidAes128Cbc) ); } /** Happy-path test for AES-192-CBC content encryption. **/ STATIC UNIT_TEST_STATUS EFIAPI TestPkcs7EncryptAes192Cbc ( IN UNIT_TEST_CONTEXT Context ) { return RunPkcs7EncryptSingleRecipient ( CRYPTO_NID_AES192CBC, mOidAes192Cbc, sizeof (mOidAes192Cbc) ); } /** Happy-path test for AES-256-CBC content encryption. **/ STATIC UNIT_TEST_STATUS EFIAPI TestPkcs7EncryptAes256Cbc ( IN UNIT_TEST_CONTEXT Context ) { return RunPkcs7EncryptSingleRecipient ( CRYPTO_NID_AES256CBC, mOidAes256Cbc, sizeof (mOidAes256Cbc) ); } /** Build a stack with two distinct recipients and verify the resulting envelopedData carries two RecipientInfo entries. **/ STATIC UNIT_TEST_STATUS EFIAPI TestPkcs7EncryptMultipleRecipients ( IN UNIT_TEST_CONTEXT Context ) { UINT8 *X509Stack; UINT8 *ContentInfo; UINTN ContentInfoSize; BOOLEAN Result; X509Stack = NULL; ContentInfo = NULL; ContentInfoSize = 0; Result = X509ConstructCertificateStack ( &X509Stack, mPkcs7TestRecipient1Cert, (UINTN)sizeof (mPkcs7TestRecipient1Cert), mPkcs7TestRecipient2Cert, (UINTN)sizeof (mPkcs7TestRecipient2Cert), NULL ); UT_ASSERT_TRUE (Result); UT_ASSERT_NOT_NULL (X509Stack); Result = Pkcs7Encrypt ( X509Stack, (UINT8 *)mPkcs7EncryptPlaintext, sizeof (mPkcs7EncryptPlaintext), CRYPTO_NID_AES256CBC, CRYPTO_PKCS7_DEFAULT, &ContentInfo, &ContentInfoSize ); X509StackFree (X509Stack); UT_ASSERT_TRUE (Result); UT_ASSERT_NOT_NULL (ContentInfo); UT_ASSERT_NOT_EQUAL (ContentInfoSize, 0); Result = ValidatePkcs7EnvelopedData ( ContentInfo, ContentInfoSize, mOidAes256Cbc, sizeof (mOidAes256Cbc), 2 ); FreePool (ContentInfo); UT_ASSERT_TRUE (Result); return UNIT_TEST_PASSED; } /** NULL X509Stack must be rejected. **/ STATIC UNIT_TEST_STATUS EFIAPI TestPkcs7EncryptNullX509Stack ( IN UNIT_TEST_CONTEXT Context ) { UINT8 *ContentInfo; UINTN ContentInfoSize; BOOLEAN Result; ContentInfo = (UINT8 *)(UINTN)0xDEADBEEF; ContentInfoSize = 0xA5A5A5; Result = Pkcs7Encrypt ( NULL, (UINT8 *)mPkcs7EncryptPlaintext, sizeof (mPkcs7EncryptPlaintext), CRYPTO_NID_AES256CBC, CRYPTO_PKCS7_DEFAULT, &ContentInfo, &ContentInfoSize ); UT_ASSERT_FALSE (Result); UT_ASSERT_TRUE (ContentInfo == NULL); UT_ASSERT_EQUAL (ContentInfoSize, 0); return UNIT_TEST_PASSED; } /** NULL InData must be rejected. **/ STATIC UNIT_TEST_STATUS EFIAPI TestPkcs7EncryptNullInData ( IN UNIT_TEST_CONTEXT Context ) { UINT8 *X509Stack; UINT8 *ContentInfo; UINTN ContentInfoSize; BOOLEAN Result; X509Stack = NULL; ContentInfo = NULL; ContentInfoSize = 0; Result = X509ConstructCertificateStack ( &X509Stack, mPkcs7TestRecipient1Cert, (UINTN)sizeof (mPkcs7TestRecipient1Cert), NULL ); UT_ASSERT_TRUE (Result); Result = Pkcs7Encrypt ( X509Stack, NULL, sizeof (mPkcs7EncryptPlaintext), CRYPTO_NID_AES256CBC, CRYPTO_PKCS7_DEFAULT, &ContentInfo, &ContentInfoSize ); X509StackFree (X509Stack); UT_ASSERT_FALSE (Result); UT_ASSERT_TRUE (ContentInfo == NULL); UT_ASSERT_EQUAL (ContentInfoSize, 0); return UNIT_TEST_PASSED; } /** NULL ContentInfo output pointer must be rejected. **/ STATIC UNIT_TEST_STATUS EFIAPI TestPkcs7EncryptNullContentInfo ( IN UNIT_TEST_CONTEXT Context ) { UINT8 *X509Stack; UINTN ContentInfoSize; BOOLEAN Result; X509Stack = NULL; ContentInfoSize = 0; Result = X509ConstructCertificateStack ( &X509Stack, mPkcs7TestRecipient1Cert, (UINTN)sizeof (mPkcs7TestRecipient1Cert), NULL ); UT_ASSERT_TRUE (Result); Result = Pkcs7Encrypt ( X509Stack, (UINT8 *)mPkcs7EncryptPlaintext, sizeof (mPkcs7EncryptPlaintext), CRYPTO_NID_AES256CBC, CRYPTO_PKCS7_DEFAULT, NULL, &ContentInfoSize ); X509StackFree (X509Stack); UT_ASSERT_FALSE (Result); UT_ASSERT_EQUAL (ContentInfoSize, 0); return UNIT_TEST_PASSED; } /** NULL ContentInfoSize output pointer must be rejected. **/ STATIC UNIT_TEST_STATUS EFIAPI TestPkcs7EncryptNullContentInfoSize ( IN UNIT_TEST_CONTEXT Context ) { UINT8 *X509Stack; UINT8 *ContentInfo; BOOLEAN Result; X509Stack = NULL; ContentInfo = NULL; Result = X509ConstructCertificateStack ( &X509Stack, mPkcs7TestRecipient1Cert, (UINTN)sizeof (mPkcs7TestRecipient1Cert), NULL ); UT_ASSERT_TRUE (Result); Result = Pkcs7Encrypt ( X509Stack, (UINT8 *)mPkcs7EncryptPlaintext, sizeof (mPkcs7EncryptPlaintext), CRYPTO_NID_AES256CBC, CRYPTO_PKCS7_DEFAULT, &ContentInfo, NULL ); X509StackFree (X509Stack); UT_ASSERT_FALSE (Result); UT_ASSERT_TRUE (ContentInfo == NULL); return UNIT_TEST_PASSED; } /** An unsupported CipherNid (e.g. 0) must be rejected. **/ STATIC UNIT_TEST_STATUS EFIAPI TestPkcs7EncryptUnsupportedCipher ( IN UNIT_TEST_CONTEXT Context ) { UINT8 *X509Stack; UINT8 *ContentInfo; UINTN ContentInfoSize; BOOLEAN Result; X509Stack = NULL; ContentInfo = NULL; ContentInfoSize = 0; Result = X509ConstructCertificateStack ( &X509Stack, mPkcs7TestRecipient1Cert, (UINTN)sizeof (mPkcs7TestRecipient1Cert), NULL ); UT_ASSERT_TRUE (Result); Result = Pkcs7Encrypt ( X509Stack, (UINT8 *)mPkcs7EncryptPlaintext, sizeof (mPkcs7EncryptPlaintext), 0, CRYPTO_PKCS7_DEFAULT, &ContentInfo, &ContentInfoSize ); X509StackFree (X509Stack); UT_ASSERT_FALSE (Result); UT_ASSERT_TRUE (ContentInfo == NULL); UT_ASSERT_EQUAL (ContentInfoSize, 0); return UNIT_TEST_PASSED; } /** Any Flags value other than CRYPTO_PKCS7_DEFAULT must be rejected. **/ STATIC UNIT_TEST_STATUS EFIAPI TestPkcs7EncryptUnsupportedFlags ( IN UNIT_TEST_CONTEXT Context ) { UINT8 *X509Stack; UINT8 *ContentInfo; UINTN ContentInfoSize; BOOLEAN Result; X509Stack = NULL; ContentInfo = NULL; ContentInfoSize = 0; Result = X509ConstructCertificateStack ( &X509Stack, mPkcs7TestRecipient1Cert, (UINTN)sizeof (mPkcs7TestRecipient1Cert), NULL ); UT_ASSERT_TRUE (Result); Result = Pkcs7Encrypt ( X509Stack, (UINT8 *)mPkcs7EncryptPlaintext, sizeof (mPkcs7EncryptPlaintext), CRYPTO_NID_AES256CBC, 0xDEADBEEF, &ContentInfo, &ContentInfoSize ); X509StackFree (X509Stack); UT_ASSERT_FALSE (Result); UT_ASSERT_TRUE (ContentInfo == NULL); UT_ASSERT_EQUAL (ContentInfoSize, 0); return UNIT_TEST_PASSED; } // // Test descriptor table consumed by BaseCryptLibUnitTests and // BaseCryptLibUnitTestsMbedTls. // TEST_DESC mPkcs7EncryptTest[] = { // // -----Description--------------------------------------Class-----------------------Function-----------------------------------PreReq-Post-Context // { "TestPkcs7EncryptAes128Cbc()", "CryptoPkg.BaseCryptLib", TestPkcs7EncryptAes128Cbc, NULL, NULL, NULL }, { "TestPkcs7EncryptAes192Cbc()", "CryptoPkg.BaseCryptLib", TestPkcs7EncryptAes192Cbc, NULL, NULL, NULL }, { "TestPkcs7EncryptAes256Cbc()", "CryptoPkg.BaseCryptLib", TestPkcs7EncryptAes256Cbc, NULL, NULL, NULL }, { "TestPkcs7EncryptMultipleRecipients()", "CryptoPkg.BaseCryptLib", TestPkcs7EncryptMultipleRecipients, NULL, NULL, NULL }, { "TestPkcs7EncryptNullX509Stack()", "CryptoPkg.BaseCryptLib", TestPkcs7EncryptNullX509Stack, NULL, NULL, NULL }, { "TestPkcs7EncryptNullInData()", "CryptoPkg.BaseCryptLib", TestPkcs7EncryptNullInData, NULL, NULL, NULL }, { "TestPkcs7EncryptNullContentInfo()", "CryptoPkg.BaseCryptLib", TestPkcs7EncryptNullContentInfo, NULL, NULL, NULL }, { "TestPkcs7EncryptNullContentInfoSize()", "CryptoPkg.BaseCryptLib", TestPkcs7EncryptNullContentInfoSize, NULL, NULL, NULL }, { "TestPkcs7EncryptUnsupportedCipher()", "CryptoPkg.BaseCryptLib", TestPkcs7EncryptUnsupportedCipher, NULL, NULL, NULL }, { "TestPkcs7EncryptUnsupportedFlags()", "CryptoPkg.BaseCryptLib", TestPkcs7EncryptUnsupportedFlags, NULL, NULL, NULL }, }; UINTN mPkcs7EncryptTestNum = ARRAY_SIZE (mPkcs7EncryptTest);