|
| 1 | +from edk2toollib.utility_functions import export_c_type_array |
| 2 | + |
| 3 | +from edk2toollib.uefi.authenticated_variables_structure_support import ( |
| 4 | + EfiSignatureDataFactory, |
| 5 | + EfiSignatureList, |
| 6 | +) |
| 7 | + |
| 8 | +from tempfile import TemporaryFile |
| 9 | +from typing import Union |
| 10 | + |
| 11 | +import uuid |
| 12 | +import base64 |
| 13 | +import hashlib |
| 14 | + |
| 15 | +possible_targets = [ |
| 16 | + "Certs/PlatformKey.der" |
| 17 | +] |
| 18 | + |
| 19 | +replacement_cert = ("Certs/WindowsOEMDevicesPK.der", |
| 20 | + "77fa9abd-0359-4d32-bd60-28f4e78f784b") |
| 21 | + |
| 22 | + |
| 23 | +def _is_pem_encoded(certificate_data: Union[str, bytes]) -> bool: |
| 24 | + """This function is used to check if a certificate is pem encoded (base64 encoded). |
| 25 | +
|
| 26 | + Args: |
| 27 | + certificate_data (str | bytes): The certificate to check. |
| 28 | +
|
| 29 | + Returns: |
| 30 | + bool: True if the certificate is pem encoded, False otherwise. |
| 31 | + """ |
| 32 | + try: |
| 33 | + if isinstance(certificate_data, str): |
| 34 | + # If there's any unicode here, an exception will be thrown and the function will return false |
| 35 | + sb_bytes = bytes(certificate_data, "ascii") |
| 36 | + elif isinstance(certificate_data, bytes): |
| 37 | + sb_bytes = certificate_data |
| 38 | + else: |
| 39 | + raise ValueError("Argument must be string or bytes") |
| 40 | + |
| 41 | + return base64.b64encode(base64.b64decode(sb_bytes)) == sb_bytes |
| 42 | + except Exception: |
| 43 | + return False |
| 44 | + |
| 45 | + |
| 46 | +def _convert_pem_to_der(certificate_data: Union[str, bytes]) -> bytes: |
| 47 | + """This function is used to convert a pem encoded certificate to a der encoded certificate. |
| 48 | +
|
| 49 | + Args: |
| 50 | + certificate_data: The certificate to convert. |
| 51 | +
|
| 52 | + Returns: |
| 53 | + bytes: The der encoded certificate. |
| 54 | + """ |
| 55 | + if isinstance(certificate_data, str): |
| 56 | + # If there's any unicode here, an exception will be thrown and the function will return false |
| 57 | + certificate_data = bytes(certificate_data, "ascii") |
| 58 | + |
| 59 | + return base64.b64decode(certificate_data) |
| 60 | + |
| 61 | + |
| 62 | +def _convert_crt_to_signature_list(file: str, signature_owner: str, **kwargs: any) -> bytes: |
| 63 | + """This function converts a single crt file to a signature list. |
| 64 | +
|
| 65 | + Args: |
| 66 | + file: The path to the crt file |
| 67 | + signature_owner: The signature owner. Defaults to DEFAULT_MS_SIGNATURE_GUID. |
| 68 | +
|
| 69 | + Optional Args: |
| 70 | + **kwargs: Additional arguments to be passed to the function (These will be intentionally ignored) |
| 71 | +
|
| 72 | + Returns: |
| 73 | + bytes: The signature list |
| 74 | + """ |
| 75 | + if signature_owner is not None and not isinstance(signature_owner, uuid.UUID): |
| 76 | + signature_owner = uuid.UUID(signature_owner) |
| 77 | + |
| 78 | + siglist = EfiSignatureList( |
| 79 | + typeguid=EfiSignatureDataFactory.EFI_CERT_X509_GUID) |
| 80 | + |
| 81 | + with open(file, "rb") as crt_file, TemporaryFile() as temp_file: |
| 82 | + certificate = crt_file.read() |
| 83 | + if _is_pem_encoded(certificate): |
| 84 | + certificate = _convert_pem_to_der(certificate) |
| 85 | + |
| 86 | + temp_file.write(certificate) |
| 87 | + temp_file.seek(0) |
| 88 | + |
| 89 | + sigdata = EfiSignatureDataFactory.create( |
| 90 | + EfiSignatureDataFactory.EFI_CERT_X509_GUID, temp_file, signature_owner) |
| 91 | + |
| 92 | + # X.509 certificates are variable size, so they must be contained in their own signature list |
| 93 | + siglist.AddSignatureHeader(None, SigSize=sigdata.get_total_size()) |
| 94 | + siglist.AddSignatureData(sigdata) |
| 95 | + |
| 96 | + return siglist.encode() |
| 97 | + |
| 98 | + |
| 99 | +def generate_header_file(possible_targets, replacement_cert): |
| 100 | + """ |
| 101 | + Generates a C header file named 'TargetList.h' that contains potential target |
| 102 | + SHA256 hashes and a new platform key signature list. |
| 103 | + The function performs the following steps: |
| 104 | + 1. Iterates over a list of possible targets, converting each target's certificate |
| 105 | + to an EFI_SIGNATURE_LIST and appending it to a list. |
| 106 | + 2. Converts a replacement certificate to an EFI_SIGNATURE_LIST. |
| 107 | + 3. Opens (or creates) 'TargetList.h' for writing. |
| 108 | + 4. Writes the necessary header guards and includes to the file. |
| 109 | + 5. Iterates over the possible targets again, converting each to a SHA256 hash, |
| 110 | + and writes these hashes to the header file in a specific format. |
| 111 | + 6. Writes the new platform key signature list to a temporary file and exports it |
| 112 | + as a C type array to the header file. |
| 113 | + 7. Closes the header file. |
| 114 | + """ |
| 115 | + |
| 116 | + |
| 117 | + # Convert the cert to an EFI_SIGNATURE_LIST |
| 118 | + new_platform_key_sig_list = _convert_crt_to_signature_list( |
| 119 | + file=replacement_cert[0], signature_owner=replacement_cert[1]) |
| 120 | + |
| 121 | + with open("PlatformKey.h", 'w') as header_file: |
| 122 | + header_file.write("#ifndef PLATFORM_KEY_H_\n") |
| 123 | + header_file.write("#define PLATFORM_KEY_H_\n\n") |
| 124 | + |
| 125 | + header_file.write("#include <Uefi.h>\n") |
| 126 | + header_file.write("#include <Library/BaseCryptLib.h>\n") |
| 127 | + |
| 128 | + targets = [] |
| 129 | + |
| 130 | + for target in possible_targets: |
| 131 | + # Convert the cert to a signature list |
| 132 | + with open(target, 'rb') as f: |
| 133 | + |
| 134 | + # Calculate the SHA256 hash of the signature list |
| 135 | + sha256_hash = hashlib.sha256() |
| 136 | + sha256_hash.update(f.read()) |
| 137 | + targets.append((sha256_hash.hexdigest(), target)) |
| 138 | + |
| 139 | + header_file.write("\n//\n") |
| 140 | + header_file.write("//Number of Hashes to check against\n") |
| 141 | + header_file.write("//\n") |
| 142 | + header_file.write(f"#define POTENTIAL_TARGETS {len(targets)}\n\n") |
| 143 | + |
| 144 | + header_file.write("\n//\n") |
| 145 | + header_file.write( |
| 146 | + "// List of certificate hashes that should be targeted\n") |
| 147 | + header_file.write("//\n") |
| 148 | + header_file.write( |
| 149 | + f"UINT8 PotentialTargetsHashes[POTENTIAL_TARGETS][SHA256_DIGEST_SIZE] = {{\n") |
| 150 | + for target in targets: |
| 151 | + byte_array = bytes.fromhex(target[0]) |
| 152 | + first_half = ', '.join(f'0x{byte_array[i]:02x}' for i in range(16)) |
| 153 | + first_half += ',' |
| 154 | + second_half = ', '.join( |
| 155 | + f'0x{byte_array[i]:02x}' for i in range(16, 32)) |
| 156 | + |
| 157 | + header_file.write( |
| 158 | + f" {{ // SHA256: {target[0]} ({target[1]})\n") |
| 159 | + header_file.write(f" {first_half}\n") |
| 160 | + header_file.write(f" {second_half}\n") |
| 161 | + header_file.write(" },\n") |
| 162 | + header_file.write("};\n\n") |
| 163 | + |
| 164 | + header_file.write("//\n") |
| 165 | + header_file.write( |
| 166 | + "// In production this should be PkDefault via either SecureBootFetchData(..) or FixedPcdGetPtr (PcdDefaultPk)\n") |
| 167 | + header_file.write(f"// Certificate: {replacement_cert[0]}\n") |
| 168 | + header_file.write("//\n") |
| 169 | + |
| 170 | + with TemporaryFile() as new_platform_key: |
| 171 | + new_platform_key.write(new_platform_key_sig_list) |
| 172 | + new_platform_key.seek(0) |
| 173 | + |
| 174 | + export_c_type_array( |
| 175 | + new_platform_key, "NewPlatformKey", header_file) |
| 176 | + |
| 177 | + header_file.write("#endif // PLATFORM_KEY_H_\n") |
| 178 | + |
| 179 | + |
| 180 | +generate_header_file(possible_targets, replacement_cert) |
0 commit comments