diff options
Diffstat (limited to 'src/ukify')
-rwxr-xr-x | src/ukify/test/test_ukify.py | 29 | ||||
-rwxr-xr-x | src/ukify/ukify.py | 59 |
2 files changed, 59 insertions, 29 deletions
diff --git a/src/ukify/test/test_ukify.py b/src/ukify/test/test_ukify.py index 5866447..0e3f932 100755 --- a/src/ukify/test/test_ukify.py +++ b/src/ukify/test/test_ukify.py @@ -120,7 +120,7 @@ def test_apply_config(tmp_path): assert ns.sign_kernel is False assert ns._groups == ['NAME'] - assert ns.pcr_private_keys == [pathlib.Path('some/path7')] + assert ns.pcr_private_keys == ['some/path7'] assert ns.pcr_public_keys == [pathlib.Path('some/path8')] assert ns.phase_path_groups == [['enter-initrd:leave-initrd:sysinit:ready:shutdown:final']] @@ -143,7 +143,7 @@ def test_apply_config(tmp_path): assert ns.sign_kernel is False assert ns._groups == ['NAME'] - assert ns.pcr_private_keys == [pathlib.Path('some/path7')] + assert ns.pcr_private_keys == ['some/path7'] assert ns.pcr_public_keys == [pathlib.Path('some/path8')] assert ns.phase_path_groups == [['enter-initrd:leave-initrd:sysinit:ready:shutdown:final']] @@ -189,7 +189,7 @@ def test_parse_args_many_deprecated(): assert opts.pcrpkey == pathlib.Path('PATH') assert opts.uname == '1.2.3' assert opts.stub == pathlib.Path('STUBPATH') - assert opts.pcr_private_keys == [pathlib.Path('PKEY1')] + assert opts.pcr_private_keys == ['PKEY1'] assert opts.pcr_public_keys == [pathlib.Path('PKEY2')] assert opts.pcr_banks == ['SHA1', 'SHA256'] assert opts.signing_engine == 'ENGINE' @@ -235,7 +235,7 @@ def test_parse_args_many(): assert opts.pcrpkey == pathlib.Path('PATH') assert opts.uname == '1.2.3' assert opts.stub == pathlib.Path('STUBPATH') - assert opts.pcr_private_keys == [pathlib.Path('PKEY1')] + assert opts.pcr_private_keys == ['PKEY1'] assert opts.pcr_public_keys == [pathlib.Path('PKEY2')] assert opts.pcr_banks == ['SHA1', 'SHA256'] assert opts.signing_engine == 'ENGINE' @@ -342,8 +342,7 @@ def test_config_priority(tmp_path): assert opts.pcrpkey == pathlib.Path('PATH') assert opts.uname == '1.2.3' assert opts.stub == pathlib.Path('STUBPATH') - assert opts.pcr_private_keys == [pathlib.Path('PKEY1'), - pathlib.Path('some/path7')] + assert opts.pcr_private_keys == ['PKEY1', 'some/path7'] assert opts.pcr_public_keys == [pathlib.Path('PKEY2'), pathlib.Path('some/path8')] assert opts.pcr_banks == ['SHA1', 'SHA256'] @@ -522,14 +521,12 @@ baz,3 assert found is True - def unbase64(filename): tmp = tempfile.NamedTemporaryFile() base64.decode(filename.open('rb'), tmp) tmp.flush() return tmp - def test_uname_scraping(kernel_initrd): if kernel_initrd is None: pytest.skip('linux+initrd not found') @@ -539,7 +536,8 @@ def test_uname_scraping(kernel_initrd): assert re.match(r'\d+\.\d+\.\d+', uname) @pytest.mark.skipif(not slow_tests, reason='slow') -def test_efi_signing_sbsign(kernel_initrd, tmp_path): +@pytest.mark.parametrize("days", [365*10, None]) +def test_efi_signing_sbsign(days, kernel_initrd, tmp_path): if kernel_initrd is None: pytest.skip('linux+initrd not found') if not shutil.which('sbsign'): @@ -550,7 +548,7 @@ def test_efi_signing_sbsign(kernel_initrd, tmp_path): key = unbase64(ourdir / 'example.signing.key.base64') output = f'{tmp_path}/signed.efi' - opts = ukify.parse_args([ + args = [ 'build', *kernel_initrd, f'--output={output}', @@ -558,7 +556,11 @@ def test_efi_signing_sbsign(kernel_initrd, tmp_path): '--cmdline=ARG1 ARG2 ARG3', f'--secureboot-certificate={cert.name}', f'--secureboot-private-key={key.name}', - ]) + ] + if days is not None: + args += [f'--secureboot-certificate-validity={days}'] + + opts = ukify.parse_args(args) try: ukify.check_inputs(opts) @@ -701,8 +703,9 @@ def test_pcr_signing(kernel_initrd, tmp_path): f'--pcr-private-key={priv.name}', ] + arg_tools - # If the public key is not explicitly specified, it is derived automatically. Let's make sure everything - # works as expected both when the public keys is specified explicitly and when it is derived from the + # If the public key is not explicitly specified, it is derived + # automatically. Let's make sure everything works as expected both when the + # public keys is specified explicitly and when it is derived from the # private key. for extra in ([f'--pcrpkey={pub.name}', f'--pcr-public-key={pub.name}'], []): opts = ukify.parse_args(args + extra) diff --git a/src/ukify/ukify.py b/src/ukify/ukify.py index b0d0961..f1db9ba 100755 --- a/src/ukify/ukify.py +++ b/src/ukify/ukify.py @@ -51,7 +51,7 @@ from typing import (Any, import pefile # type: ignore -__version__ = '{{PROJECT_VERSION}} ({{GIT_VERSION}})' +__version__ = '{{PROJECT_VERSION_FULL}} ({{VERSION_TAG}})' EFI_ARCH_MAP = { # host_arch glob : [efi_arch, 32_bit_efi_arch if mixed mode is supported] @@ -68,7 +68,7 @@ EFI_ARCHES: list[str] = sum(EFI_ARCH_MAP.values(), []) # Default configuration directories and file name. # When the user does not specify one, the directories are searched in this order and the first file found is used. -DEFAULT_CONFIG_DIRS = ['/run/systemd', '/etc/systemd', '/usr/local/lib/systemd', '/usr/lib/systemd'] +DEFAULT_CONFIG_DIRS = ['/etc/systemd', '/run/systemd', '/usr/local/lib/systemd', '/usr/lib/systemd'] DEFAULT_CONFIG_FILE = 'ukify.conf' class Style: @@ -236,7 +236,7 @@ class Uname: @classmethod def scrape_x86(cls, filename, opts=None): # Based on https://gitlab.archlinux.org/archlinux/mkinitcpio/mkinitcpio/-/blob/master/functions#L136 - # and https://www.kernel.org/doc/html/latest/x86/boot.html#the-real-mode-kernel-header + # and https://docs.kernel.org/arch/x86/boot.html#the-real-mode-kernel-header with open(filename, 'rb') as f: f.seek(0x202) magic = f.read(4) @@ -303,6 +303,7 @@ class Uname: DEFAULT_SECTIONS_TO_SHOW = { '.linux' : 'binary', '.initrd' : 'binary', + '.ucode' : 'binary', '.splash' : 'binary', '.dtb' : 'binary', '.cmdline' : 'text', @@ -449,7 +450,7 @@ def check_cert_and_keys_nonexistent(opts): *((priv_key, pub_key) for priv_key, pub_key, _ in key_path_groups(opts))) for path in paths: - if path and path.exists(): + if path and pathlib.Path(path).exists(): raise ValueError(f'{path} is present') @@ -539,7 +540,11 @@ def call_systemd_measure(uki, linux, opts): for priv_key, pub_key, group in key_path_groups(opts): extra = [f'--private-key={priv_key}'] - if pub_key: + if opts.signing_engine is not None: + assert pub_key + extra += [f'--private-key-source=engine:{opts.signing_engine}'] + extra += [f'--certificate={pub_key}'] + elif pub_key: extra += [f'--public-key={pub_key}'] extra += [f'--phase={phase_path}' for phase_path in group or ()] @@ -728,11 +733,13 @@ def sbsign_sign(sbsign_tool, input_f, output_f, opts=None): sbsign_tool, '--key', opts.sb_key, '--cert', opts.sb_cert, - input_f, - '--output', output_f, ] if opts.signing_engine is not None: sign_invocation += ['--engine', opts.signing_engine] + sign_invocation += [ + input_f, + '--output', output_f, + ] signer_sign(sign_invocation) def find_pesign(opts=None): @@ -818,9 +825,23 @@ def make_uki(opts): if pcrpkey is None: if opts.pcr_public_keys and len(opts.pcr_public_keys) == 1: pcrpkey = opts.pcr_public_keys[0] + # If we are getting a certificate when using an engine, we need to convert it to public key format + if opts.signing_engine is not None and pathlib.Path(pcrpkey).exists(): + from cryptography.hazmat.primitives import serialization + from cryptography.x509 import load_pem_x509_certificate + + try: + cert = load_pem_x509_certificate(pathlib.Path(pcrpkey).read_bytes()) + except ValueError: + raise ValueError(f'{pcrpkey} must be an X.509 certificate when signing with an engine') + else: + pcrpkey = cert.public_key().public_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo, + ) elif opts.pcr_private_keys and len(opts.pcr_private_keys) == 1: - import cryptography.hazmat.primitives.serialization as serialization - privkey = serialization.load_pem_private_key(opts.pcr_private_keys[0].read_bytes(), password=None) + from cryptography.hazmat.primitives import serialization + privkey = serialization.load_pem_private_key(pathlib.Path(opts.pcr_private_keys[0]).read_bytes(), password=None) pcrpkey = privkey.public_key().public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo, @@ -835,6 +856,7 @@ def make_uki(opts): ('.splash', opts.splash, True ), ('.pcrpkey', pcrpkey, True ), ('.initrd', initrd, True ), + ('.ucode', opts.microcode, True ), # linux shall be last to leave breathing room for decompression. # We'll add it later. @@ -897,7 +919,6 @@ uki-addon,1,UKI Addon,addon,1,https://www.freedesktop.org/software/systemd/man/l print(f"Wrote {'signed' if sign_args_present else 'unsigned'} {opts.output}") - @contextlib.contextmanager def temporary_umask(mask: int): # Drop <mask> bits from umask @@ -1008,7 +1029,7 @@ def generate_keys(opts): print(f'Writing private key for PCR signing to {priv_key}') with temporary_umask(0o077): - priv_key.write_bytes(priv_key_pem) + pathlib.Path(priv_key).write_bytes(priv_key_pem) if pub_key: print(f'Writing public key for PCR signing to {pub_key}') pub_key.write_bytes(pub_key_pem) @@ -1261,6 +1282,14 @@ CONFIG_ITEMS = [ ), ConfigItem( + '--microcode', + metavar = 'UCODE', + type = pathlib.Path, + help = 'microcode file [.ucode section]', + config_key = 'UKI/Microcode', + ), + + ConfigItem( ('--config', '-c'), metavar = 'PATH', type = pathlib.Path, @@ -1411,10 +1440,8 @@ CONFIG_ITEMS = [ ConfigItem( '--pcr-private-key', dest = 'pcr_private_keys', - metavar = 'PATH', - type = pathlib.Path, action = 'append', - help = 'private part of the keypair for signing PCR signatures', + help = 'private part of the keypair or engine-specific designation for signing PCR signatures', config_key = 'PCRSignature:/PCRPrivateKey', config_push = ConfigItem.config_set_group, ), @@ -1424,7 +1451,7 @@ CONFIG_ITEMS = [ metavar = 'PATH', type = pathlib.Path, action = 'append', - help = 'public part of the keypair for signing PCR signatures', + help = 'public part of the keypair or engine-specific designation for signing PCR signatures', config_key = 'PCRSignature:/PCRPublicKey', config_push = ConfigItem.config_set_group, ), @@ -1619,7 +1646,7 @@ def finalize_options(opts): opts.verb = 'build' # Check that --pcr-public-key=, --pcr-private-key=, and --phases= - # have either the same number of arguments are are not specified at all. + # have either the same number of arguments or are not specified at all. n_pcr_pub = None if opts.pcr_public_keys is None else len(opts.pcr_public_keys) n_pcr_priv = None if opts.pcr_private_keys is None else len(opts.pcr_private_keys) n_phase_path_groups = None if opts.phase_path_groups is None else len(opts.phase_path_groups) |