From: Luca Boccassi Date: Mon, 20 Jan 2025 00:30:48 +0000 (+0000) Subject: ukify: add --policy-digest option X-Git-Tag: v258-rc1~1543 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=606c5e75809966177c556a9b3ae8a95fab36e098;p=thirdparty%2Fsystemd.git ukify: add --policy-digest option Uses the newly added policy-digest verb of systemd-measure, for the same purpose: build a UKI and get digests for the .pcrsig section out, so that they can be offline signed and reattached --- diff --git a/man/ukify.xml b/man/ukify.xml index 13c14a45c84..dfd74d59270 100644 --- a/man/ukify.xml +++ b/man/ukify.xml @@ -209,6 +209,18 @@ + + + + + Enable or disable a call to + systemd-measure1 + to print pre-calculated TPM2 policy digests. Useful for offline signing of PCR policies. + Defaults to false. + + + + diff --git a/src/ukify/test/test_ukify.py b/src/ukify/test/test_ukify.py index 3ed21fc0ace..0f974b3558b 100755 --- a/src/ukify/test/test_ukify.py +++ b/src/ukify/test/test_ukify.py @@ -242,6 +242,8 @@ def test_parse_args_many(): '--output=OUTPUT', '--measure', '--no-measure', + '--policy-digest', + '--no-policy-digest', ]) assert opts.linux == pathlib.Path('/ARG1') assert opts.initrd == [pathlib.Path('/ARG2'), pathlib.Path('/ARG3 WITH SPACE')] @@ -262,6 +264,7 @@ def test_parse_args_many(): assert opts.tools == [pathlib.Path('TOOLZ/')] assert opts.output == pathlib.Path('OUTPUT') assert opts.measure is False + assert opts.policy_digest is False def test_parse_sections(): opts = ukify.parse_args( diff --git a/src/ukify/ukify.py b/src/ukify/ukify.py index b3673a032aa..eaeaa542024 100755 --- a/src/ukify/ukify.py +++ b/src/ukify/ukify.py @@ -255,6 +255,7 @@ class UkifyConfig: pcr_public_keys: list[str] pcrpkey: Optional[Path] phase_path_groups: Optional[list[str]] + policy_digest: bool profile: Union[str, Path, None] sb_cert: Union[str, Path, None] sb_cert_name: Optional[str] @@ -751,7 +752,7 @@ def call_systemd_measure(uki: UKI, opts: UkifyConfig, profile_start: int = 0) -> unique_to_measure[section.name] = section - if opts.measure: + if opts.measure or opts.policy_digest: to_measure = unique_to_measure.copy() for dtbauto in dtbauto_to_measure: @@ -762,7 +763,7 @@ def call_systemd_measure(uki: UKI, opts: UkifyConfig, profile_start: int = 0) -> cmd = [ measure_tool, - 'calculate', + 'calculate' if opts.measure else 'policy-digest', '--json', opts.json, *(f'--{s.name.removeprefix(".")}={s.content}' for s in to_measure.values()), @@ -772,6 +773,11 @@ def call_systemd_measure(uki: UKI, opts: UkifyConfig, profile_start: int = 0) -> *(f'--phase={phase_path}' for phase_path in itertools.chain.from_iterable(pp_groups)), ] + # The JSON object will be used for offline signing, include the public key + # so that the fingerprint is included too. + if opts.policy_digest and opts.pcr_public_keys: + cmd += [f'--public-key={opts.pcr_public_keys[0]}'] + print('+', shell_join(cmd), file=sys.stderr) subprocess.check_call(cmd) @@ -1940,6 +1946,11 @@ CONFIG_ITEMS = [ action=argparse.BooleanOptionalAction, help='print systemd-measure output for the UKI', ), + ConfigItem( + '--policy-digest', + action=argparse.BooleanOptionalAction, + help='print systemd-measure policy digests for the UKI', + ), ConfigItem( '--json', choices=('pretty', 'short', 'off'), @@ -2108,10 +2119,16 @@ def finalize_options(opts: argparse.Namespace) -> None: # Check that --pcr-public-key=, --pcr-private-key=, and --phases= # have either the same number of arguments or are not specified at all. + # But allow a single public key, for offline PCR signing, to pre-populate the JSON object + # with the certificate's fingerprint. 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) - if n_pcr_pub is not None and n_pcr_pub != n_pcr_priv: + if opts.policy_digest and n_pcr_priv is not None: + raise ValueError('--pcr-private-key= cannot be specified with --policy-digest') + if opts.policy_digest and (n_pcr_pub is None or n_pcr_pub != 1): + raise ValueError('--policy-digest requires exactly one --pcr-public-key=') + if n_pcr_pub is not None and n_pcr_priv is not None and n_pcr_pub != n_pcr_priv: raise ValueError('--pcr-public-key= specifications must match --pcr-private-key=') if n_phase_path_groups is not None and n_phase_path_groups != n_pcr_priv: raise ValueError('--phases= specifications must match --pcr-private-key=')