--- /dev/null
+/** @file\r
+ This module verifies that Enhanced Key Usages (EKU's) are present within\r
+ a PKCS7 signature blob using OpenSSL.\r
+\r
+ Copyright (C) Microsoft Corporation. All Rights Reserved.\r
+ Copyright (c) 2019, Intel Corporation. All rights reserved.<BR>\r
+\r
+ SPDX-License-Identifier: BSD-2-Clause-Patent\r
+\r
+**/\r
+\r
+#include <Base.h>\r
+#include "InternalCryptLib.h"\r
+#include <openssl/x509v3.h>\r
+#include <openssl/asn1.h>\r
+#include <openssl/x509.h>\r
+#include <openssl/bio.h>\r
+#include <internal/x509_int.h>\r
+#include <openssl/pkcs7.h>\r
+#include <openssl/bn.h>\r
+#include <openssl/x509_vfy.h>\r
+#include <openssl/pem.h>\r
+#include <openssl/evp.h>\r
+#include <internal/asn1_int.h>\r
+\r
+/**\r
+ This function will return the leaf signer certificate in a chain. This is\r
+ required because certificate chains are not guaranteed to have the\r
+ certificates in the order that they were issued.\r
+\r
+ A typical certificate chain looks like this:\r
+\r
+\r
+ ----------------------------\r
+ | Root |\r
+ ----------------------------\r
+ ^\r
+ |\r
+ ----------------------------\r
+ | Policy CA | <-- Typical Trust Anchor.\r
+ ----------------------------\r
+ ^\r
+ |\r
+ ----------------------------\r
+ | Issuing CA |\r
+ ----------------------------\r
+ ^\r
+ |\r
+ -----------------------------\r
+ / End-Entity (leaf) signer / <-- Bottom certificate.\r
+ ----------------------------- EKU: "1.3.6.1.4.1.311.76.9.21.1"\r
+ (Firmware Signing)\r
+\r
+\r
+ @param[in] CertChain Certificate chain.\r
+\r
+ @param[out] SignerCert Last certificate in the chain. For PKCS7 signatures,\r
+ this will be the end-entity (leaf) signer cert.\r
+\r
+ @retval EFI_SUCCESS The required EKUs were found in the signature.\r
+ @retval EFI_INVALID_PARAMETER A parameter was invalid.\r
+ @retval EFI_NOT_FOUND The number of signers found was not 1.\r
+\r
+**/\r
+EFI_STATUS\r
+GetSignerCertificate (\r
+ IN CONST PKCS7 *CertChain,\r
+ OUT X509 **SignerCert\r
+ )\r
+{\r
+ EFI_STATUS Status;\r
+ STACK_OF(X509) *Signers;\r
+ INT32 NumberSigners;\r
+\r
+ Status = EFI_SUCCESS;\r
+ Signers = NULL;\r
+ NumberSigners = 0;\r
+\r
+ if (CertChain == NULL || SignerCert == NULL) {\r
+ Status = EFI_INVALID_PARAMETER;\r
+ goto Exit;\r
+ }\r
+\r
+ //\r
+ // Get the signers from the chain.\r
+ //\r
+ Signers = PKCS7_get0_signers ((PKCS7*) CertChain, NULL, PKCS7_BINARY);\r
+ if (Signers == NULL) {\r
+ //\r
+ // Fail to get signers form PKCS7\r
+ //\r
+ Status = EFI_INVALID_PARAMETER;\r
+ goto Exit;\r
+ }\r
+\r
+ //\r
+ // There should only be one signer in the PKCS7 stack.\r
+ //\r
+ NumberSigners = sk_X509_num (Signers);\r
+ if (NumberSigners != 1) {\r
+ //\r
+ // The number of singers should have been 1\r
+ //\r
+ Status = EFI_NOT_FOUND;\r
+ goto Exit;\r
+ }\r
+\r
+ *SignerCert = sk_X509_value (Signers, 0);\r
+\r
+Exit:\r
+ //\r
+ // Release Resources\r
+ //\r
+ if (Signers) {\r
+ sk_X509_free (Signers);\r
+ }\r
+\r
+ return Status;\r
+}\r
+\r
+\r
+/**\r
+ Determines if the specified EKU represented in ASN1 form is present\r
+ in a given certificate.\r
+\r
+ @param[in] Cert The certificate to check.\r
+\r
+ @param[in] Asn1ToFind The EKU to look for.\r
+\r
+ @retval EFI_SUCCESS We successfully identified the signing type.\r
+ @retval EFI_INVALID_PARAMETER A parameter was invalid.\r
+ @retval EFI_NOT_FOUND One or more EKU's were not found in the signature.\r
+\r
+**/\r
+EFI_STATUS\r
+IsEkuInCertificate (\r
+ IN CONST X509 *Cert,\r
+ IN ASN1_OBJECT *Asn1ToFind\r
+ )\r
+{\r
+ EFI_STATUS Status;\r
+ X509 *ClonedCert;\r
+ X509_EXTENSION *Extension;\r
+ EXTENDED_KEY_USAGE *Eku;\r
+ INT32 ExtensionIndex;\r
+ INTN NumExtensions;\r
+ ASN1_OBJECT *Asn1InCert;\r
+ INTN Index;\r
+\r
+ Status = EFI_NOT_FOUND;\r
+ ClonedCert = NULL;\r
+ Extension = NULL;\r
+ Eku = NULL;\r
+ ExtensionIndex = -1;\r
+ NumExtensions = 0;\r
+ Asn1InCert = NULL;\r
+\r
+ if (Cert == NULL || Asn1ToFind == NULL) {\r
+ Status = EFI_INVALID_PARAMETER;\r
+ goto Exit;\r
+ }\r
+\r
+ //\r
+ // Clone the certificate. This is required because the Extension API's\r
+ // only work once per instance of an X509 object.\r
+ //\r
+ ClonedCert = X509_dup ((X509*)Cert);\r
+ if (ClonedCert == NULL) {\r
+ //\r
+ // Fail to duplicate cert.\r
+ //\r
+ Status = EFI_INVALID_PARAMETER;\r
+ goto Exit;\r
+ }\r
+\r
+ //\r
+ // Look for the extended key usage.\r
+ //\r
+ ExtensionIndex = X509_get_ext_by_NID (ClonedCert, NID_ext_key_usage, -1);\r
+\r
+ if (ExtensionIndex < 0) {\r
+ //\r
+ // Fail to find 'NID_ext_key_usage' in Cert.\r
+ //\r
+ goto Exit;\r
+ }\r
+\r
+ Extension = X509_get_ext (ClonedCert, ExtensionIndex);\r
+ if (Extension == NULL) {\r
+ //\r
+ // Fail to get Extension form cert.\r
+ //\r
+ goto Exit;\r
+ }\r
+\r
+ Eku = (EXTENDED_KEY_USAGE*)X509V3_EXT_d2i (Extension);\r
+ if (Eku == NULL) {\r
+ //\r
+ // Fail to get Eku from extension.\r
+ //\r
+ goto Exit;\r
+ }\r
+\r
+ NumExtensions = sk_ASN1_OBJECT_num (Eku);\r
+\r
+ //\r
+ // Now loop through the extensions, looking for the specified Eku.\r
+ //\r
+ for (Index = 0; Index < NumExtensions; Index++) {\r
+ Asn1InCert = sk_ASN1_OBJECT_value (Eku, (INT32)Index);\r
+ if (Asn1InCert == NULL) {\r
+ //\r
+ // Fail to get ASN object from Eku.\r
+ //\r
+ goto Exit;\r
+ }\r
+\r
+ if (Asn1InCert->length == Asn1ToFind->length &&\r
+ CompareMem (Asn1InCert->data, Asn1ToFind->data, Asn1InCert->length) == 0) {\r
+ //\r
+ // Found Eku in certificate.\r
+ //\r
+ Status = EFI_SUCCESS;\r
+ goto Exit;\r
+ }\r
+ }\r
+\r
+Exit:\r
+\r
+ //\r
+ // Release Resources\r
+ //\r
+ if (ClonedCert) {\r
+ X509_free (ClonedCert);\r
+ }\r
+\r
+ if (Eku) {\r
+ sk_ASN1_OBJECT_pop_free (Eku, ASN1_OBJECT_free);\r
+ }\r
+\r
+ return Status;\r
+}\r
+\r
+\r
+/**\r
+ Determines if the specified EKUs are present in a signing certificate.\r
+\r
+ @param[in] SignerCert The certificate to check.\r
+ @param[in] RequiredEKUs The EKUs to look for.\r
+ @param[in] RequiredEKUsSize The number of EKUs\r
+ @param[in] RequireAllPresent If TRUE, then all the specified EKUs\r
+ must be present in the certificate.\r
+\r
+ @retval EFI_SUCCESS We successfully identified the signing type.\r
+ @retval EFI_INVALID_PARAMETER A parameter was invalid.\r
+ @retval EFI_NOT_FOUND One or more EKU's were not found in the signature.\r
+**/\r
+EFI_STATUS\r
+CheckEKUs(\r
+ IN CONST X509 *SignerCert,\r
+ IN CONST CHAR8 *RequiredEKUs[],\r
+ IN CONST UINT32 RequiredEKUsSize,\r
+ IN BOOLEAN RequireAllPresent\r
+ )\r
+{\r
+ EFI_STATUS Status;\r
+ ASN1_OBJECT *Asn1ToFind;\r
+ UINT32 NumEkusFound;\r
+ UINT32 Index;\r
+\r
+ Status = EFI_SUCCESS;\r
+ Asn1ToFind = NULL;\r
+ NumEkusFound = 0;\r
+\r
+ if (SignerCert == NULL || RequiredEKUs == NULL || RequiredEKUsSize == 0) {\r
+ Status = EFI_INVALID_PARAMETER;\r
+ goto Exit;\r
+ }\r
+\r
+ for (Index = 0; Index < RequiredEKUsSize; Index++) {\r
+ //\r
+ // Finding required EKU in cert.\r
+ //\r
+ if (Asn1ToFind) {\r
+ ASN1_OBJECT_free(Asn1ToFind);\r
+ Asn1ToFind = NULL;\r
+ }\r
+\r
+ Asn1ToFind = OBJ_txt2obj (RequiredEKUs[Index], 0);\r
+ if (!Asn1ToFind) {\r
+ //\r
+ // Fail to convert required EKU to ASN1.\r
+ //\r
+ Status = EFI_INVALID_PARAMETER;\r
+ goto Exit;\r
+ }\r
+\r
+ Status = IsEkuInCertificate (SignerCert, Asn1ToFind);\r
+ if (Status == EFI_SUCCESS) {\r
+ NumEkusFound++;\r
+ if (!RequireAllPresent) {\r
+ //\r
+ // Found at least one, so we are done.\r
+ //\r
+ goto Exit;\r
+ }\r
+ } else {\r
+ //\r
+ // Fail to find Eku in cert\r
+ break;\r
+ }\r
+ }\r
+\r
+Exit:\r
+\r
+ if (Asn1ToFind) {\r
+ ASN1_OBJECT_free(Asn1ToFind);\r
+ }\r
+\r
+ if (RequireAllPresent &&\r
+ NumEkusFound == RequiredEKUsSize) {\r
+ //\r
+ // Found all required EKUs in certificate.\r
+ //\r
+ Status = EFI_SUCCESS;\r
+ }\r
+\r
+ return Status;\r
+}\r
+\r
+/**\r
+ This function receives a PKCS#7 formatted signature blob,\r
+ looks for the EKU SEQUENCE blob, and if found then looks\r
+ for all the required EKUs. This function was created so that\r
+ the Surface team can cut down on the number of Certificate\r
+ Authorities (CA's) by checking EKU's on leaf signers for\r
+ a specific product. This prevents one product's certificate\r
+ from signing another product's firmware or unlock blobs.\r
+\r
+ Note that this function does not validate the certificate chain.\r
+ That needs to be done before using this function.\r
+\r
+ @param[in] Pkcs7Signature The PKCS#7 signed information content block. An array\r
+ containing the content block with both the signature,\r
+ the signer's certificate, and any necessary intermediate\r
+ certificates.\r
+ @param[in] Pkcs7SignatureSize Number of bytes in Pkcs7Signature.\r
+ @param[in] RequiredEKUs Array of null-terminated strings listing OIDs of\r
+ required EKUs that must be present in the signature.\r
+ @param[in] RequiredEKUsSize Number of elements in the RequiredEKUs string array.\r
+ @param[in] RequireAllPresent If this is TRUE, then all of the specified EKU's\r
+ must be present in the leaf signer. If it is\r
+ FALSE, then we will succeed if we find any\r
+ of the specified EKU's.\r
+\r
+ @retval EFI_SUCCESS The required EKUs were found in the signature.\r
+ @retval EFI_INVALID_PARAMETER A parameter was invalid.\r
+ @retval EFI_NOT_FOUND One or more EKU's were not found in the signature.\r
+\r
+**/\r
+EFI_STATUS\r
+EFIAPI\r
+VerifyEKUsInPkcs7Signature (\r
+ IN CONST UINT8 *Pkcs7Signature,\r
+ IN CONST UINT32 SignatureSize,\r
+ IN CONST CHAR8 *RequiredEKUs[],\r
+ IN CONST UINT32 RequiredEKUsSize,\r
+ IN BOOLEAN RequireAllPresent\r
+ )\r
+{\r
+ EFI_STATUS Status;\r
+ PKCS7 *Pkcs7;\r
+ STACK_OF(X509) *CertChain;\r
+ INT32 SignatureType;\r
+ INT32 NumberCertsInSignature;\r
+ X509 *SignerCert;\r
+ UINT8 *SignedData;\r
+ UINT8 *Temp;\r
+ UINTN SignedDataSize;\r
+ BOOLEAN IsWrapped;\r
+ BOOLEAN Ok;\r
+\r
+ Status = EFI_SUCCESS;\r
+ Pkcs7 = NULL;\r
+ CertChain = NULL;\r
+ SignatureType = 0;\r
+ NumberCertsInSignature = 0;\r
+ SignerCert = NULL;\r
+ SignedData = NULL;\r
+ SignedDataSize = 0;\r
+ IsWrapped = FALSE;\r
+ Ok = FALSE;\r
+\r
+ //\r
+ //Validate the input parameters.\r
+ //\r
+ if (Pkcs7Signature == NULL ||\r
+ SignatureSize == 0 ||\r
+ RequiredEKUs == NULL ||\r
+ RequiredEKUsSize == 0) {\r
+ Status = EFI_INVALID_PARAMETER;\r
+ goto Exit;\r
+ }\r
+\r
+ if (RequiredEKUsSize == 1) {\r
+ RequireAllPresent = TRUE;\r
+ }\r
+\r
+ //\r
+ // Wrap the PKCS7 data if needed.\r
+ //\r
+ Ok = WrapPkcs7Data (Pkcs7Signature,\r
+ SignatureSize,\r
+ &IsWrapped,\r
+ &SignedData,\r
+ &SignedDataSize);\r
+ if (!Ok) {\r
+ //\r
+ // Fail to Wrap the PKCS7 data.\r
+ //\r
+ Status = EFI_INVALID_PARAMETER;\r
+ goto Exit;\r
+ }\r
+\r
+ Temp = SignedData;\r
+\r
+ //\r
+ // Create the PKCS7 object.\r
+ //\r
+ Pkcs7 = d2i_PKCS7 (NULL, (const unsigned char **)&Temp, (INT32)SignedDataSize);\r
+ if (Pkcs7 == NULL) {\r
+ //\r
+ // Fail to read PKCS7 data.\r
+ //\r
+ Status = EFI_INVALID_PARAMETER;\r
+ goto Exit;\r
+ }\r
+\r
+ //\r
+ // Get the certificate chain.\r
+ //\r
+ SignatureType = OBJ_obj2nid (Pkcs7->type);\r
+ switch (SignatureType) {\r
+ case NID_pkcs7_signed:\r
+ if (Pkcs7->d.sign != NULL) {\r
+ CertChain = Pkcs7->d.sign->cert;\r
+ }\r
+ break;\r
+ case NID_pkcs7_signedAndEnveloped:\r
+ if (Pkcs7->d.signed_and_enveloped != NULL) {\r
+ CertChain = Pkcs7->d.signed_and_enveloped->cert;\r
+ }\r
+ break;\r
+ default:\r
+ break;\r
+ }\r
+\r
+ //\r
+ // Ensure we have a certificate stack\r
+ //\r
+ if (CertChain == NULL) {\r
+ //\r
+ // Fail to get the certificate stack from signature.\r
+ //\r
+ Status = EFI_INVALID_PARAMETER;\r
+ goto Exit;\r
+ }\r
+\r
+ //\r
+ // Find out how many certificates were in the PKCS7 signature.\r
+ //\r
+ NumberCertsInSignature = sk_X509_num (CertChain);\r
+\r
+ if (NumberCertsInSignature == 0) {\r
+ //\r
+ // Fail to find any certificates in signature.\r
+ //\r
+ Status = EFI_INVALID_PARAMETER;\r
+ goto Exit;\r
+ }\r
+\r
+ //\r
+ // Get the leaf signer.\r
+ //\r
+ Status = GetSignerCertificate (Pkcs7, &SignerCert);\r
+ if (Status != EFI_SUCCESS || SignerCert == NULL) {\r
+ //\r
+ // Fail to get the end-entity leaf signer certificate.\r
+ //\r
+ Status = EFI_INVALID_PARAMETER;\r
+ goto Exit;\r
+ }\r
+\r
+ Status = CheckEKUs (SignerCert, RequiredEKUs, RequiredEKUsSize, RequireAllPresent);\r
+ if (Status != EFI_SUCCESS) {\r
+ goto Exit;\r
+ }\r
+\r
+Exit:\r
+\r
+ //\r
+ // Release Resources\r
+ //\r
+ // If the signature was not wrapped, then the call to WrapData() will allocate\r
+ // the data and add a header to it\r
+ //\r
+ if (!IsWrapped && SignedData) {\r
+ free (SignedData);\r
+ }\r
+\r
+ if (SignerCert) {\r
+ X509_free (SignerCert);\r
+ }\r
+\r
+ if (Pkcs7) {\r
+ PKCS7_free (Pkcs7);\r
+ }\r
+\r
+ return Status;\r
+}\r
+\r