/** @file -- Pkcs7EkuVerify.c
 * Copyright (c) Microsoft Corporation.
 * SPDX-License-Identifier: BSD-2-Clause-Patent

  This is an test code which verifies specified
  Enhanced Key Usages (EKU)'s are present in the leaf signer
  of a PKCS7 formatted signature.


  A typical signing certificate chain looks like this: (Could be RSA or ECC).

      ------------------------------------------
     |                                          | // Root of trust. ECDSA P521 curve
     |          TestEKUParsingRoot              | // SHA 256 Key Usage: CERT_DIGITAL_SIGNATURE_KEY_USAGE
     |                                          | // CERT_KEY_CERT_SIGN_KEY_USAGE | CERT_CRL_SIGN_KEY_USAGE
      ------------------------------------------
                        ^
                        |
      ------------------------------------------
     |                                          | // Policy CA.  Issues subordinate CAs. ECC P384 curve.
     |       TestEKUParsingPolicyCA             | // SHA 256 Key Usage:
     |                                          | // CERT_KEY_CERT_SIGN_KEY_USAGE | CERT_CRL_SIGN_KEY_USAGE
      ------------------------------------------
                        ^
                        |
      ------------------------------------------
     |                                          | // Issues end-entity (leaf) signers. ECC P256 curve.
     |        TestEKUParsingIssuingCA           | // SHA 256 Key Usage: CERT_DIGITAL_SIGNATURE_KEY_USAGE
     |                                          | // Enhanced Key Usage:
      ------------------------------------------  // 1.3.6.1.4.1.311.76.9.21.1 (Surface firmware signing)
                        ^
                        |
         --------------------------------------
        /     TestEKUParsingLeafSigner &&     /   // Leaf signer,  ECC P256 curve.
       /    TestEKUParsingLeafSignerPid12345 /    // SHA 256 Key Usage: CERT_DIGITAL_SIGNATURE_KEY_USAGE
      /                                     /     // Enhanced Key usages:
      --------------------------------------      // 1.3.6.1.4.1.311.76.9.21.1 (Surface firmware signing)
                                                  // 1.3.6.1.4.1.311.76.9.21.1.N, N == Product ID.





**/

#include "TestBaseCryptLib.h"

#include "Pkcs7EkuTestSignatures.h"

EFI_STATUS
EFIAPI
VerifyEKUsInPkcs7Signature (
  IN CONST UINT8   *Pkcs7Signature,
  IN CONST UINT32  SignatureSize,
  IN CONST CHAR8   *RequiredEKUs[],
  IN CONST UINT32  RequiredEKUsSize,
  IN BOOLEAN       RequireAllPresent
  );

/// ================================================================================================
/// ================================================================================================
///
/// TEST CASES
///
/// ================================================================================================
/// ================================================================================================

CONST CHAR8  FIRMWARE_SIGNER_EKU[] = "1.3.6.1.4.1.311.76.9.21.1";

/**
  TestVerifyEKUsInSignature()

  Verify that "1.3.6.1.4.1.311.76.9.21.1" (Firmware signature) is in the
  leaf signer certificate.


  @param[in]  Framework - Unit-test framework handle.
  @param[in]  Context   - Optional context pointer for this test.

  @retval UNIT_TEST_PASSED            - The required EKUs were found in the signature.
  @retval UNIT_TEST_ERROR_TEST_FAILED - Something failed, check the debug output.
**/
static
UNIT_TEST_STATUS
EFIAPI
TestVerifyEKUsInSignature (
  IN UNIT_TEST_CONTEXT  Context
  )
{
  EFI_STATUS  Status = EFI_SUCCESS;

  CONST CHAR8  *RequiredEKUs[] = { FIRMWARE_SIGNER_EKU };

  Status = VerifyEKUsInPkcs7Signature (
             ProductionECCSignature,
             ARRAY_SIZE (ProductionECCSignature),
             (CONST CHAR8 **)RequiredEKUs,
             ARRAY_SIZE (RequiredEKUs),
             TRUE
             );
  UT_ASSERT_STATUS_EQUAL (Status, EFI_SUCCESS);

  return UNIT_TEST_PASSED;
}// TestVerifyEKUsInSignature()

/**
  TestVerifyEKUsWith3CertsInSignature()

  This PKCS7 signature has 3 certificates in it. (Policy CA, Issuing CA
  and leaf signer). It has one firmware signing EKU in it.
  "1.3.6.1.4.1.311.76.9.21.1"

  @param[in]  Framework - Unit-test framework handle.
  @param[in]  Context   - Optional context pointer for this test.

  @retval UNIT_TEST_PASSED            - The required EKUs were found in the signature.
  @retval UNIT_TEST_ERROR_TEST_FAILED - Something failed, check the debug output.
**/
static
UNIT_TEST_STATUS
EFIAPI
TestVerifyEKUsWith3CertsInSignature (
  IN UNIT_TEST_CONTEXT  Context
  )
{
  EFI_STATUS  Status = EFI_SUCCESS;

  CONST CHAR8  *RequiredEKUs[] = { FIRMWARE_SIGNER_EKU };

  Status = VerifyEKUsInPkcs7Signature (
             TestSignEKUsWith3CertsInSignature,
             ARRAY_SIZE (TestSignEKUsWith3CertsInSignature),
             (CONST CHAR8 **)RequiredEKUs,
             ARRAY_SIZE (RequiredEKUs),
             TRUE
             );
  UT_ASSERT_STATUS_EQUAL (Status, EFI_SUCCESS);

  return UNIT_TEST_PASSED;
}// TestVerifyEKUsWith3CertsInSignature()

/**
  TestVerifyEKUsWith2CertsInSignature()

  This PKCS7 signature has 2 certificates in it. (Issuing CA and leaf signer).
  It has one firmware signing EKU in it. "1.3.6.1.4.1.311.76.9.21.1"

  @param[in]  Framework - Unit-test framework handle.
  @param[in]  Context   - Optional context pointer for this test.

  @retval UNIT_TEST_PASSED            - The required EKUs were found in the signature.
  @retval UNIT_TEST_ERROR_TEST_FAILED - Something failed, check the debug output.
**/
static
UNIT_TEST_STATUS
EFIAPI
TestVerifyEKUsWith2CertsInSignature (
  IN UNIT_TEST_CONTEXT  Context
  )
{
  EFI_STATUS  Status = EFI_SUCCESS;

  CONST CHAR8  *RequiredEKUs[] = { FIRMWARE_SIGNER_EKU };

  Status = VerifyEKUsInPkcs7Signature (
             TestSignEKUsWith2CertsInSignature,
             ARRAY_SIZE (TestSignEKUsWith2CertsInSignature),
             (CONST CHAR8 **)RequiredEKUs,
             ARRAY_SIZE (RequiredEKUs),
             TRUE
             );
  UT_ASSERT_STATUS_EQUAL (Status, EFI_SUCCESS);

  return UNIT_TEST_PASSED;
}// TestVerifyEKUsWith2CertsInSignature()

/**
  TestVerifyEKUsWith1CertInSignature()

  This PKCS7 signature only has the leaf signer in it.
  It has one firmware signing EKU in it. "1.3.6.1.4.1.311.76.9.21.1"

  @param[in]  Framework - Unit-test framework handle.
  @param[in]  Context   - Optional context pointer for this test.

  @retval UNIT_TEST_PASSED            - The required EKUs were found in the signature.
  @retval UNIT_TEST_ERROR_TEST_FAILED - Something failed, check the debug output.
**/
static
UNIT_TEST_STATUS
EFIAPI
TestVerifyEKUsWith1CertInSignature (
  IN UNIT_TEST_CONTEXT  Context
  )
{
  EFI_STATUS  Status = EFI_SUCCESS;

  CONST CHAR8  *RequiredEKUs[] = { FIRMWARE_SIGNER_EKU };

  Status = VerifyEKUsInPkcs7Signature (
             TestSignEKUsWith1CertInSignature,
             ARRAY_SIZE (TestSignEKUsWith1CertInSignature),
             (CONST CHAR8 **)RequiredEKUs,
             ARRAY_SIZE (RequiredEKUs),
             TRUE
             );
  UT_ASSERT_STATUS_EQUAL (Status, EFI_SUCCESS);

  return UNIT_TEST_PASSED;
}// TestVerifyEKUsWith1CertInSignature()

/**
  TestVerifyEKUsWithMultipleEKUsInCert()


  This signature has two EKU's in it:
  "1.3.6.1.4.1.311.76.9.21.1"
  "1.3.6.1.4.1.311.76.9.21.2"
  We verify that both EKU's were present in the leaf signer.

  @param[in]  Framework - Unit-test framework handle.
  @param[in]  Context   - Optional context pointer for this test.

  @retval UNIT_TEST_PASSED            - The required EKUs were found in the signature.
  @retval UNIT_TEST_ERROR_TEST_FAILED - Something failed, check the debug output.
**/
static
UNIT_TEST_STATUS
EFIAPI
TestVerifyEKUsWithMultipleEKUsInCert (
  IN UNIT_TEST_CONTEXT  Context
  )
{
  EFI_STATUS  Status = EFI_SUCCESS;

  CONST CHAR8  *RequiredEKUs[] = {
    "1.3.6.1.4.1.311.76.9.21.1",
    "1.3.6.1.4.1.311.76.9.21.1.2"
  };

  Status = VerifyEKUsInPkcs7Signature (
             TestSignedWithMultipleEKUsInCert,
             ARRAY_SIZE (TestSignedWithMultipleEKUsInCert),
             (CONST CHAR8 **)RequiredEKUs,
             ARRAY_SIZE (RequiredEKUs),
             TRUE
             );
  UT_ASSERT_STATUS_EQUAL (Status, EFI_SUCCESS);

  return UNIT_TEST_PASSED;
}// TestVerifyEKUsWithMultipleEKUsInCert()

/**
  TestEkusNotPresentInSignature()

  This test verifies that if we send an EKU that is not in the signature,
  that we get back an error.

  @param[in]  Framework - Unit-test framework handle.
  @param[in]  Context   - Optional context pointer for this test.

  @retval UNIT_TEST_PASSED            - The required EKUs were found in the signature.
  @retval UNIT_TEST_ERROR_TEST_FAILED - Something failed, check the debug output.
**/
static
UNIT_TEST_STATUS
EFIAPI
TestEkusNotPresentInSignature (
  IN UNIT_TEST_CONTEXT  Context
  )
{
  EFI_STATUS  Status = EFI_SUCCESS;

  //
  // This EKU is not in the signature.
  //
  CONST CHAR8  *RequiredEKUs[] = { "1.3.6.1.4.1.311.76.9.21.3" };

  Status = VerifyEKUsInPkcs7Signature (
             TestSignedWithMultipleEKUsInCert,
             ARRAY_SIZE (TestSignedWithMultipleEKUsInCert),
             (CONST CHAR8 **)RequiredEKUs,
             ARRAY_SIZE (RequiredEKUs),
             TRUE
             );
  UT_ASSERT_NOT_EQUAL (Status, EFI_SUCCESS);

  return UNIT_TEST_PASSED;
}// TestEkusNotPresentInSignature()

/**
  TestEkusNotPresentInSignature()

  This test signature has two EKU's in it:  (Product ID is 10001)
  "1.3.6.1.4.1.311.76.9.21.1"
  "1.3.6.1.4.1.311.76.9.21.1.10001"

  @param[in]  Framework - Unit-test framework handle.
  @param[in]  Context   - Optional context pointer for this test.

  @retval UNIT_TEST_PASSED            - The required EKUs were found in the signature.
  @retval UNIT_TEST_ERROR_TEST_FAILED - Something failed, check the debug output.
**/
static
UNIT_TEST_STATUS
EFIAPI
TestProductId10001PresentInSignature (
  IN UNIT_TEST_CONTEXT  Context
  )
{
  EFI_STATUS  Status = EFI_SUCCESS;

  //
  // These EKU's are present in the leaf signer certificate.
  //
  CONST CHAR8  *RequiredEKUs[] = {
    "1.3.6.1.4.1.311.76.9.21.1",
    "1.3.6.1.4.1.311.76.9.21.1.10001"
  };

  Status = VerifyEKUsInPkcs7Signature (
             TestSignedWithProductId10001,
             ARRAY_SIZE (TestSignedWithProductId10001),
             (CONST CHAR8 **)RequiredEKUs,
             ARRAY_SIZE (RequiredEKUs),
             TRUE
             );
  UT_ASSERT_STATUS_EQUAL (Status, EFI_SUCCESS);

  return UNIT_TEST_PASSED;
}// TestProductId10001PresentInSignature()

/**
  TestOnlyOneEkuInListRequired()

  This test will check the BOOLEAN RequireAllPresent parameter in the
  call to VerifyEKUsInPkcs7Signature() behaves properly.  The signature
  has two EKU's in it:

  "1.3.6.1.4.1.311.76.9.21.1"
  "1.3.6.1.4.1.311.76.9.21.1.10001"

  but we only pass in one of them, and set RequireAllPresent to FALSE.

  @param[in]  Framework - Unit-test framework handle.
  @param[in]  Context   - Optional context pointer for this test.

  @retval UNIT_TEST_PASSED            - The required EKUs were found in the signature.
  @retval UNIT_TEST_ERROR_TEST_FAILED - Something failed, check the debug output.
**/
static
UNIT_TEST_STATUS
EFIAPI
TestOnlyOneEkuInListRequired (
  IN UNIT_TEST_CONTEXT  Context
  )
{
  EFI_STATUS  Status = EFI_SUCCESS;

  //
  // This will test the flag that specifies it is OK to succeed if
  // any one of the EKU's passed in is found.
  //
  CONST CHAR8  *RequiredEKUs[] = { "1.3.6.1.4.1.311.76.9.21.1.10001" };

  Status = VerifyEKUsInPkcs7Signature (
             TestSignedWithProductId10001,
             ARRAY_SIZE (TestSignedWithProductId10001),
             (CONST CHAR8 **)RequiredEKUs,
             ARRAY_SIZE (RequiredEKUs),
             FALSE
             );
  UT_ASSERT_STATUS_EQUAL (Status, EFI_SUCCESS);

  return UNIT_TEST_PASSED;
}// TestOnlyOneEkuInListRequired()

/**
  TestNoEKUsInSignature()

  This test uses a signature that was signed with a certificate that does
  not contain any EKUs.


  @param[in]  Framework - Unit-test framework handle.
  @param[in]  Context   - Optional context pointer for this test.

  @retval UNIT_TEST_PASSED            - The required EKUs were found in the signature.
  @retval UNIT_TEST_ERROR_TEST_FAILED - Something failed, check the debug output.
**/
static
UNIT_TEST_STATUS
EFIAPI
TestNoEKUsInSignature (
  IN UNIT_TEST_CONTEXT  Context
  )
{
  EFI_STATUS  Status = EFI_SUCCESS;

  //
  // This EKU is not in the certificate, so it should fail.
  //
  CONST CHAR8  *RequiredEKUs[] = { "1.3.6.1.4.1.311.76.9.21.1" };

  Status = VerifyEKUsInPkcs7Signature (
             TestSignatureWithNoEKUsPresent,
             ARRAY_SIZE (TestSignatureWithNoEKUsPresent),
             (CONST CHAR8 **)RequiredEKUs,
             ARRAY_SIZE (RequiredEKUs),
             TRUE
             );
  UT_ASSERT_NOT_EQUAL (Status, EFI_SUCCESS);

  return UNIT_TEST_PASSED;
}// TestNoEKUsInSignature()

/**
  TestInvalidParameters()

  Passes the API invalid parameters, and ensures that it does not succeed.

  @param[in]  Framework - Unit-test framework handle.
  @param[in]  Context   - Optional context pointer for this test.

  @retval UNIT_TEST_PASSED            - The required EKUs were found in the signature.
  @retval UNIT_TEST_ERROR_TEST_FAILED - Something failed, check the debug output.
**/
static
UNIT_TEST_STATUS
EFIAPI
TestInvalidParameters (
  IN UNIT_TEST_CONTEXT  Context
  )
{
  EFI_STATUS  Status = EFI_SUCCESS;

  CONST CHAR8  *RequiredEKUs[] = { "1.3.6.1.4.1.311.76.9.21.1" };

  //
  // Check bad signature.
  //
  Status = VerifyEKUsInPkcs7Signature (
             NULL,
             0,
             (CONST CHAR8 **)RequiredEKUs,
             ARRAY_SIZE (RequiredEKUs),
             TRUE
             );
  UT_ASSERT_STATUS_EQUAL (Status, EFI_INVALID_PARAMETER);

  //
  // Check invalid EKU's
  //
  Status = VerifyEKUsInPkcs7Signature (
             TestSignatureWithNoEKUsPresent,
             ARRAY_SIZE (TestSignatureWithNoEKUsPresent),
             (CONST CHAR8 **)NULL,
             0,
             TRUE
             );
  UT_ASSERT_STATUS_EQUAL (Status, EFI_INVALID_PARAMETER);

  return UNIT_TEST_PASSED;
}// TestInvalidParameters()

/**
  TestEKUSubStringFails()

  Pass the API a sub set and super set of an EKU and ensure that they
  don't pass.

  @param[in]  Framework - Unit-test framework handle.
  @param[in]  Context   - Optional context pointer for this test.

  @retval UNIT_TEST_PASSED            - The required EKUs were found in the signature.
  @retval UNIT_TEST_ERROR_TEST_FAILED - Something failed, check the debug output.
**/
static
UNIT_TEST_STATUS
EFIAPI
TestEKUSubsetSupersetFails (
  IN UNIT_TEST_CONTEXT  Context
  )
{
  EFI_STATUS  Status = EFI_SUCCESS;

  //
  // This signature has an EKU of:
  // "1.3.6.1.4.1.311.76.9.21.1.10001"
  // so ensure that
  // "1.3.6.1.4.1.311.76.9.21"
  // does not pass.
  //
  CONST CHAR8  *RequiredEKUs1[] = { "1.3.6.1.4.1.311.76.9.21" };

  Status = VerifyEKUsInPkcs7Signature (
             TestSignedWithProductId10001,
             ARRAY_SIZE (TestSignedWithProductId10001),
             (CONST CHAR8 **)RequiredEKUs1,
             ARRAY_SIZE (RequiredEKUs1),
             TRUE
             );
  UT_ASSERT_NOT_EQUAL (Status, EFI_SUCCESS);

  //
  // This signature has an EKU of:
  // "1.3.6.1.4.1.311.76.9.21.1.10001"
  // so ensure that a super set
  // "1.3.6.1.4.1.311.76.9.21.1.10001.1"
  // does not pass.
  //
  CONST CHAR8  *RequiredEKUs2[] = { "1.3.6.1.4.1.311.76.9.21.1.10001.1" };

  Status = VerifyEKUsInPkcs7Signature (
             TestSignedWithProductId10001,
             ARRAY_SIZE (TestSignedWithProductId10001),
             (CONST CHAR8 **)RequiredEKUs2,
             ARRAY_SIZE (RequiredEKUs2),
             TRUE
             );
  UT_ASSERT_NOT_EQUAL (Status, EFI_SUCCESS);

  return UNIT_TEST_PASSED;
}// TestEKUSubsetSupersetFails()

TEST_DESC  mPkcs7EkuTest[] = {
  //
  // -----Description--------------------------------Class----------------------------Function------------------------------Pre---Post--Context
  //
  { "TestVerifyEKUsInSignature()",            "CryptoPkg.BaseCryptLib.Eku", TestVerifyEKUsInSignature,            NULL, NULL, NULL },
  { "TestVerifyEKUsWith3CertsInSignature()",  "CryptoPkg.BaseCryptLib.Eku", TestVerifyEKUsWith3CertsInSignature,  NULL, NULL, NULL },
  { "TestVerifyEKUsWith2CertsInSignature()",  "CryptoPkg.BaseCryptLib.Eku", TestVerifyEKUsWith2CertsInSignature,  NULL, NULL, NULL },
  { "TestVerifyEKUsWith1CertInSignature()",   "CryptoPkg.BaseCryptLib.Eku", TestVerifyEKUsWith1CertInSignature,   NULL, NULL, NULL },
  { "TestVerifyEKUsWithMultipleEKUsInCert()", "CryptoPkg.BaseCryptLib.Eku", TestVerifyEKUsWithMultipleEKUsInCert, NULL, NULL, NULL },
  { "TestEkusNotPresentInSignature()",        "CryptoPkg.BaseCryptLib.Eku", TestEkusNotPresentInSignature,        NULL, NULL, NULL },
  { "TestProductId10001PresentInSignature()", "CryptoPkg.BaseCryptLib.Eku", TestProductId10001PresentInSignature, NULL, NULL, NULL },
  { "TestOnlyOneEkuInListRequired()",         "CryptoPkg.BaseCryptLib.Eku", TestOnlyOneEkuInListRequired,         NULL, NULL, NULL },
  { "TestNoEKUsInSignature()",                "CryptoPkg.BaseCryptLib.Eku", TestNoEKUsInSignature,                NULL, NULL, NULL },
  { "TestInvalidParameters()",                "CryptoPkg.BaseCryptLib.Eku", TestInvalidParameters,                NULL, NULL, NULL },
  { "TestEKUSubsetSupersetFails()",           "CryptoPkg.BaseCryptLib.Eku", TestEKUSubsetSupersetFails,           NULL, NULL, NULL },
};

UINTN  mPkcs7EkuTestNum = ARRAY_SIZE (mPkcs7EkuTest);

TEST_DESC  mPkcs7EkuTestMbedTls[] = {
  //
  // -----Description--------------------------------Class----------------------------Function------------------------------Pre---Post--Context
  //
  { "TestVerifyEKUsWith1CertInSignature()",   "CryptoPkg.BaseCryptLib.Eku", TestVerifyEKUsWith1CertInSignature,   NULL, NULL, NULL },
  { "TestVerifyEKUsWithMultipleEKUsInCert()", "CryptoPkg.BaseCryptLib.Eku", TestVerifyEKUsWithMultipleEKUsInCert, NULL, NULL, NULL },
  { "TestEkusNotPresentInSignature()",        "CryptoPkg.BaseCryptLib.Eku", TestEkusNotPresentInSignature,        NULL, NULL, NULL },
  { "TestProductId10001PresentInSignature()", "CryptoPkg.BaseCryptLib.Eku", TestProductId10001PresentInSignature, NULL, NULL, NULL },
  { "TestOnlyOneEkuInListRequired()",         "CryptoPkg.BaseCryptLib.Eku", TestOnlyOneEkuInListRequired,         NULL, NULL, NULL },
  { "TestInvalidParameters()",                "CryptoPkg.BaseCryptLib.Eku", TestInvalidParameters,                NULL, NULL, NULL },
  { "TestEKUSubsetSupersetFails()",           "CryptoPkg.BaseCryptLib.Eku", TestEKUSubsetSupersetFails,           NULL, NULL, NULL },
};

UINTN  mPkcs7EkuTestMbedTlsNum = ARRAY_SIZE (mPkcs7EkuTestMbedTls);
