diff options
| author | Steve McIntyre <steve@einval.com> | 2026-04-28 14:01:32 +0100 |
|---|---|---|
| committer | Steve McIntyre <steve@einval.com> | 2026-04-28 17:35:25 +0100 |
| commit | e2a56d673c66989136c41a6f1c997ff1aad10097 (patch) | |
| tree | 9679eb8f7be520822e397011e6e97ab941b58658 | |
| parent | d51bfcf945e1070a551cf8ec5d94b91b213991e1 (diff) | |
| download | shim-signed-e2a56d673c66989136c41a6f1c997ff1aad10097.tar.gz shim-signed-e2a56d673c66989136c41a6f1c997ff1aad10097.zip | |
Add support for verifying and then combining signatures
from multiple signed shims.
* Move the verification logic out into a new helper script
verify_combine_sigs - see comments there for how it works.
* Rename the existing shim binaries and CA cert to match
* Include some extra certs and binaries for testing with
| -rw-r--r-- | 0001-MicCorUEFCA2011_2011-06-27.crt (renamed from MicCorUEFCA2011_2011-06-27.crt) | 0 | ||||
| -rw-r--r-- | 0002-snakeoil.crt | 21 | ||||
| -rw-r--r-- | 0003-wrong.crt | 26 | ||||
| -rw-r--r-- | Makefile | 17 | ||||
| -rw-r--r-- | TEST-shimx64.efi.signed.multi | bin | 0 -> 958840 bytes | |||
| -rw-r--r-- | TEST-shimx64.efi.signed.snakeoil | bin | 0 -> 949072 bytes | |||
| -rw-r--r-- | debian/changelog | 9 | ||||
| -rw-r--r-- | debian/control | 3 | ||||
| -rw-r--r-- | shimaa64.efi.signed.MS-2011 (renamed from shimaa64.efi.signed) | bin | 987432 -> 987432 bytes | |||
| -rw-r--r-- | shimx64.efi.signed.MS-2011 (renamed from shimx64.efi.signed) | bin | 957136 -> 957136 bytes | |||
| -rwxr-xr-x | verify_combine_sigs | 294 |
11 files changed, 355 insertions, 15 deletions
diff --git a/MicCorUEFCA2011_2011-06-27.crt b/0001-MicCorUEFCA2011_2011-06-27.crt index d7c29ef..d7c29ef 100644 --- a/MicCorUEFCA2011_2011-06-27.crt +++ b/0001-MicCorUEFCA2011_2011-06-27.crt diff --git a/0002-snakeoil.crt b/0002-snakeoil.crt new file mode 100644 index 0000000..dd02a82 --- /dev/null +++ b/0002-snakeoil.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIULTs+L+8XzClMGhAvyFIdsp/PYgUwDQYJKoZIhvcNAQEL +BQAwSjELMAkGA1UEBhMCVVMxETAPBgNVBAgMCENvbG9yYWRvMRUwEwYDVQQHDAxG +b3J0IENvbGxpbnMxETAPBgNVBAoMCFNuYWtlT2lsMCAXDTIwMDkwNzE4NDMyMloY +DzIxMjAwODE0MTg0MzIyWjBKMQswCQYDVQQGEwJVUzERMA8GA1UECAwIQ29sb3Jh +ZG8xFTATBgNVBAcMDEZvcnQgQ29sbGluczERMA8GA1UECgwIU25ha2VPaWwwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIi65d6LmojD5S9q8vE/LI2HHQ +boiO5/1KrFVc6kpxD6XdkJwpBoItYIfSls9CPnzvNWOAxR3hIeBd1U/prAPPxvQ1 +wuDLMXfWkcGaYHfPnme/YluAjnpuLH1MQcumgOzj5xYBvZZk+RbytX/phH7FW4Tx ++L1oBYnsfh3BSE/NTtEEHV1nXAXpa/dvyefWMlrlbwjfM5362lZzM6yrJGcOcWEy +I66UYCIVO2Yhe/ZVF5B/tPGtd2oACz11xLeqLPM1WBjlekAG2Zi7UCPIvDCpdn5u +Vna2ZRQmJyDDdh0Ja2VMC19dkMd/5nOAI21O+FvYPOkBWYX8f4DzDyVQlmIFAgMB +AAGjUzBRMB0GA1UdDgQWBBRjuNXuXfh7mi8I3eTboeYGyFTa2zAfBgNVHSMEGDAW +gBRjuNXuXfh7mi8I3eTboeYGyFTa2zAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQBW2ckn0APqBnwSiOXCWkMCnvY7K7UOfxAlotEsMFSrkzdEa4IE +sn0+A3RV/r3HZGqIaE8GMsBqp8UiVIbL5H67dkqvJEke94/7wEUC16JSSOBc0Mac +HeArDWsL/WIbzKiVcRrmgX+XwJFlsUN5UtR/feTHR08yiy5srSCIJEqli/cTrOxS +JAgvWPLxcoFhOKf6Mi+nwWdrQEbpXvvv8Jv/qyyz5e/VmTRY0wIVmUjd+Yseu+5M +3+cpKtlYaawMxVni5RibA0A12fm+i60fGPrkCNhascUrNY+Oppaf/h+QmKOwEM7h +pqKXyGFQyU6dB6cFBQ/uD5IABUYuEOuL7VFY +-----END CERTIFICATE----- diff --git a/0003-wrong.crt b/0003-wrong.crt new file mode 100644 index 0000000..a9039db --- /dev/null +++ b/0003-wrong.crt @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEUzCCAzugAwIBAgIUa3jcFK7TK14FlPqQlHZGuPANPiMwDQYJKoZIhvcNAQEL +BQAwgYoxCzAJBgNVBAYTAkdCMQ4wDAYDVQQIDAVDYW1iczESMBAGA1UEBwwJQ2Ft +YnJpZGdlMQ8wDQYDVQQKDAZTbGVkZ2UxJTAjBgNVBAMMHFNlY3VyZSBCb290IFNp +Z25pbmcgdGVzdCBrZXkxHzAdBgkqhkiG9w0BCQEWEHN0ZXZlQGVpbnZhbC5jb20w +IBcNMTkwNDI4MjMyNDA1WhgPMjExOTA0MDQyMzI0MDVaMIGKMQswCQYDVQQGEwJH +QjEOMAwGA1UECAwFQ2FtYnMxEjAQBgNVBAcMCUNhbWJyaWRnZTEPMA0GA1UECgwG +U2xlZGdlMSUwIwYDVQQDDBxTZWN1cmUgQm9vdCBTaWduaW5nIHRlc3Qga2V5MR8w +HQYJKoZIhvcNAQkBFhBzdGV2ZUBlaW52YWwuY29tMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEArc9AXOIymkUfi3XJD1vZk7LR4cBVdQR71RGQxuM4omg/ +dvu+xaLH+hiwUGQ4/BvOBh95JcKg+ryCRWuILNTbYmxSB6TlJcv/JQgJ7iUmzVcH +NFeEvtfV6Xd5zbh4wqK6ugtiqa7SGEhYKncHcbCL44gZdMKyamEeb5BMaL1VbBSQ +baNeNUiaHeLwREBmNTi+tb0Btq1vTuzNzBN8baGk2RvP91gCUeCN+ILA0DXZ4a43 +flrLt+5d3go76p464htYB+Ab/Z/AFgEBWoKZm6MYZc8igCMdTyLY5Pn4FIX7r23T +5g1u8pCf5MaEO1+ABxnVxim3hbvouABjlNj3BE2HZwIDAQABo4GsMIGpMB0GA1Ud +DgQWBBSND9MzlvKlYN9gpyzKQaKbkq1kjTAfBgNVHSMEGDAWgBSND9MzlvKlYN9g +pyzKQaKbkq1kjTAMBgNVHRMBAf8EAjAAMCsGA1UdJQQkMCIGCCsGAQUFBwMDBgor +BgEEAYI3CgMGBgorBgEEAZIIEAECMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdl +bmVyYXRlZCBDZXJ0aWZpY2F0ZTANBgkqhkiG9w0BAQsFAAOCAQEABJDbjwU0Ua+B +xFGfJlYFBlOzGa+Ppj0zQFonSnEA6zXVTOrnZulScKVUzEBaTA64TY6YdGELums+ +vGPiJ5/JWAY6FAiceXAEy+aIUXBHT1NQbis4kGOaYYiFCMkSOA1QH34/3zLVk3y1 +n//BSURlCh4amKoUZO3H1rS8hqQRYP4Hprv7TTJ+qvlKlKiYZcuODFyAusx+No1T +5ht9dGzDtq9CX7Xi4BVHNVddEN1wKC8+9PlGB837DQUqofXGGt7C9fhIa10h/xWL +KC+c5gaqgG8nBr7qVI7xlAqDq9Mr4ZoWwv43HGHno57d95blMj17aGjBRHF/Y2ak +ebBQxN+Z7A== +-----END CERTIFICATE----- @@ -1,15 +1,4 @@ -all: verify +all: verify_combine -verify: - mkdir -p build - # Verifying that the image is signed with the correct key. - sbverify --cert MicCorUEFCA2011_2011-06-27.crt shim$(EFI_ARCH).efi.signed - # Verifying that we have the correct binary. - sbattach --detach build/detached-sig shim$(EFI_ARCH).efi.signed - cp /usr/lib/shim/shim$(EFI_ARCH).efi build/shim$(EFI_ARCH).efi.signed - sbattach --attach build/detached-sig build/shim$(EFI_ARCH).efi.signed - cmp shim$(EFI_ARCH).efi.signed build/shim$(EFI_ARCH).efi.signed - sha256sum shim$(EFI_ARCH).efi.signed build/shim$(EFI_ARCH).efi.signed - -clean: - rm -rf build +verify_combine: + ./verify_combine_sigs -a ${EFI_ARCH} shim*$(EFI_ARCH).efi*signed* diff --git a/TEST-shimx64.efi.signed.multi b/TEST-shimx64.efi.signed.multi Binary files differnew file mode 100644 index 0000000..e4430c3 --- /dev/null +++ b/TEST-shimx64.efi.signed.multi diff --git a/TEST-shimx64.efi.signed.snakeoil b/TEST-shimx64.efi.signed.snakeoil Binary files differnew file mode 100644 index 0000000..518f593 --- /dev/null +++ b/TEST-shimx64.efi.signed.snakeoil diff --git a/debian/changelog b/debian/changelog index a040b2a..ec43225 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,12 @@ +shim-signed (1.48) UNRELEASED; urgency=medium + + * Add support for verifying and then combining signatures from + multiple signed shims. + + WIP: needs a fixed version of sbverify to work... + + -- Steve McIntyre <93sam@debian.org> Tue, 12 Apr 2026 15:11:11 +0100 + shim-signed (1.47) unstable; urgency=medium * update-secureboot-policy: do better checking around DKMS diff --git a/debian/control b/debian/control index 8357ba8..80eb196 100644 --- a/debian/control +++ b/debian/control @@ -11,7 +11,8 @@ Build-Depends: debhelper (>= 13), # which broke our build sbsigntool (>= 0.9.2-2), po-debconf, - debhelper-compat (= 13) + debhelper-compat (= 13), + python3-cryptography, Standards-Version: 4.5.1 Vcs-Browser: https://salsa.debian.org/efi-team/shim-signed Vcs-Git: https://salsa.debian.org/efi-team/shim-signed.git diff --git a/shimaa64.efi.signed b/shimaa64.efi.signed.MS-2011 Binary files differindex bb54a7b..bb54a7b 100644 --- a/shimaa64.efi.signed +++ b/shimaa64.efi.signed.MS-2011 diff --git a/shimx64.efi.signed b/shimx64.efi.signed.MS-2011 Binary files differindex f2336f6..f2336f6 100644 --- a/shimx64.efi.signed +++ b/shimx64.efi.signed.MS-2011 diff --git a/verify_combine_sigs b/verify_combine_sigs new file mode 100755 index 0000000..438f125 --- /dev/null +++ b/verify_combine_sigs @@ -0,0 +1,294 @@ +#!/usr/bin/python3 +# +# verify_combine_sigs +# +# Helper script for shim-signed +# +# Microsoft currently only return signed binaries with one signature; +# if they are signing with more than key/cert, then we will get +# multiple separate signed binaries, one per key/cert. +# +# Check that all our signed shims are signed with an expected key that +# we can remove and re-add; error out otherwise. +# +# Then finally we will add all those signatures to one output binary. +# +# Order of the listed signed shims matters here - list them *in the +# same order* as the signatures we'd like in the final binary. It's +# recommended to do this in the order: +# +# <oldest CA> +# ... +# <newest CA> +# +# as that is most likely to work with older firmware implementations. +# That's most easily achieved by naming the signatures like +# 0001-first-CA.crt, 0002-second-CA.crt, etc. + +import os +import glob +import sys +import subprocess +import argparse +import shutil +from cryptography import x509 +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.serialization import pkcs7 + +# Allowed certificates - each binary must be signed with a certificate +# from this set. +SIGN_CERTS = "0*.crt" + +# Path to sbverify - special build for now +SBVERIFY = "/usr/bin/sbverify" + + +def parse_args(): + parser = argparse.ArgumentParser(description="verify_combine_sigs") + parser.add_argument( + "--efi_arch", "-a", help="EFI architecture for binaries", required=True + ) + parser.add_argument("signed", help="signed binaries to verify/merge", nargs="+") + args = parser.parse_args() + return args + + +def grab_cert_details(check_cert: str) -> (str, str): + """ + Parse a certificate from disk and grab out: + - a hash of the certificate for comparison later + - the Subject test in a format matching the output of sbverify -l + """ + print(f"Loading details from {check_cert}") + with open(check_cert, "rb") as inf: + pem_data = inf.read() + cert = x509.load_pem_x509_certificate(pem_data) + subject = "/" + "/".join([x.value for x in cert.subject]) + sha256 = cert.fingerprint(hashes.SHA256()).hex() + print(f" - {subject}") + print(f" - sha256sum {sha256}") + return subject, sha256 + + +def list_signatures(signed_filename: str): + cmd = [SBVERIFY, "-l", signed_filename] + output = subprocess.check_output(cmd, stderr=subprocess.DEVNULL, text=True) + for line in output.splitlines(): + print(line) + + +def verify_signature(signed_filename: str, certs: list[str]): + cmd = [SBVERIFY, signed_filename] + for cert in certs: + cmd.extend(["--cert", cert]) + subprocess.check_output(cmd, stderr=subprocess.STDOUT) + + +def parse_sbverify(signed_filename: str) -> list[dict]: + cmd = [SBVERIFY, "-l", signed_filename] + output = subprocess.check_output(cmd, text=True, stderr=subprocess.STDOUT) + signatures = [] + state = 0 + for line in output.splitlines(): + if line.startswith("signature"): + state = 1 + continue + if state == 1 and line.startswith("image signature issuers:"): + state = 2 + continue + if state == 2: + issuer = line[3:] + signatures.append(issuer) + state = 0 + continue + + return signatures + + +def detach_signature(signed_filename: str, signum: int, outfile: str): + """ + Detach a numbered signature from the signature table in a + signed binary. + """ + cmd = [ + "sbattach", + "--signum", + f"{signum}", + "--detach", + outfile, + signed_filename, + ] + subprocess.check_output(cmd, text=True, stderr=subprocess.STDOUT) + + +def certs_in_detached_signature(detached: str) -> list[dict]: + """ + Extract certificate details from a PKCS7 blob. + """ + with open(detached, "rb") as inf: + pkcs7_data = inf.read() + + output = [] + certs = pkcs7.load_der_pkcs7_certificates(pkcs7_data) + for cert in certs: + subject = "/" + "/".join([x.value for x in cert.subject]) + sha256 = cert.fingerprint(hashes.SHA256()).hex() + output.append({"sha256": sha256, "subject": subject}) + + # We want them in the order CA -> leaf + output.reverse() + return output + + +def attach_sig(sigfile: str, unsigned: str): + """ + Use sbattach to add a signature onto a binary. + """ + cmd = [ + "sbattach", + "--attach", + sigfile, + unsigned, + ] + subprocess.check_output(cmd, text=True, stderr=subprocess.STDOUT) + + +def checksum_file(filename: str) -> str: + """ + Calculate the sha256sum of a file + """ + with open(filename, "rb") as inf: + data = inf.read() + hashalg = hashes.SHA256() + hasher = hashes.Hash(hashalg, backend=default_backend()) + hasher.update(data) + digest = hasher.finalize() + return digest.hex() + + +def main(): + + args = parse_args() + + print("Loading details of all the expected certificates") + print("==========") + known_certs = {} + for check_cert in glob.glob(SIGN_CERTS): + subject, sha256 = grab_cert_details(check_cert) + known_certs[subject] = {"sha256": sha256, "filename": check_cert} + print("") + + print(f"Verifying signatures for arch {args.efi_arch} ...") + print("==========\n") + + build = "build" + shutil.rmtree(build, ignore_errors=True) + os.mkdir(build) + + for signed in args.signed: + + print(f"Checking {signed}") + print("----------\n") + + # Verify that the image is signed and valid + print("Looking for any valid checksum and signature") + try: + verify_signature(signed, glob.glob(SIGN_CERTS)) + except Exception as exc: + print(f"Invalid signature on {signed}: {exc}") + sys.exit(1) + + signatures = parse_sbverify(signed) + num = len(signatures) + if num != 1: + print(f"Only expected 1 signature, but {signed} has {num}!") + print("Abort") + sys.exit(1) + # else + print(f"{signed} has 1 signature, good!") + + # Now see what signature we have. We'll have to extract the + # signature table here, then extract the list of certificates + # included in the 1 signature we have. + detached_sig = "detached.sig" + detach_signature(signed, 1, detached_sig) + sig_certs = certs_in_detached_signature(detached_sig) + + matched_filename = None + + print("certs attached:") + for cert in sig_certs: + print(f' - {cert["subject"]}') + print(f' - {cert["sha256"]}') + + # Now we need to compare the root certificate there to our + # known certificates + for subject, data in known_certs.items(): + if ( + sig_certs[0]["subject"] == subject + and sig_certs[0]["sha256"] == data["sha256"] + ): + print( + f'\nroot certificate matches a known certificate ({data["filename"]})' + ) + matched_filename = data["filename"] + + if matched_filename is None: + print(f"\nERROR: {signed} signature unknown, abort!") + sys.exit(1) + + # Move the detached signature to one side, for future use + new_filename = os.path.join(build, f"detached-{matched_filename}") + shutil.move(detached_sig, new_filename) + + # Copy our matching unsigned binary into the ${BUILD} directory. + unsigned = f"{build}/shim{args.efi_arch}.efi.signed" + shutil.copy( + f"/usr/lib/shim/shim{args.efi_arch}.efi", + unsigned, + ) + + # Attach the signature to our unsigned binary, so we know that + # the binary has not been tampered with during the signing + # process. + print("Checking the signature applies to our original binary") + attach_sig(new_filename, unsigned) + print(" Signature applies OK") + + # Now compare the result to the signed binary we were given + print("Comparing the signed binaries") + old_sha = checksum_file(signed) + print(f"{old_sha} {signed}") + new_sha = checksum_file(unsigned) + print(f"{new_sha} {unsigned}") + + if old_sha != new_sha: + print("\nERROR: signatures don't match, abort!") + sys.exit(1) + + print("Binaries match!\n") + + # If we've got this far, then we've checked all the binaries we + # were given and things look OK. Now we want to build a single + # output image with all the signatures attached. + + print(f"Building final combined shim for arch {args.efi_arch} ...") + print("==========") + + shutil.copy( + f"/usr/lib/shim/shim{args.efi_arch}.efi", + unsigned, + ) + + for sig in glob.glob(f"{build}/detached-*"): + print(f"Adding signature {sig}") + attach_sig(sig, unsigned) + + # And finally show the list of signatures + print(f"Signatures on {unsigned} :") + list_signatures(unsigned) + + +if __name__ == "__main__": + main() |
