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