* Update BaseCryptLib internal worker functions to be 'STATIC' * Update BaseCryptLib internal working functions to not use EFIAPI * Add GLOBAL_REMOVE_IF_UNREFERENCED to BaseCryptLib global variables Cc: Jiewen Yao <jiewen.yao@intel.com> Cc: Jian J Wang <jian.j.wang@intel.com> Cc: Xiaoyu Lu <xiaoyu1.lu@intel.com> Cc: Guomin Jiang <guomin.jiang@intel.com> Cc: Christopher Zurcher <christopher.zurcher@microsoft.com> Signed-off-by: Michael D Kinney <michael.d.kinney@intel.com> Reviewed-by: Jiewen Yao <jiewen.yao@intel.com>
		
			
				
	
	
		
			527 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			527 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/** @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;
 | 
						|
}
 |