Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DXE_DRIVER Example: Replace Test Platform Key with a new Platform Key #247

Draft
wants to merge 1 commit into
base: dev/202405
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 180 additions & 0 deletions OemPkg/OverridePlatformKey/GenerateTargetList.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
from edk2toollib.utility_functions import export_c_type_array

from edk2toollib.uefi.authenticated_variables_structure_support import (
EfiSignatureDataFactory,
EfiSignatureList,
)

from tempfile import TemporaryFile
from typing import Union

import uuid
import base64
import hashlib

possible_targets = [
"Certs/PlatformKey.der"
]

replacement_cert = ("Certs/WindowsOEMDevicesPK.der",
"77fa9abd-0359-4d32-bd60-28f4e78f784b")


def _is_pem_encoded(certificate_data: Union[str, bytes]) -> bool:
"""This function is used to check if a certificate is pem encoded (base64 encoded).

Args:
certificate_data (str | bytes): The certificate to check.

Returns:
bool: True if the certificate is pem encoded, False otherwise.
"""
try:
if isinstance(certificate_data, str):
# If there's any unicode here, an exception will be thrown and the function will return false
sb_bytes = bytes(certificate_data, "ascii")
elif isinstance(certificate_data, bytes):
sb_bytes = certificate_data
else:
raise ValueError("Argument must be string or bytes")

return base64.b64encode(base64.b64decode(sb_bytes)) == sb_bytes
except Exception:
return False


def _convert_pem_to_der(certificate_data: Union[str, bytes]) -> bytes:
"""This function is used to convert a pem encoded certificate to a der encoded certificate.

Args:
certificate_data: The certificate to convert.

Returns:
bytes: The der encoded certificate.
"""
if isinstance(certificate_data, str):
# If there's any unicode here, an exception will be thrown and the function will return false
certificate_data = bytes(certificate_data, "ascii")

return base64.b64decode(certificate_data)


def _convert_crt_to_signature_list(file: str, signature_owner: str, **kwargs: any) -> bytes:
"""This function converts a single crt file to a signature list.

Args:
file: The path to the crt file
signature_owner: The signature owner. Defaults to DEFAULT_MS_SIGNATURE_GUID.

Optional Args:
**kwargs: Additional arguments to be passed to the function (These will be intentionally ignored)

Returns:
bytes: The signature list
"""
if signature_owner is not None and not isinstance(signature_owner, uuid.UUID):
signature_owner = uuid.UUID(signature_owner)

siglist = EfiSignatureList(
typeguid=EfiSignatureDataFactory.EFI_CERT_X509_GUID)

with open(file, "rb") as crt_file, TemporaryFile() as temp_file:
certificate = crt_file.read()
if _is_pem_encoded(certificate):
certificate = _convert_pem_to_der(certificate)

temp_file.write(certificate)
temp_file.seek(0)

sigdata = EfiSignatureDataFactory.create(
EfiSignatureDataFactory.EFI_CERT_X509_GUID, temp_file, signature_owner)

# X.509 certificates are variable size, so they must be contained in their own signature list
siglist.AddSignatureHeader(None, SigSize=sigdata.get_total_size())
siglist.AddSignatureData(sigdata)

return siglist.encode()


def generate_header_file(possible_targets, replacement_cert):
"""
Generates a C header file named 'TargetList.h' that contains potential target
SHA256 hashes and a new platform key signature list.
The function performs the following steps:
1. Iterates over a list of possible targets, converting each target's certificate
to an EFI_SIGNATURE_LIST and appending it to a list.
2. Converts a replacement certificate to an EFI_SIGNATURE_LIST.
3. Opens (or creates) 'TargetList.h' for writing.
4. Writes the necessary header guards and includes to the file.
5. Iterates over the possible targets again, converting each to a SHA256 hash,
and writes these hashes to the header file in a specific format.
6. Writes the new platform key signature list to a temporary file and exports it
as a C type array to the header file.
7. Closes the header file.
"""


# Convert the cert to an EFI_SIGNATURE_LIST
new_platform_key_sig_list = _convert_crt_to_signature_list(
file=replacement_cert[0], signature_owner=replacement_cert[1])

with open("PlatformKey.h", 'w') as header_file:
header_file.write("#ifndef PLATFORM_KEY_H_\n")
header_file.write("#define PLATFORM_KEY_H_\n\n")

header_file.write("#include <Uefi.h>\n")
header_file.write("#include <Library/BaseCryptLib.h>\n")

targets = []

for target in possible_targets:
# Convert the cert to a signature list
with open(target, 'rb') as f:

# Calculate the SHA256 hash of the signature list
sha256_hash = hashlib.sha256()
sha256_hash.update(f.read())
targets.append((sha256_hash.hexdigest(), target))

header_file.write("\n//\n")
header_file.write("//Number of Hashes to check against\n")
header_file.write("//\n")
header_file.write(f"#define POTENTIAL_TARGETS {len(targets)}\n\n")

header_file.write("\n//\n")
header_file.write(
"// List of certificate hashes that should be targeted\n")
header_file.write("//\n")
header_file.write(
f"UINT8 PotentialTargetsHashes[POTENTIAL_TARGETS][SHA256_DIGEST_SIZE] = {{\n")
for target in targets:
byte_array = bytes.fromhex(target[0])
first_half = ', '.join(f'0x{byte_array[i]:02x}' for i in range(16))
first_half += ','
second_half = ', '.join(
f'0x{byte_array[i]:02x}' for i in range(16, 32))

header_file.write(
f" {{ // SHA256: {target[0]} ({target[1]})\n")
header_file.write(f" {first_half}\n")
header_file.write(f" {second_half}\n")
header_file.write(" },\n")
header_file.write("};\n\n")

header_file.write("//\n")
header_file.write(
"// In production this should be PkDefault via either SecureBootFetchData(..) or FixedPcdGetPtr (PcdDefaultPk)\n")
header_file.write(f"// Certificate: {replacement_cert[0]}\n")
header_file.write("//\n")

with TemporaryFile() as new_platform_key:
new_platform_key.write(new_platform_key_sig_list)
new_platform_key.seek(0)

export_c_type_array(
new_platform_key, "NewPlatformKey", header_file)

header_file.write("#endif // PLATFORM_KEY_H_\n")


generate_header_file(possible_targets, replacement_cert)
123 changes: 123 additions & 0 deletions OemPkg/OverridePlatformKey/MakeTestCert.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@



$LASTEXITCODE = 0
function GenerateCertificate {
<#
This function generates a certificate used for mock testing

:param KeyLength: The size in bits of the length of the key (ex 2048)
:param CommonName: Common name field of the certificate
:param Variable Name: Name of the variable (Not important for the certificate but used to track which pfx is tied to which signed data)
:param VariablePrefix: Prefix to append to the beginning of the certificate for tracking (Not Important)
:param Signer: Signing certificate object from the Certificate Store

:return: HashTable Object
{
.Cert # Path to Certificate in the Certificate Store
.CertPath # Path to the der encoded certificate
.PfxCertPath # Path to the pfx file generated
}
#>

# Get-ChildItem -Path "Cert:\LocalMachine\My" | Where-Object Thumbprint -EQ 4EFF6B1A0F61B4BF692C77F09889AD151EE8BB58 | Select-Object *

param (
$CertificateParams,
$VariableName,
$VariablePrefix
)

# Return object on success
$PfxCertFilePath = Join-Path -Path $Globals.Layout.CertificateFolder -ChildPath "${VariablePrefix}${VariableName}.pfx"
$CertFilePath = Join-Path -Path $Globals.Layout.CertificateFolder -ChildPath "${VariablePrefix}${VariableName}.cer"
$P7bCertFilePath = Join-Path -Path $Globals.Layout.CertificateFolder -ChildPath "${VariablePrefix}${VariableName}.p7b"

Write-Host "$> New-SelfSignedCertificate " @CertificateParams

# Generate the new certifcate with the chosen params
$Output = New-SelfSignedCertificate @CertificateParams
if ($LASTEXITCODE -ne 0) {
Write-Host "New-SelfSignedCertificate Failed"
Write-Host "Error Code: $LASTEXITCODE"
Write-Host "Output: $Output"
return $null
}

# The path of the certificate in the store
$MockCert = $Globals.Certificate.Store + $Output.Thumbprint

# Print all the details from the certificate
# Get-ChildItem -Path $Globals.Certificate.Store | Where-Object Thumbprint -EQ $Output.Thumbprint | Select-Object * | Write-Host

# export the certificate as a PFX
Export-PfxCertificate -Cert $MockCert -FilePath $PfxCertFilePath -Password $Globals.Certificate.SecurePassword | Out-Null
if ($LASTEXITCODE -ne 0) {
Write-Host "Export-PfxCertificate Failed"
return $null
}

Export-Certificate -Cert $MockCert -FilePath $CertFilePath | Out-Null
if ($LASTEXITCODE -ne 0) {
Write-Host "Export-Certificate Failed"
return $null
}

$ReturnObject = @{
Cert = $MockCert
CertPath = $CertFilePath
PfxCertPath = $PfxCertFilePath
CertInfo = "${PfxCertFilePath};password" # path to the pfx certificate and the password
}

return $ReturnObject
}

$Organization = "Project MU"
$Unit = "TEST"
$CommonName = "DO NOT TRUST - DO NOT USE - WHY DO YOU EVEN HAVE THIS"
$KeyLength = 2048

$Password = "password"
$DataFolder = "./Test"
$TestDataName = "TestData"
$CertName = "Certs"

# Global Variables used throughout the script
$Globals = @{
Certificate = @{
Store = "Cert:\LocalMachine\My\"
Organization = "ProjectMu"
Password = $Password
SecurePassword = ConvertTo-SecureString $Password -Force -AsPlainText
LifeYears = 10 # How long in the future should the Certificate be valid
}
Layout = @{
DataFolder = $DataFolder
CertName = $CertName
CertificateFolder = "$DataFolder/$CertName"
TestDataName = $TestDataName
TestDataFolder = "$DataFolder/$TestDataName"
}
}

# Create the Certificate Folder
if (-not (Test-Path -Path $Globals.Layout.CertificateFolder)) {
New-Item -Path $Globals.Layout.CertificateFolder -ItemType Directory | Out-Null
}

$CertificateParams = @{
DnsName = "www.$Organization.com"
CertStoreLocation = $Globals.Certificate.Store
KeyAlgorithm = "RSA"
KeyLength = $KeyLength
Subject = "CN=$CommonName O=$Organization OU=$Unit"
NotAfter = (Get-Date).AddYears($Globals.Certificate.LifeYears)
KeyUsage = @("CertSign", "CRLSign", "DigitalSignature")
# Basic Constraint :
# CA: A CA certificate, by definition, must have a basic constraints extension with this `CA` boolean value set to "true" in order to be a CA.
# pathlength: Limits the number of intermediate certificates allowed by the next certificates
TextExtension = @("2.5.29.19={text}CA=1")
}

GenerateCertificate -CertificateParams $CertificateParams -VariableName "TestCert" -VariablePrefix "Test"
Loading
Loading