File shim-bsc1177315-verify-eku-codesign.patch of Package shim.32617

From 6ff890bf0af9d37acc6ea8ad64f597060e8bb143 Mon Sep 17 00:00:00 2001
From: Gary Lin <glin@suse.com>
Date: Wed, 14 Oct 2020 14:31:12 +0800
Subject: [PATCH] Enforce EKU CodeSign extension check

Per NIAP OS_PP, the signer certificate of the UEFI image has to contain
"CodeSign" extension in its Extended Key Usage(EKU).

This commit borrows VerifyEKUsInPkcs7Signature() from edk2 and enforces
the CodeSign check in Pkcs7Verify().
+ Also merged the buffer use-after-free fix (*)

(*) https://bugzilla.tianocore.org/show_bug.cgi?id=2459

Signed-off-by: Gary Lin <glin@suse.com>
---
 Cryptlib/InternalCryptLib.h       |  32 ++
 Cryptlib/Library/BaseCryptLib.h   |  40 +++
 Cryptlib/Makefile                 |   1 +
 Cryptlib/Pk/CryptPkcs7Verify.c    |  10 +
 Cryptlib/Pk/CryptPkcs7VerifyEku.c | 516 ++++++++++++++++++++++++++++++
 5 files changed, 599 insertions(+)
 create mode 100644 Cryptlib/Pk/CryptPkcs7VerifyEku.c

diff --git a/Cryptlib/InternalCryptLib.h b/Cryptlib/InternalCryptLib.h
index e9a4c20..8c9a2a4 100644
--- a/Cryptlib/InternalCryptLib.h
+++ b/Cryptlib/InternalCryptLib.h
@@ -30,5 +30,37 @@ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
 #define OBJ_length(o) ((o)->length)
 #endif
 
+/**
+  Check input P7Data is a wrapped ContentInfo structure or not. If not construct
+  a new structure to wrap P7Data.
+
+  Caution: This function may receive untrusted input.
+  UEFI Authenticated Variable is external input, so this function will do basic
+  check for PKCS#7 data structure.
+
+  @param[in]  P7Data       Pointer to the PKCS#7 message to verify.
+  @param[in]  P7Length     Length of the PKCS#7 message in bytes.
+  @param[out] WrapFlag     If TRUE P7Data is a ContentInfo structure, otherwise
+                           return FALSE.
+  @param[out] WrapData     If return status of this function is TRUE:
+                           1) when WrapFlag is TRUE, pointer to P7Data.
+                           2) when WrapFlag is FALSE, pointer to a new ContentInfo
+                           structure. It's caller's responsibility to free this
+                           buffer.
+  @param[out] WrapDataSize Length of ContentInfo structure in bytes.
+
+  @retval     TRUE         The operation is finished successfully.
+  @retval     FALSE        The operation is failed due to lack of resources.
+
+**/
+BOOLEAN
+WrapPkcs7Data (
+  IN  CONST UINT8  *P7Data,
+  IN  UINTN        P7Length,
+  OUT BOOLEAN      *WrapFlag,
+  OUT UINT8        **WrapData,
+  OUT UINTN        *WrapDataSize
+  );
+
 #endif
 
diff --git a/Cryptlib/Library/BaseCryptLib.h b/Cryptlib/Library/BaseCryptLib.h
index 2df8bd2..ed482d3 100644
--- a/Cryptlib/Library/BaseCryptLib.h
+++ b/Cryptlib/Library/BaseCryptLib.h
@@ -2403,6 +2403,46 @@ Pkcs7Verify (
   IN  UINTN        DataLength
   );
 
+/**
+  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
+  );
+
 /**
   Extracts the attached content from a PKCS#7 signed data if existed. The input signed
   data could be wrapped in a ContentInfo structure.
diff --git a/Cryptlib/Makefile b/Cryptlib/Makefile
index 18a33b1..a1d8b02 100644
--- a/Cryptlib/Makefile
+++ b/Cryptlib/Makefile
@@ -41,6 +41,7 @@ OBJS		=   Hash/CryptMd4Null.o \
 		    Pk/CryptRsaExtNull.o \
 		    Pk/CryptPkcs7SignNull.o \
 		    Pk/CryptPkcs7Verify.o \
+		    Pk/CryptPkcs7VerifyEku.o \
 		    Pk/CryptDhNull.o \
 		    Pk/CryptTs.o \
 		    Pk/CryptX509.o \
diff --git a/Cryptlib/Pk/CryptPkcs7Verify.c b/Cryptlib/Pk/CryptPkcs7Verify.c
index 09895d8..da15be2 100644
--- a/Cryptlib/Pk/CryptPkcs7Verify.c
+++ b/Cryptlib/Pk/CryptPkcs7Verify.c
@@ -29,6 +29,8 @@ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
 #include <openssl/pkcs7.h>
 
 UINT8 mOidValue[9] = { 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x02 };
+/* EKU CodeSign */
+CHAR8 mOidCodeSign[] = "1.3.6.1.5.5.7.3.3";
 
 #if 1
 #if OPENSSL_VERSION_NUMBER < 0x10100000L
@@ -846,6 +848,8 @@ Pkcs7Verify (
   CONST UINT8 *Temp;
   UINTN       SignedDataSize;
   BOOLEAN     Wrapped;
+  CONST CHAR8 *Ekus[1];
+  EFI_STATUS  EFI_Status;
 
   //
   // Check input parameters.
@@ -859,6 +863,7 @@ Pkcs7Verify (
   DataBio   = NULL;
   Cert      = NULL;
   CertStore = NULL;
+  Ekus[0]   = mOidCodeSign;
 
   //
   // Register & Initialize necessary digest algorithms for PKCS#7 Handling
@@ -958,6 +963,11 @@ Pkcs7Verify (
   //
   X509_STORE_set_purpose (CertStore, X509_PURPOSE_ANY);
 
+  EFI_Status = VerifyEKUsInPkcs7Signature(P7Data, P7Length, Ekus, 1, TRUE);
+  if (EFI_Status != EFI_SUCCESS) {
+	  goto _Exit;
+  }
+
   //
   // Verifies the PKCS#7 signedData structure
   //
diff --git a/Cryptlib/Pk/CryptPkcs7VerifyEku.c b/Cryptlib/Pk/CryptPkcs7VerifyEku.c
new file mode 100644
index 0000000..2c172e2
--- /dev/null
+++ b/Cryptlib/Pk/CryptPkcs7VerifyEku.c
@@ -0,0 +1,516 @@
+/** @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 <openssl/x509.h>
+#include <openssl/pkcs7.h>
+#include <openssl/bn.h>
+#include <openssl/x509_vfy.h>
+#include <openssl/pem.h>
+#include <openssl/evp.h>
+#include <openssl/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.
+
+**/
+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.
+
+**/
+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 (OBJ_cmp(Asn1InCert, Asn1ToFind) == 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.
+**/
+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_NOT_FOUND;
+  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;
+}
+
-- 
2.29.2

openSUSE Build Service is sponsored by