diff options
author | Daan De Meyer <daan.j.demeyer@gmail.com> | 2024-11-06 18:09:37 +0100 |
---|---|---|
committer | Daan De Meyer <daan.j.demeyer@gmail.com> | 2024-11-07 20:33:08 +0100 |
commit | 64cc7ba517c7d57a25ede9833e60aa8aa25fc6ed (patch) | |
tree | b43d68cab67b8963d989e7cf939248c4055eb4a1 | |
parent | measure: Add pcrpkey verb (diff) | |
download | systemd-64cc7ba517c7d57a25ede9833e60aa8aa25fc6ed.tar.xz systemd-64cc7ba517c7d57a25ede9833e60aa8aa25fc6ed.zip |
ukify: Introduce --certificate-provider= option
This translates to --certificate-source=provider:<provider> for
signing tools invoked by ukify.
-rw-r--r-- | man/ukify.xml | 11 | ||||
-rwxr-xr-x | src/ukify/test/test_ukify.py | 19 | ||||
-rwxr-xr-x | src/ukify/ukify.py | 90 |
3 files changed, 72 insertions, 48 deletions
diff --git a/man/ukify.xml b/man/ukify.xml index 6a697ee6e1..c42d6ae5c7 100644 --- a/man/ukify.xml +++ b/man/ukify.xml @@ -528,6 +528,17 @@ </varlistentry> <varlistentry> + <term><varname>CertificateProvider=<replaceable>PROVIDER</replaceable></varname></term> + <term><option>--certificate-provider=<replaceable>PROVIDER</replaceable></option></term> + + <listitem><para>An OpenSSL provider to be used for loading the certificate used to sign the + resulting binary and PCR measurements. This option can only be used when using + <command>systemd-sbsign</command> as the signing tool.</para> + + <xi:include href="version-info.xml" xpointer="v257"/></listitem> + </varlistentry> + + <varlistentry> <term><varname>SignKernel=<replaceable>BOOL</replaceable></varname></term> <term><option>--sign-kernel</option></term> <term><option>--no-sign-kernel</option></term> diff --git a/src/ukify/test/test_ukify.py b/src/ukify/test/test_ukify.py index 70e1a4e1d0..9eebf7eca1 100755 --- a/src/ukify/test/test_ukify.py +++ b/src/ukify/test/test_ukify.py @@ -138,7 +138,7 @@ def test_apply_config(tmp_path): assert ns._groups == ['NAME'] assert ns.pcr_private_keys == ['some/path7'] - assert ns.pcr_public_keys == [pathlib.Path('some/path8')] + assert ns.pcr_public_keys == ['some/path8'] assert ns.phase_path_groups == [['enter-initrd:leave-initrd:sysinit:ready:shutdown:final']] ukify.finalize_options(ns) @@ -156,12 +156,12 @@ def test_apply_config(tmp_path): assert ns.pcr_banks == ['sha512', 'sha1'] assert ns.signing_engine == 'engine1' assert ns.sb_key == 'some/path5' - assert ns.sb_cert == 'some/path6' + assert ns.sb_cert == pathlib.Path('some/path6') assert ns.sign_kernel is False assert ns._groups == ['NAME'] assert ns.pcr_private_keys == ['some/path7'] - assert ns.pcr_public_keys == [pathlib.Path('some/path8')] + assert ns.pcr_public_keys == ['some/path8'] assert ns.phase_path_groups == [['enter-initrd:leave-initrd:sysinit:ready:shutdown:final']] def test_parse_args_minimal(): @@ -207,11 +207,11 @@ def test_parse_args_many_deprecated(): assert opts.uname == '1.2.3' assert opts.stub == pathlib.Path('STUBPATH') assert opts.pcr_private_keys == ['PKEY1'] - assert opts.pcr_public_keys == [pathlib.Path('PKEY2')] + assert opts.pcr_public_keys == ['PKEY2'] assert opts.pcr_banks == ['SHA1', 'SHA256'] assert opts.signing_engine == 'ENGINE' assert opts.sb_key == 'SBKEY' - assert opts.sb_cert == 'SBCERT' + assert opts.sb_cert == pathlib.Path('SBCERT') assert opts.sign_kernel is False assert opts.tools == [pathlib.Path('TOOLZ/')] assert opts.output == pathlib.Path('OUTPUT') @@ -253,11 +253,11 @@ def test_parse_args_many(): assert opts.uname == '1.2.3' assert opts.stub == pathlib.Path('STUBPATH') assert opts.pcr_private_keys == ['PKEY1'] - assert opts.pcr_public_keys == [pathlib.Path('PKEY2')] + assert opts.pcr_public_keys == ['PKEY2'] assert opts.pcr_banks == ['SHA1', 'SHA256'] assert opts.signing_engine == 'ENGINE' assert opts.sb_key == 'SBKEY' - assert opts.sb_cert == 'SBCERT' + assert opts.sb_cert == pathlib.Path('SBCERT') assert opts.sign_kernel is False assert opts.tools == [pathlib.Path('TOOLZ/')] assert opts.output == pathlib.Path('OUTPUT') @@ -360,13 +360,12 @@ def test_config_priority(tmp_path): assert opts.uname == '1.2.3' assert opts.stub == pathlib.Path('STUBPATH') assert opts.pcr_private_keys == ['PKEY1', 'some/path7'] - assert opts.pcr_public_keys == [pathlib.Path('PKEY2'), - pathlib.Path('some/path8')] + assert opts.pcr_public_keys == ['PKEY2', 'some/path8'] assert opts.pcr_banks == ['SHA1', 'SHA256'] assert opts.signing_engine == 'ENGINE' assert opts.signtool == ukify.SbSign # from args assert opts.sb_key == 'SBKEY' # from args - assert opts.sb_cert == 'SBCERT' # from args + assert opts.sb_cert == pathlib.Path('SBCERT') # from args assert opts.sb_certdir == 'some/path5' # from config assert opts.sb_cert_name == 'some/name1' # from config assert opts.sign_kernel is False diff --git a/src/ukify/ukify.py b/src/ukify/ukify.py index ef4e9264c2..355e3f99f4 100755 --- a/src/ukify/ukify.py +++ b/src/ukify/ukify.py @@ -249,21 +249,22 @@ class UkifyConfig: output: Optional[str] pcr_banks: list[str] pcr_private_keys: list[str] - pcr_public_keys: list[Path] + pcr_public_keys: list[str] pcrpkey: Optional[Path] phase_path_groups: Optional[list[str]] profile: Union[str, Path, None] - sb_cert: Path + sb_cert: Union[str, Path, None] sb_cert_name: Optional[str] sb_cert_validity: int sb_certdir: Path - sb_key: Optional[Path] + sb_key: Union[str, Path, None] sbat: Optional[list[str]] sections: list['Section'] sections_by_name: dict[str, 'Section'] sign_kernel: bool signing_engine: Optional[str] signing_provider: Optional[str] + certificate_provider: Optional[str] signtool: Optional[type['SignTool']] splash: Optional[Path] stub: Path @@ -554,6 +555,11 @@ class SystemdSbSign(SignTool): if opts.signing_provider is not None else [] ), + *( + ['--certificate-source', f'provider:{opts.certificate_provider}'] + if opts.certificate_provider is not None + else [] + ), input_f, '--output', output_f, ] # fmt: skip @@ -666,7 +672,7 @@ def combine_signatures(pcrsigs: list[dict[str, str]]) -> str: return json.dumps(combined) -def key_path_groups(opts: UkifyConfig) -> Iterator[tuple[str, Optional[Path], Optional[str]]]: +def key_path_groups(opts: UkifyConfig) -> Iterator[tuple[str, Optional[str], Optional[str]]]: if not opts.pcr_private_keys: return @@ -757,6 +763,10 @@ def call_systemd_measure(uki: UKI, opts: UkifyConfig, profile_start: int = 0) -> extra += [f'--certificate={pub_key}'] elif pub_key: extra += [f'--public-key={pub_key}'] + + if opts.certificate_provider is not None: + extra += [f'--certificate-source=provider:{opts.certificate_provider}'] + extra += [f'--phase={phase_path}' for phase_path in group or ()] print('+', shell_join(cmd + extra)) # type: ignore @@ -1007,34 +1017,30 @@ def make_uki(opts: UkifyConfig) -> None: pcrpkey: Union[bytes, Path, None] = opts.pcrpkey if pcrpkey is None: + measure_tool = find_tool('systemd-measure', '/usr/lib/systemd/systemd-measure') + cmd = [measure_tool, 'pcrpkey'] + 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 or provider, we need to convert it to - # public key format. - if (opts.signing_engine or opts.signing_provider) and Path(pcrpkey).exists(): - from cryptography.hazmat.primitives import serialization - from cryptography.x509 import load_pem_x509_certificate - - try: - cert = load_pem_x509_certificate(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, - ) + # If we're using an engine or provider, the public key will be an X.509 certificate. + if opts.signing_engine or opts.signing_provider: + cmd += ['--certificate', opts.pcr_public_keys[0]] + if opts.certificate_provider: + cmd += ['--certificate-source', f'provider:{opts.certificate_provider}'] + else: + cmd += ['--public-key', opts.pcr_public_keys[0]] + + print('+', shell_join(cmd)) + pcrpkey = subprocess.check_output(cmd) elif opts.pcr_private_keys and len(opts.pcr_private_keys) == 1: - from cryptography.hazmat.primitives import serialization + cmd += ['--private-key', Path(opts.pcr_private_keys[0])] - privkey = serialization.load_pem_private_key( - Path(opts.pcr_private_keys[0]).read_bytes(), - password=None, - ) - pcrpkey = privkey.public_key().public_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PublicFormat.SubjectPublicKeyInfo, - ) + if opts.signing_engine: + cmd += ['--private-key-source', f'engine:{opts.signing_engine}'] + if opts.signing_provider: + cmd += ['--private-key-source', f'provider:{opts.signing_provider}'] + + print('+', shell_join(cmd)) + pcrpkey = subprocess.check_output(cmd) sections = [ # name, content, measure? @@ -1270,9 +1276,9 @@ def generate_keys(opts: UkifyConfig) -> None: ) print(f'Writing SecureBoot private key to {opts.sb_key}') with temporary_umask(0o077): - opts.sb_key.write_bytes(key_pem) + Path(opts.sb_key).write_bytes(key_pem) print(f'Writing SecureBoot certificate to {opts.sb_cert}') - opts.sb_cert.write_bytes(cert_pem) + Path(opts.sb_cert).write_bytes(cert_pem) work = True @@ -1284,7 +1290,7 @@ def generate_keys(opts: UkifyConfig) -> None: 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) + Path(pub_key).write_bytes(pub_key_pem) work = True @@ -1675,6 +1681,12 @@ CONFIG_ITEMS = [ config_key='UKI/SigningProvider', ), ConfigItem( + '--certificate-provider', + metavar='PROVIDER', + help='OpenSSL provider to load certificate from', + config_key='UKI/CertificateProvider', + ), + ConfigItem( '--signtool', choices=('sbsign', 'pesign', 'systemd-sbsign'), action=SignToolAction, @@ -1746,7 +1758,6 @@ CONFIG_ITEMS = [ '--pcr-public-key', dest='pcr_public_keys', metavar='PATH', - type=Path, action='append', help='public part of the keypair or engine/provider designation for signing PCR signatures', config_key='PCRSignature:/PCRPublicKey', @@ -1982,11 +1993,11 @@ def finalize_options(opts: argparse.Namespace) -> None: if opts.signing_engine and opts.signing_provider: raise ValueError('Only one of --signing-engine= and --signing-provider= may be specified') - if opts.signing_engine is None and opts.signing_provider is None: - if opts.sb_key: - opts.sb_key = Path(opts.sb_key) - if opts.sb_cert: - opts.sb_cert = Path(opts.sb_cert) + if opts.signing_engine is None and opts.signing_provider is None and opts.sb_key: + opts.sb_key = Path(opts.sb_key) + + if opts.certificate_provider is None and opts.sb_cert: + opts.sb_cert = Path(opts.sb_cert) if bool(opts.sb_key) ^ bool(opts.sb_cert): # one param only given, sbsign needs both @@ -2012,6 +2023,9 @@ def finalize_options(opts: argparse.Namespace) -> None: if opts.signing_provider and opts.signtool != SystemdSbSign: raise ValueError('--signing-provider= can only be used with--signtool=systemd-sbsign') + if opts.certificate_provider and opts.signtool != SystemdSbSign: + raise ValueError('--certificate-provider= can only be used with--signtool=systemd-sbsign') + if opts.sign_kernel and not opts.sb_key and not opts.sb_cert_name: raise ValueError( '--sign-kernel requires either --secureboot-private-key= and --secureboot-certificate= (for sbsign) or --secureboot-certificate-name= (for pesign) to be specified' # noqa: E501 |