mirror of
https://github.com/systemd/systemd.git
synced 2025-08-25 13:49:55 +03:00
ukify: add --pcr-certificate= parameter
Public keys and certificates are not the same, as the latter embeds more information that the former, and other tools like sd-measure have distinct parameters for each of them. Add a new --pcr-certificate= parameter to ukify, and use it to pass certs down to sd-measure, as an alternative to --pcr-public-key=. Do not allow specifying both.
This commit is contained in:
@ -85,7 +85,8 @@
|
|||||||
|
|
||||||
<para>If PCR signing keys are provided via the
|
<para>If PCR signing keys are provided via the
|
||||||
<varname>PCRPrivateKey=</varname>/<option>--pcr-private-key=</option> and
|
<varname>PCRPrivateKey=</varname>/<option>--pcr-private-key=</option> and
|
||||||
<varname>PCRPublicKey=</varname>/<option>--pcr-public-key=</option> options, PCR values that will be seen
|
<varname>PCRPublicKey=</varname>/<option>--pcr-public-key=</option> or
|
||||||
|
<varname>PCRCertificate=</varname>/<option>--pcr-certificate=</option> options, PCR values that will be seen
|
||||||
after booting with the given kernel, initrd, and other sections, will be calculated, signed, and embedded
|
after booting with the given kernel, initrd, and other sections, will be calculated, signed, and embedded
|
||||||
in the UKI.
|
in the UKI.
|
||||||
<citerefentry><refentrytitle>systemd-measure</refentrytitle><manvolnum>1</manvolnum></citerefentry> is
|
<citerefentry><refentrytitle>systemd-measure</refentrytitle><manvolnum>1</manvolnum></citerefentry> is
|
||||||
@ -95,7 +96,8 @@
|
|||||||
the <varname>Phases=</varname>/<option>--phases=</option> option. If not specified, the default provided
|
the <varname>Phases=</varname>/<option>--phases=</option> option. If not specified, the default provided
|
||||||
by <command>systemd-measure</command> is used. It is also possible to specify the
|
by <command>systemd-measure</command> is used. It is also possible to specify the
|
||||||
<varname>PCRPrivateKey=</varname>/<option>--pcr-private-key=</option>,
|
<varname>PCRPrivateKey=</varname>/<option>--pcr-private-key=</option>,
|
||||||
<varname>PCRPublicKey=</varname>/<option>--pcr-public-key=</option>, and
|
<varname>PCRPublicKey=</varname>/<option>--pcr-public-key=</option> or
|
||||||
|
<varname>PCRCertificate=</varname>/<option>--pcr-certificate=</option>, and
|
||||||
<varname>Phases=</varname>/<option>--phases=</option> arguments more than once. Signatures will then be
|
<varname>Phases=</varname>/<option>--phases=</option> arguments more than once. Signatures will then be
|
||||||
performed with each of the specified keys. On the command line, when both <option>--phases=</option> and
|
performed with each of the specified keys. On the command line, when both <option>--phases=</option> and
|
||||||
<option>--pcr-private-key=</option> are used, they must be specified the same number of times, and then
|
<option>--pcr-private-key=</option> are used, they must be specified the same number of times, and then
|
||||||
@ -478,8 +480,9 @@
|
|||||||
|
|
||||||
<listitem><para>A path to a public key to embed in the <literal>.pcrpkey</literal> section. If not
|
<listitem><para>A path to a public key to embed in the <literal>.pcrpkey</literal> section. If not
|
||||||
specified, and there's exactly one
|
specified, and there's exactly one
|
||||||
<varname>PCRPublicKey=</varname>/<option>--pcr-public-key=</option> argument, that key will be used.
|
<varname>PCRPublicKey=</varname>/<option>--pcr-public-key=</option> or
|
||||||
Otherwise, the section will not be present.</para>
|
<varname>PCRCertificate=</varname>/<option>--pcr-certificate=</option> argument, that key will be
|
||||||
|
used. Otherwise, the section will not be present.</para>
|
||||||
|
|
||||||
<xi:include href="version-info.xml" xpointer="v253"/></listitem>
|
<xi:include href="version-info.xml" xpointer="v253"/></listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -652,11 +655,27 @@
|
|||||||
<para>On the command line, this option may be specified more than once, similarly to the
|
<para>On the command line, this option may be specified more than once, similarly to the
|
||||||
<option>--pcr-private-key=</option> option. If not present, the public keys will be extracted from
|
<option>--pcr-private-key=</option> option. If not present, the public keys will be extracted from
|
||||||
the private keys. On the command line, if present, this option must be specified the same number of
|
the private keys. On the command line, if present, this option must be specified the same number of
|
||||||
times as the <option>--pcr-private-key=</option> option.</para>
|
times as the <option>--pcr-private-key=</option> option. Cannot be specified if
|
||||||
|
<option>--pcr-certificate=</option> is used.</para>
|
||||||
|
|
||||||
<xi:include href="version-info.xml" xpointer="v253"/></listitem>
|
<xi:include href="version-info.xml" xpointer="v253"/></listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><varname>PCRCertificate=<replaceable>PATH</replaceable></varname></term>
|
||||||
|
<term><option>--pcr-certificate=<replaceable>PATH</replaceable></option></term>
|
||||||
|
|
||||||
|
<listitem><para>An X.509 certificate to use for signing PCR policies.</para>
|
||||||
|
|
||||||
|
<para>On the command line, this option may be specified more than once, similarly to the
|
||||||
|
<option>--pcr-private-key=</option> option. If not present, the public keys will be extracted from
|
||||||
|
the private keys. On the command line, if present, this option must be specified the same number of
|
||||||
|
times as the <option>--pcr-private-key=</option> option. Cannot be specified if
|
||||||
|
<option>--pcr-public-key=</option> is used.</para>
|
||||||
|
|
||||||
|
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term><varname>Phases=<replaceable>LIST</replaceable></varname></term>
|
<term><varname>Phases=<replaceable>LIST</replaceable></varname></term>
|
||||||
<term><option>--phases=<replaceable>LIST</replaceable></option></term>
|
<term><option>--phases=<replaceable>LIST</replaceable></option></term>
|
||||||
|
@ -274,6 +274,7 @@ class UkifyConfig:
|
|||||||
pcr_banks: list[str]
|
pcr_banks: list[str]
|
||||||
pcr_private_keys: list[str]
|
pcr_private_keys: list[str]
|
||||||
pcr_public_keys: list[str]
|
pcr_public_keys: list[str]
|
||||||
|
pcr_certificates: list[str]
|
||||||
pcrpkey: Optional[Path]
|
pcrpkey: Optional[Path]
|
||||||
pcrsig: Union[str, Path, None]
|
pcrsig: Union[str, Path, None]
|
||||||
join_pcrsig: Optional[Path]
|
join_pcrsig: Optional[Path]
|
||||||
@ -683,7 +684,7 @@ def check_cert_and_keys_nonexistent(opts: UkifyConfig) -> None:
|
|||||||
# Raise if any of the keys and certs are found on disk
|
# Raise if any of the keys and certs are found on disk
|
||||||
paths: Iterator[Union[str, Path, None]] = itertools.chain(
|
paths: Iterator[Union[str, Path, None]] = itertools.chain(
|
||||||
(opts.sb_key, opts.sb_cert),
|
(opts.sb_key, opts.sb_cert),
|
||||||
*((priv_key, pub_key) for priv_key, pub_key, _ in key_path_groups(opts)),
|
*((priv_key, pub_key, cert) for priv_key, pub_key, cert, _ in key_path_groups(opts)),
|
||||||
)
|
)
|
||||||
for path in paths:
|
for path in paths:
|
||||||
if path and Path(path).exists():
|
if path and Path(path).exists():
|
||||||
@ -721,17 +722,19 @@ def combine_signatures(pcrsigs: list[dict[str, str]]) -> str:
|
|||||||
return json.dumps(combined)
|
return json.dumps(combined)
|
||||||
|
|
||||||
|
|
||||||
def key_path_groups(opts: UkifyConfig) -> Iterator[tuple[str, Optional[str], Optional[str]]]:
|
def key_path_groups(opts: UkifyConfig) -> Iterator[tuple[str, Optional[str], Optional[str], Optional[str]]]:
|
||||||
if not opts.pcr_private_keys:
|
if not opts.pcr_private_keys:
|
||||||
return
|
return
|
||||||
|
|
||||||
n_priv = len(opts.pcr_private_keys)
|
n_priv = len(opts.pcr_private_keys)
|
||||||
pub_keys = opts.pcr_public_keys or []
|
pub_keys = opts.pcr_public_keys or []
|
||||||
|
certs = opts.pcr_certificates or []
|
||||||
pp_groups = opts.phase_path_groups or []
|
pp_groups = opts.phase_path_groups or []
|
||||||
|
|
||||||
yield from itertools.zip_longest(
|
yield from itertools.zip_longest(
|
||||||
opts.pcr_private_keys,
|
opts.pcr_private_keys,
|
||||||
pub_keys[:n_priv],
|
pub_keys[:n_priv],
|
||||||
|
certs[:n_priv],
|
||||||
pp_groups[:n_priv],
|
pp_groups[:n_priv],
|
||||||
fillvalue=None,
|
fillvalue=None,
|
||||||
)
|
)
|
||||||
@ -809,9 +812,15 @@ def call_systemd_measure(uki: UKI, opts: UkifyConfig, profile_start: int = 0) ->
|
|||||||
]
|
]
|
||||||
|
|
||||||
# The JSON object will be used for offline signing, include the public key
|
# The JSON object will be used for offline signing, include the public key
|
||||||
# so that the fingerprint is included too.
|
# so that the fingerprint is included too. In case a certificate is passed, use the
|
||||||
if opts.policy_digest and opts.pcr_public_keys:
|
# right parameter so that systemd-measure can extract the public key from it.
|
||||||
cmd += [f'--public-key={opts.pcr_public_keys[0]}']
|
if opts.policy_digest:
|
||||||
|
if opts.pcr_public_keys:
|
||||||
|
cmd += ['--public-key', opts.pcr_public_keys[0]]
|
||||||
|
elif opts.pcr_certificates:
|
||||||
|
cmd += ['--certificate', opts.pcr_certificates[0]]
|
||||||
|
if opts.certificate_provider:
|
||||||
|
cmd += ['--certificate-source', f'provider:{opts.certificate_provider}']
|
||||||
|
|
||||||
print('+', shell_join(cmd), file=sys.stderr)
|
print('+', shell_join(cmd), file=sys.stderr)
|
||||||
output = subprocess.check_output(cmd, text=True) # type: ignore
|
output = subprocess.check_output(cmd, text=True) # type: ignore
|
||||||
@ -848,16 +857,24 @@ def call_systemd_measure(uki: UKI, opts: UkifyConfig, profile_start: int = 0) ->
|
|||||||
*(f'--bank={bank}' for bank in banks),
|
*(f'--bank={bank}' for bank in banks),
|
||||||
]
|
]
|
||||||
|
|
||||||
for priv_key, pub_key, group in key_path_groups(opts):
|
for priv_key, pub_key, cert, group in key_path_groups(opts):
|
||||||
extra = [f'--private-key={priv_key}']
|
extra = [f'--private-key={priv_key}']
|
||||||
if opts.signing_engine is not None:
|
if opts.signing_engine is not None:
|
||||||
assert pub_key
|
assert pub_key or cert
|
||||||
extra += [f'--private-key-source=engine:{opts.signing_engine}']
|
# Backward compatibility, we used to pass the public key as the certificate
|
||||||
extra += [f'--certificate={pub_key}']
|
# as there was no --pcr-certificate= parameter
|
||||||
|
extra += [
|
||||||
|
f'--private-key-source=engine:{opts.signing_engine}',
|
||||||
|
f'--certificate={pub_key or cert}',
|
||||||
|
]
|
||||||
elif opts.signing_provider is not None:
|
elif opts.signing_provider is not None:
|
||||||
assert pub_key
|
assert pub_key or cert
|
||||||
extra += [f'--private-key-source=provider:{opts.signing_provider}']
|
extra += [
|
||||||
extra += [f'--certificate={pub_key}']
|
f'--private-key-source=provider:{opts.signing_provider}',
|
||||||
|
f'--certificate={pub_key or cert}',
|
||||||
|
]
|
||||||
|
elif cert:
|
||||||
|
extra += [f'--certificate={cert}']
|
||||||
elif pub_key:
|
elif pub_key:
|
||||||
extra += [f'--public-key={pub_key}']
|
extra += [f'--public-key={pub_key}']
|
||||||
|
|
||||||
@ -1316,6 +1333,13 @@ def make_uki(opts: UkifyConfig) -> None:
|
|||||||
pcrpkey = subprocess.check_output(cmd)
|
pcrpkey = subprocess.check_output(cmd)
|
||||||
else:
|
else:
|
||||||
pcrpkey = Path(opts.pcr_public_keys[0])
|
pcrpkey = Path(opts.pcr_public_keys[0])
|
||||||
|
elif opts.pcr_certificates and len(opts.pcr_certificates) == 1:
|
||||||
|
cmd += ['--certificate', opts.pcr_certificates[0]]
|
||||||
|
if opts.certificate_provider:
|
||||||
|
cmd += ['--certificate-source', f'provider:{opts.certificate_provider}']
|
||||||
|
|
||||||
|
print('+', shell_join(cmd), file=sys.stderr)
|
||||||
|
pcrpkey = subprocess.check_output(cmd)
|
||||||
elif opts.pcr_private_keys and len(opts.pcr_private_keys) == 1:
|
elif opts.pcr_private_keys and len(opts.pcr_private_keys) == 1:
|
||||||
cmd += ['--private-key', Path(opts.pcr_private_keys[0])]
|
cmd += ['--private-key', Path(opts.pcr_private_keys[0])]
|
||||||
|
|
||||||
@ -1594,7 +1618,7 @@ def generate_keys(opts: UkifyConfig) -> None:
|
|||||||
|
|
||||||
work = True
|
work = True
|
||||||
|
|
||||||
for priv_key, pub_key, _ in key_path_groups(opts):
|
for priv_key, pub_key, _, _ in key_path_groups(opts):
|
||||||
priv_key_pem, pub_key_pem = generate_priv_pub_key_pair()
|
priv_key_pem, pub_key_pem = generate_priv_pub_key_pair()
|
||||||
|
|
||||||
print(f'Writing private key for PCR signing to {priv_key}')
|
print(f'Writing private key for PCR signing to {priv_key}')
|
||||||
@ -2099,6 +2123,15 @@ CONFIG_ITEMS = [
|
|||||||
config_key='PCRSignature:/PCRPublicKey',
|
config_key='PCRSignature:/PCRPublicKey',
|
||||||
config_push=ConfigItem.config_set_group,
|
config_push=ConfigItem.config_set_group,
|
||||||
),
|
),
|
||||||
|
ConfigItem(
|
||||||
|
'--pcr-certificate',
|
||||||
|
dest='pcr_certificates',
|
||||||
|
metavar='PATH',
|
||||||
|
action='append',
|
||||||
|
help='certificate part of the keypair or engine/provider designation for signing PCR signatures',
|
||||||
|
config_key='PCRSignature:/PCRCertificate',
|
||||||
|
config_push=ConfigItem.config_set_group,
|
||||||
|
),
|
||||||
ConfigItem(
|
ConfigItem(
|
||||||
'--phases',
|
'--phases',
|
||||||
dest='phase_path_groups',
|
dest='phase_path_groups',
|
||||||
@ -2298,17 +2331,27 @@ def finalize_options(opts: argparse.Namespace) -> None:
|
|||||||
|
|
||||||
# Check that --pcr-public-key=, --pcr-private-key=, and --phases=
|
# Check that --pcr-public-key=, --pcr-private-key=, and --phases=
|
||||||
# have either the same number of arguments or are not specified at all.
|
# have either the same number of arguments or are not specified at all.
|
||||||
|
# Also check that --pcr-public-key= and --pcr-certificate= are not set at the same time.
|
||||||
# But allow a single public key, for offline PCR signing, to pre-populate the JSON object
|
# But allow a single public key, for offline PCR signing, to pre-populate the JSON object
|
||||||
# with the certificate's fingerprint.
|
# with the certificate's fingerprint.
|
||||||
|
n_pcr_cert = None if opts.pcr_certificates is None else len(opts.pcr_certificates)
|
||||||
n_pcr_pub = None if opts.pcr_public_keys is None else len(opts.pcr_public_keys)
|
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_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)
|
n_phase_path_groups = None if opts.phase_path_groups is None else len(opts.phase_path_groups)
|
||||||
if opts.policy_digest and n_pcr_priv is not None:
|
if opts.policy_digest and n_pcr_priv is not None:
|
||||||
raise ValueError('--pcr-private-key= cannot be specified with --policy-digest')
|
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):
|
if (
|
||||||
raise ValueError('--policy-digest requires exactly one --pcr-public-key=')
|
opts.policy_digest
|
||||||
|
and (n_pcr_pub is None or n_pcr_pub != 1)
|
||||||
|
and (n_pcr_cert is None or n_pcr_cert != 1)
|
||||||
|
):
|
||||||
|
raise ValueError('--policy-digest requires exactly one --pcr-public-key= or --pcr-certificate=')
|
||||||
if n_pcr_pub is not None and n_pcr_priv is not None and n_pcr_pub != n_pcr_priv:
|
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=')
|
raise ValueError('--pcr-public-key= specifications must match --pcr-private-key=')
|
||||||
|
if n_pcr_cert is not None and n_pcr_priv is not None and n_pcr_cert != n_pcr_priv:
|
||||||
|
raise ValueError('--pcr-certificate= specifications must match --pcr-private-key=')
|
||||||
|
if n_pcr_pub is not None and n_pcr_cert is not None:
|
||||||
|
raise ValueError('--pcr-public-key= and --pcr-certificate= cannot be used at the same time')
|
||||||
if n_phase_path_groups is not None and n_phase_path_groups != n_pcr_priv:
|
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=')
|
raise ValueError('--phases= specifications must match --pcr-private-key=')
|
||||||
|
|
||||||
@ -2405,6 +2448,7 @@ def finalize_options(opts: argparse.Namespace) -> None:
|
|||||||
or opts.devicetree_auto
|
or opts.devicetree_auto
|
||||||
or opts.pcr_private_keys
|
or opts.pcr_private_keys
|
||||||
or opts.pcr_public_keys
|
or opts.pcr_public_keys
|
||||||
|
or opts.pcr_certificates
|
||||||
):
|
):
|
||||||
raise ValueError('--pcrsig and --join-pcrsig cannot be used with other sections')
|
raise ValueError('--pcrsig and --join-pcrsig cannot be used with other sections')
|
||||||
if opts.pcrsig:
|
if opts.pcrsig:
|
||||||
|
Reference in New Issue
Block a user