/** @file PKCS7 Decryption implementation over OpenSSL Copyright (c) 2026, Microsoft Corporation. All rights reserved. SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include "InternalCryptLib.h" #include #include #include #include #include /** Decrypts a DER-encoded PKCS#7 ContentInfo containing an envelopedData structure (such as one produced by Pkcs7Encrypt) and recovers the original content. The private key is supplied in PEM form so that this interface is not tied to any particular key algorithm (currently supports RSA). The correct recipient within the envelopedData is identified automatically. The envelopedData carries, for each recipient, an issuer name and serial number that identify the recipient certificate. If RecipientCert is supplied, its issuer name and serial number are used to select the matching recipient. If RecipientCert is NULL, the supplied private key is tried against every recipient in the envelope. If this interface is not supported, return FALSE. @param[in] PemData Pointer to the PEM-encoded private key of a recipient of the message. May be an RSA or other supported key type. @param[in] PemSize Size of the PEM key data in bytes. @param[in] Password [Optional] NULL-terminated passphrase used to decrypt an encrypted PEM key. Pass NULL if the PEM key is not password protected. @param[in] RecipientCert [Optional] Pointer to the DER-encoded X.509 certificate of the recipient whose private key is supplied in PemData. If provided, it is used to select the matching recipient in the envelope by issuer name and serial number. Pass NULL to try the private key against every recipient. @param[in] RecipientCertSize Size of the DER-encoded certificate in bytes. Ignored (and may be 0) when RecipientCert is NULL. @param[in] ContentInfo Pointer to the PKCS#7 DER-encoded ContentInfo that wraps an envelopedData to be decrypted. @param[in] ContentInfoSize Size of the ContentInfo in bytes. @param[in] Flags Flags for the decryption operation. Currently only CRYPTO_PKCS7_DEFAULT is supported, which indicates that the decrypted content is treated as binary data. @param[out] OutData Receives a pointer to the newly allocated buffer containing the decrypted content. The caller must free the returned buffer with FreePool(). @param[out] OutDataSize Receives the size of the decrypted content in bytes. @retval TRUE PKCS#7 data decryption succeeded. @retval FALSE PKCS#7 data decryption failed. @retval FALSE This interface is not supported. **/ BOOLEAN EFIAPI Pkcs7Decrypt ( IN CONST UINT8 *PemData, IN UINTN PemSize, IN CONST CHAR8 *Password OPTIONAL, IN CONST UINT8 *RecipientCert OPTIONAL, IN UINTN RecipientCertSize OPTIONAL, IN CONST UINT8 *ContentInfo, IN UINTN ContentInfoSize, IN UINT32 Flags, OUT UINT8 **OutData, OUT UINTN *OutDataSize ) { BOOLEAN Succeeded; UINT8 *ReturnData; UINTN ReturnSize; VOID *Rsa; BIO *OutBio; EVP_PKEY *Key; X509 *Cert; PKCS7 *Pkcs7; CONST UINT8 *DerCursor; long OutLength; char *OutBuffer; ReturnData = NULL; ReturnSize = 0; Rsa = NULL; OutBio = NULL; Key = NULL; Cert = NULL; Pkcs7 = NULL; if ((PemData == NULL) || (PemSize == 0) || (PemSize > INT_MAX) || (ContentInfo == NULL) || (ContentInfoSize == 0) || (ContentInfoSize > INT_MAX) || (Flags != CRYPTO_PKCS7_DEFAULT) || (OutData == NULL) || (OutDataSize == NULL)) { Succeeded = FALSE; // Invalid argument. goto Done; } if ((RecipientCert != NULL) && ((RecipientCertSize == 0) || (RecipientCertSize > INT_MAX))) { Succeeded = FALSE; // Invalid certificate argument. goto Done; } // // Retrieve the recipient's private key from the PEM data and wrap it in an // EVP_PKEY for PKCS7_decrypt. // if (!RsaGetPrivateKeyFromPem (PemData, PemSize, Password, &Rsa)) { Succeeded = FALSE; // Invalid PEM key data or incorrect password. goto Done; } Key = EVP_PKEY_new (); if (Key == NULL) { Succeeded = FALSE; // Memory allocation failed. goto Done; } if (EVP_PKEY_assign_RSA (Key, (RSA *)Rsa) == 0) { Succeeded = FALSE; // Key conversion failed. goto Done; } // // Key now owns Rsa and frees it via EVP_PKEY_free. // Rsa = NULL; // // Optionally parse the recipient certificate. When supplied, OpenSSL uses its // issuer name and serial number to select the matching recipient in the envelope. // if (RecipientCert != NULL) { DerCursor = RecipientCert; Cert = d2i_X509 (NULL, &DerCursor, (long)RecipientCertSize); if (Cert == NULL) { Succeeded = FALSE; // Invalid certificate. goto Done; } } // // Parse the DER-encoded PKCS#7 ContentInfo. // DerCursor = ContentInfo; Pkcs7 = d2i_PKCS7 (NULL, &DerCursor, (long)ContentInfoSize); if (Pkcs7 == NULL) { Succeeded = FALSE; // Invalid PKCS#7 data. goto Done; } if (!PKCS7_type_is_enveloped (Pkcs7)) { Succeeded = FALSE; // Not an envelopedData structure. goto Done; } // // Decrypt into a memory BIO. // OutBio = BIO_new (BIO_s_mem ()); if (OutBio == NULL) { Succeeded = FALSE; // BIO creation failed. goto Done; } if (PKCS7_decrypt (Pkcs7, Key, Cert, OutBio, PKCS7_BINARY) != 1) { Succeeded = FALSE; // Decryption failed (wrong key, no matching recipient, etc.). goto Done; } OutLength = BIO_get_mem_data (OutBio, &OutBuffer); if ((OutLength < 0) || ((UINTN)OutLength > INT_MAX)) { Succeeded = FALSE; // Unexpected decrypted length. goto Done; } ReturnSize = (UINTN)OutLength; // // Always allocate at least one byte so that a successful decryption of empty // content still yields a non-NULL, freeable buffer. // ReturnData = (UINT8 *)AllocateZeroPool (ReturnSize == 0 ? 1 : ReturnSize); if (ReturnData == NULL) { ReturnSize = 0; Succeeded = FALSE; // Memory allocation failed. goto Done; } if (ReturnSize != 0) { CopyMem (ReturnData, OutBuffer, ReturnSize); } Succeeded = TRUE; // Success, result in ReturnData/ReturnSize. Done: if (Pkcs7 != NULL) { PKCS7_free (Pkcs7); Pkcs7 = NULL; } if (Cert != NULL) { X509_free (Cert); Cert = NULL; } if (Key != NULL) { EVP_PKEY_free (Key); Key = NULL; } if (Rsa != NULL) { RsaFree (Rsa); Rsa = NULL; } if (OutBio != NULL) { BIO_free (OutBio); OutBio = NULL; } if (OutData != NULL) { *OutData = ReturnData; } if (OutDataSize != NULL) { *OutDataSize = ReturnSize; } return Succeeded; }