1
0
mirror of https://github.com/systemd/systemd.git synced 2024-10-27 18:55:40 +03:00

Merge pull request #27946 from keszybz/ukify-genkey-verb

Add 'genkey' verb to ukify
This commit is contained in:
Zbigniew Jędrzejewski-Szmek 2023-06-14 17:57:24 +02:00 committed by GitHub
commit 2b8628c704
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 403 additions and 96 deletions

14
man/uki.conf.example Normal file
View File

@ -0,0 +1,14 @@
[UKI]
SecureBootPrivateKey=/etc/kernel/secure-boot.key.pem
SecureBootCertificate=/etc/kernel/secure-boot.cert.pem
[PCRSignature:initrd]
Phases=enter-initrd
PCRPrivateKey=/etc/kernel/pcr-initrd.key.pem
PCRPublicKey=/etc/kernel/pcr-initrd.pub.pem
[PCRSignature:system]
Phases=enter-initrd:leave-initrd enter-initrd:leave-initrd:sysinit
enter-initrd:leave-initrd:sysinit:ready
PCRPrivateKey=/etc/kernel/pcr-system.key.pem
PCRPublicKey=/etc/kernel/pcr-system.pub.pem

View File

@ -25,6 +25,7 @@
<command>/usr/lib/systemd/ukify</command>
<arg choice="opt" rep="repeat">OPTIONS</arg>
<arg choice="plain">build</arg>
<arg choice="plain">genkey</arg>
</cmdsynopsis>
</refsynopsisdiv>
@ -34,60 +35,83 @@
<para>Note: this command is experimental for now. While it is intended to become a regular component of
systemd, it might still change in behaviour and interface.</para>
<para><command>ukify</command> is a tool that combines components (usually a kernel, an initrd, and a
UEFI boot stub) to create a
<para><command>ukify</command> is a tool whose primary purpose is to combine components (usually a
kernel, an initrd, and a UEFI boot stub) to create a
<ulink url="https://uapi-group.org/specifications/specs/unified_kernel_image/">Unified Kernel Image (UKI)</ulink>
— a PE binary that can be executed by the firmware to start the embedded linux kernel.
See <citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry>
for details about the stub.</para>
</refsect1>
<para>The two primary options that should be specified for the <command>build</command> verb are
<varname>Linux=</varname>/<option>--linux=</option>, and
<varname>Initrd=</varname>/<option>--initrd=</option>. <varname>Initrd=</varname> accepts multiple
whitespace-separated paths and <option>--initrd=</option> can be specified multiple times.</para>
<refsect1>
<title>Commands</title>
<para>Additional sections will be inserted into the UKI, either automatically or only if a specific
option is provided. See the discussions of
<varname>Cmdline=</varname>/<option>--cmdline=</option>,
<varname>OSRelease=</varname>/<option>--os-release=</option>,
<varname>DeviceTree=</varname>/<option>--devicetree=</option>,
<varname>Splash=</varname>/<option>--splash=</option>,
<varname>PCRPKey=</varname>/<option>--pcrpkey=</option>,
<varname>Uname=</varname>/<option>--uname=</option>,
<varname>SBAT=</varname>/<option>--sbat=</option>,
and <option>--section=</option>
below.</para>
<para>The following commands are understood:</para>
<para><command>ukify</command> can also be used to assemble a PE binary that is not executable but
contains auxiliary data, for example additional kernel command line entries.</para>
<refsect2>
<title><command>build</command></title>
<para>If PCR signing keys are provided via the
<varname>PCRPrivateKey=</varname>/<option>--pcr-private-key=</option> and
<varname>PCRPublicKey=</varname>/<option>--pcr-public-key=</option> options, PCR values that will be seen
after booting with the given kernel, initrd, and other sections, will be calculated, signed, and embedded
in the UKI.
<citerefentry><refentrytitle>systemd-measure</refentrytitle><manvolnum>1</manvolnum></citerefentry> is
used to perform this calculation and signing.</para>
<para>This command creates a Unified Kernel Image. The two primary options that should be specified for
the <command>build</command> verb are <varname>Linux=</varname>/<option>--linux=</option>, and
<varname>Initrd=</varname>/<option>--initrd=</option>. <varname>Initrd=</varname> accepts multiple
whitespace-separated paths and <option>--initrd=</option> can be specified multiple times.</para>
<para>The calculation of PCR values is done for specific boot phase paths. Those can be specified with
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
<varname>PCRPrivateKey=</varname>/<option>--pcr-private-key=</option>,
<varname>PCRPublicKey=</varname>/<option>--pcr-public-key=</option>, and
<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
<option>--pcr-private-key=</option> are used, they must be specified the same number of times, and then
the n-th boot phase path set will be signed by the n-th key. This can be used to build different trust
policies for different phases of the boot. In the config file, <varname>PCRPrivateKey=</varname>,
<varname>PCRPublicKey=</varname>, and <varname>Phases=</varname> are grouped into separate sections,
describing separate boot phases.</para>
<para>Additional sections will be inserted into the UKI, either automatically or only if a specific
option is provided. See the discussions of
<varname>Cmdline=</varname>/<option>--cmdline=</option>,
<varname>OSRelease=</varname>/<option>--os-release=</option>,
<varname>DeviceTree=</varname>/<option>--devicetree=</option>,
<varname>Splash=</varname>/<option>--splash=</option>,
<varname>PCRPKey=</varname>/<option>--pcrpkey=</option>,
<varname>Uname=</varname>/<option>--uname=</option>,
<varname>SBAT=</varname>/<option>--sbat=</option>,
and <option>--section=</option>
below.</para>
<para>If a SecureBoot signing key is provided via the
<varname>SecureBootPrivateKey=</varname>/<option>--secureboot-private-key=</option> option, the resulting
PE binary will be signed as a whole, allowing the resulting UKI to be trusted by SecureBoot. Also see the
discussion of automatic enrollment in
<citerefentry><refentrytitle>systemd-boot</refentrytitle><manvolnum>7</manvolnum></citerefentry>.
</para>
<para><command>ukify</command> can also be used to assemble a PE binary that is not executable but
contains auxiliary data, for example additional kernel command line entries.</para>
<para>If PCR signing keys are provided via the
<varname>PCRPrivateKey=</varname>/<option>--pcr-private-key=</option> and
<varname>PCRPublicKey=</varname>/<option>--pcr-public-key=</option> options, PCR values that will be seen
after booting with the given kernel, initrd, and other sections, will be calculated, signed, and embedded
in the UKI.
<citerefentry><refentrytitle>systemd-measure</refentrytitle><manvolnum>1</manvolnum></citerefentry> is
used to perform this calculation and signing.</para>
<para>The calculation of PCR values is done for specific boot phase paths. Those can be specified with
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
<varname>PCRPrivateKey=</varname>/<option>--pcr-private-key=</option>,
<varname>PCRPublicKey=</varname>/<option>--pcr-public-key=</option>, and
<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
<option>--pcr-private-key=</option> are used, they must be specified the same number of times, and then
the n-th boot phase path set will be signed by the n-th key. This can be used to build different trust
policies for different phases of the boot. In the config file, <varname>PCRPrivateKey=</varname>,
<varname>PCRPublicKey=</varname>, and <varname>Phases=</varname> are grouped into separate sections,
describing separate boot phases.</para>
<para>If a SecureBoot signing key is provided via the
<varname>SecureBootPrivateKey=</varname>/<option>--secureboot-private-key=</option> option, the resulting
PE binary will be signed as a whole, allowing the resulting UKI to be trusted by SecureBoot. Also see the
discussion of automatic enrollment in
<citerefentry><refentrytitle>systemd-boot</refentrytitle><manvolnum>7</manvolnum></citerefentry>.
</para>
</refsect2>
<refsect2>
<title><command>genkey</command></title>
<para>This command creates the keys for PCR signing and the key and certificate used for SecureBoot
signing. The same configuration options that determine what keys and in which paths will be needed for
signing when <command>build</command> is used, here determine which keys will be created. See the
discussion of <varname>PCRPrivateKey=</varname>/<option>--pcr-private-key=</option>,
<varname>PCRPublicKey=</varname>/<option>--pcr-public-key=</option>, and
<varname>SecureBootPrivateKey=</varname>/<option>--secureboot-private-key=</option> below.</para>
<para>The output files must not exist.</para>
</refsect2>
</refsect1>
<refsect1>
@ -305,6 +329,14 @@
This option is required by <varname>SecureBootSigningTool=pesign</varname>/<option>--signtool=pesign</option>.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>SecureBootCertificateValidity=<replaceable>DAYS</replaceable></varname></term>
<term><option>--secureboot-certificate-validity=<replaceable>DAYS</replaceable></option></term>
<listitem><para>Period of validity (in days) for a certificate created by
<command>genkey</command>. Defaults to 3650, i.e. 10 years.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>SigningEngine=<replaceable>ENGINE</replaceable></varname></term>
<term><option>--signing-engine=<replaceable>ENGINE</replaceable></option></term>
@ -415,7 +447,7 @@
<example>
<title>All the bells and whistles</title>
<programlisting># /usr/lib/systemd/ukify build \
<programlisting>$ /usr/lib/systemd/ukify build \
--linux=/lib/modules/6.0.9-300.fc37.x86_64/vmlinuz \
--initrd=early_cpio \
--initrd=/some/path/initramfs-6.0.9-300.fc37.x86_64.img \
@ -472,7 +504,7 @@ Phases=enter-initrd:leave-initrd
enter-initrd:leave-initrd:sysinit
enter-initrd:leave-initrd:sysinit:ready
# /usr/lib/systemd/ukify -c ukify.conf build \
$ /usr/lib/systemd/ukify -c ukify.conf build \
--linux=/lib/modules/6.0.9-300.fc37.x86_64/vmlinuz \
--initrd=/some/path/initramfs-6.0.9-300.fc37.x86_64.img
</programlisting>
@ -498,6 +530,36 @@ Phases=enter-initrd:leave-initrd
<para>This creates a signed PE binary that contains the additional kernel command line parameter
<literal>debug</literal> with SBAT metadata referring to the owner of the addon.</para>
</example>
<example>
<title>Decide signing policy and create certificate and keys</title>
<para>First, let's create an config file that specifies what signatures shall be made:</para>
<programlisting># cat >/etc/kernel/uki.conf &lt;&lt;EOF
<xi:include href="uki.conf.example" parse="text" />EOF</programlisting>
<para>Next, we can generate the certificate and keys:</para>
<programlisting># /usr/lib/systemd/ukify genkey --config=/etc/kernel/uki.conf
Writing SecureBoot private key to /etc/kernel/secure-boot.key.pem
Writing SecureBoot certicate to /etc/kernel/secure-boot.cert.pem
Writing private key for PCR signing to /etc/kernel/pcr-initrd.key.pem
Writing public key for PCR signing to /etc/kernel/pcr-initrd.pub.pem
Writing private key for PCR signing to /etc/kernel/pcr-system.key.pem
Writing public key for PCR signing to /etc/kernel/pcr-system.pub.pem
</programlisting>
<para>(Both operations need to be done as root to allow write access
to <filename>/etc/kernel/</filename>.)</para>
<para>Subsequent invocations of using the config file
(<command>/usr/lib/systemd/ukify build --config=/etc/kernel/uki.conf</command>)
will use this certificate and key files. Note that the
<citerefentry><refentrytitle>kernel-install</refentrytitle><manvolnum>8</manvolnum></citerefentry>
plugin <filename>60-ukify.install</filename> uses <filename>/etc/kernel/uki.conf</filename>
by default, so after this file has been created, installations of kernels that create a UKI on the
local machine using <command>kernel-install</command> would perform signing using this config.</para>
</example>
</refsect1>
<refsect1>

View File

@ -186,7 +186,7 @@ def call_ukify(opts):
# Create "empty" namespace. We want to override just a few settings, so it
# doesn't make sense to configure everything. We pretend to parse an empty
# argument set to prepopulate the namespace with the defaults.
opts2 = ukify['create_parser']().parse_args(())
opts2 = ukify['create_parser']().parse_args(['build'])
opts2.config = config_file_location()
opts2.uname = opts.kernel_version

View File

@ -4,6 +4,7 @@
# pylint: disable=missing-docstring,redefined-outer-name,invalid-name
# pylint: disable=unused-import,import-outside-toplevel,useless-else-on-loop
# pylint: disable=consider-using-with,wrong-import-position,unspecified-encoding
# pylint: disable=protected-access
import base64
import json
@ -87,7 +88,7 @@ def test_apply_config(tmp_path):
Phases = {':'.join(ukify.KNOWN_PHASES)}
'''))
ns = ukify.create_parser().parse_args(())
ns = ukify.create_parser().parse_args(['build'])
ns.linux = None
ns.initrd = []
ukify.apply_config(ns, config)
@ -106,7 +107,7 @@ def test_apply_config(tmp_path):
assert ns.signing_engine == 'engine1'
assert ns.sb_key == 'some/path5'
assert ns.sb_cert == 'some/path6'
assert ns.sign_kernel == False
assert ns.sign_kernel is False
assert ns._groups == ['NAME']
assert ns.pcr_private_keys == [pathlib.Path('some/path7')]
@ -129,7 +130,7 @@ def test_apply_config(tmp_path):
assert ns.signing_engine == 'engine1'
assert ns.sb_key == 'some/path5'
assert ns.sb_cert == 'some/path6'
assert ns.sign_kernel == False
assert ns.sign_kernel is False
assert ns._groups == ['NAME']
assert ns.pcr_private_keys == [pathlib.Path('some/path7')]
@ -447,7 +448,7 @@ def test_sections(kernel_initrd, tmpdir):
for sect in 'text osrel cmdline linux initrd uname test'.split():
assert re.search(fr'^\s*\d+\s+.{sect}\s+0', dump, re.MULTILINE)
def test_addon(kernel_initrd, tmpdir):
def test_addon(tmpdir):
output = f'{tmpdir}/addon.efi'
args = [
'build',
@ -459,7 +460,7 @@ def test_addon(kernel_initrd, tmpdir):
args += [f'--stub={stub}']
expected_exceptions = ()
else:
expected_exceptions = FileNotFoundError,
expected_exceptions = (FileNotFoundError,)
opts = ukify.parse_args(args)
try:
@ -588,7 +589,7 @@ def test_pcr_signing(kernel_initrd, tmpdir):
'--uname=1.2.3',
'--cmdline=ARG1 ARG2 ARG3',
'--os-release=ID=foobar\n',
'--pcr-banks=sha1', # use sha1 as that is most likely to be supported
'--pcr-banks=sha1', # use sha1 because it doesn't really matter
f'--pcrpkey={pub.name}',
f'--pcr-public-key={pub.name}',
f'--pcr-private-key={priv.name}',
@ -655,7 +656,7 @@ def test_pcr_signing2(kernel_initrd, tmpdir):
'--uname=1.2.3',
'--cmdline=ARG1 ARG2 ARG3',
'--os-release=ID=foobar\n',
'--pcr-banks=sha1', # use sha1 as that is most likely to be supported
'--pcr-banks=sha1',
f'--pcrpkey={pub2.name}',
f'--pcr-public-key={pub.name}',
f'--pcr-private-key={priv.name}',
@ -698,5 +699,60 @@ def test_pcr_signing2(kernel_initrd, tmpdir):
assert list(sig.keys()) == ['sha1']
assert len(sig['sha1']) == 6 # six items for six phases paths
def test_key_cert_generation(tmpdir):
opts = ukify.parse_args([
'genkey',
f"--pcr-public-key={tmpdir / 'pcr1.pub.pem'}",
f"--pcr-private-key={tmpdir / 'pcr1.priv.pem'}",
'--phases=enter-initrd enter-initrd:leave-initrd',
f"--pcr-public-key={tmpdir / 'pcr2.pub.pem'}",
f"--pcr-private-key={tmpdir / 'pcr2.priv.pem'}",
'--phases=sysinit ready',
f"--secureboot-private-key={tmpdir / 'sb.priv.pem'}",
f"--secureboot-certificate={tmpdir / 'sb.cert.pem'}",
])
assert opts.verb == 'genkey'
ukify.check_cert_and_keys_nonexistent(opts)
pytest.importorskip('cryptography')
ukify.generate_keys(opts)
if not shutil.which('openssl'):
return
for key in (tmpdir / 'pcr1.priv.pem',
tmpdir / 'pcr2.priv.pem',
tmpdir / 'sb.priv.pem'):
out = subprocess.check_output([
'openssl', 'rsa',
'-in', key,
'-text',
'-noout',
], text = True)
assert 'Private-Key' in out
assert '2048 bit' in out
for pub in (tmpdir / 'pcr1.pub.pem',
tmpdir / 'pcr2.pub.pem'):
out = subprocess.check_output([
'openssl', 'rsa',
'-pubin',
'-in', pub,
'-text',
'-noout',
], text = True)
assert 'Public-Key' in out
assert '2048 bit' in out
out = subprocess.check_output([
'openssl', 'x509',
'-in', tmpdir / 'sb.cert.pem',
'-text',
'-noout',
], text = True)
assert 'Certificate' in out
assert 'Issuer: CN = SecureBoot signing key on host' in out
if __name__ == '__main__':
sys.exit(pytest.main(sys.argv))

View File

@ -25,17 +25,21 @@
import argparse
import configparser
import contextlib
import collections
import dataclasses
import datetime
import fnmatch
import itertools
import json
import os
import pathlib
import pprint
import pydoc
import re
import shlex
import shutil
import socket
import subprocess
import sys
import tempfile
@ -43,6 +47,7 @@ from typing import (Any,
Callable,
IO,
Optional,
Sequence,
Union)
import pefile # type: ignore
@ -88,6 +93,15 @@ def guess_efi_arch():
return efi_arch
def page(text: str, enabled: Optional[bool]) -> None:
if enabled:
# Initialize less options from $SYSTEMD_LESS or provide a suitable fallback.
os.environ['LESS'] = os.getenv('SYSTEMD_LESS', 'FRSXMK')
pydoc.pager(text)
else:
print(text)
def shell_join(cmd):
# TODO: drop in favour of shlex.join once shlex.join supports pathlib.Path.
return ' '.join(shlex.quote(str(x)) for x in cmd)
@ -345,6 +359,17 @@ def check_inputs(opts):
check_splash(opts.splash)
def check_cert_and_keys_nonexistent(opts):
# Raise if any of the keys and certs are found on disk
paths = itertools.chain(
(opts.sb_key, opts.sb_cert),
*((priv_key, pub_key)
for priv_key, pub_key, _ in key_path_groups(opts)))
for path in paths:
if path and path.exists():
raise ValueError(f'{path} is present')
def find_tool(name, fallback=None, opts=None):
if opts and opts.tools:
for d in opts.tools:
@ -370,6 +395,19 @@ def combine_signatures(pcrsigs):
return json.dumps(combined)
def key_path_groups(opts):
if not opts.pcr_private_keys:
return
n_priv = len(opts.pcr_private_keys)
pub_keys = opts.pcr_public_keys or [None] * n_priv
pp_groups = opts.phase_path_groups or [None] * n_priv
yield from zip(opts.pcr_private_keys,
pub_keys,
pp_groups)
def call_systemd_measure(uki, linux, opts):
measure_tool = find_tool('systemd-measure',
'/usr/lib/systemd/systemd-measure',
@ -403,10 +441,6 @@ def call_systemd_measure(uki, linux, opts):
# PCR signing
if opts.pcr_private_keys:
n_priv = len(opts.pcr_private_keys or ())
pp_groups = opts.phase_path_groups or [None] * n_priv
pub_keys = opts.pcr_public_keys or [None] * n_priv
pcrsigs = []
cmd = [
@ -420,9 +454,7 @@ def call_systemd_measure(uki, linux, opts):
for bank in banks),
]
for priv_key, pub_key, group in zip(opts.pcr_private_keys,
pub_keys,
pp_groups):
for priv_key, pub_key, group in key_path_groups(opts):
extra = [f'--private-key={priv_key}']
if pub_key:
extra += [f'--public-key={pub_key}']
@ -711,6 +743,119 @@ def make_uki(opts):
print(f"Wrote {'signed' if sign_args_present else 'unsigned'} {opts.output}")
ONE_DAY = datetime.timedelta(1, 0, 0)
@contextlib.contextmanager
def temporary_umask(mask: int):
# Drop <mask> bits from umask
old = os.umask(0)
os.umask(old | mask)
try:
yield
finally:
os.umask(old)
def generate_key_cert_pair(
common_name: str,
valid_days: int,
keylength: int = 2048,
) -> tuple[bytes]:
from cryptography import x509
import cryptography.hazmat.primitives as hp
# We use a keylength of 2048 bits. That is what Microsoft documents as
# supported/expected:
# https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/windows-secure-boot-key-creation-and-management-guidance?view=windows-11#12-public-key-cryptography
now = datetime.datetime.utcnow()
key = hp.asymmetric.rsa.generate_private_key(
public_exponent=65537,
key_size=keylength,
)
cert = x509.CertificateBuilder(
).subject_name(
x509.Name([x509.NameAttribute(x509.oid.NameOID.COMMON_NAME, common_name)])
).issuer_name(
x509.Name([x509.NameAttribute(x509.oid.NameOID.COMMON_NAME, common_name)])
).not_valid_before(
now,
).not_valid_after(
now + ONE_DAY * valid_days
).serial_number(
x509.random_serial_number()
).public_key(
key.public_key()
).add_extension(
x509.BasicConstraints(ca=False, path_length=None),
critical=True,
).sign(
private_key=key,
algorithm=hp.hashes.SHA256(),
)
cert_pem = cert.public_bytes(
encoding=hp.serialization.Encoding.PEM,
)
key_pem = key.private_bytes(
encoding=hp.serialization.Encoding.PEM,
format=hp.serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=hp.serialization.NoEncryption(),
)
return key_pem, cert_pem
def generate_priv_pub_key_pair(keylength : int = 2048) -> tuple[bytes]:
import cryptography.hazmat.primitives as hp
key = hp.asymmetric.rsa.generate_private_key(
public_exponent=65537,
key_size=keylength,
)
priv_key_pem = key.private_bytes(
encoding=hp.serialization.Encoding.PEM,
format=hp.serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=hp.serialization.NoEncryption(),
)
pub_key_pem = key.public_key().public_bytes(
encoding=hp.serialization.Encoding.PEM,
format=hp.serialization.PublicFormat.SubjectPublicKeyInfo,
)
return priv_key_pem, pub_key_pem
def generate_keys(opts):
# This will generate keys and certificates and write them to the paths that
# are specified as input paths.
if opts.sb_key or opts.sb_cert:
fqdn = socket.getfqdn()
cn = f'SecureBoot signing key on host {fqdn}'
key_pem, cert_pem = generate_key_cert_pair(
common_name=cn,
valid_days=opts.sb_cert_validity,
)
print(f'Writing SecureBoot private key to {opts.sb_key}')
with temporary_umask(0o077):
opts.sb_key.write_bytes(key_pem)
print(f'Writing SecureBoot certicate to {opts.sb_cert}')
opts.sb_cert.write_bytes(cert_pem)
for priv_key, pub_key, _ in key_path_groups(opts):
priv_key_pem, pub_key_pem = generate_priv_pub_key_pair()
print(f'Writing private key for PCR signing to {priv_key}')
with temporary_umask(0o077):
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)
@dataclasses.dataclass(frozen=True)
class ConfigItem:
@staticmethod
@ -843,7 +988,7 @@ class ConfigItem:
return (section_name, key, value)
VERBS = ('build',)
VERBS = ('build', 'genkey')
CONFIG_ITEMS = [
ConfigItem(
@ -1011,6 +1156,14 @@ uki.addon,1,UKI Addon,uki.addon,1,https://www.freedesktop.org/software/systemd/m
help = 'required by --signtool=pesign. pesign needs a certificate nickname of nss certificate database entry to use for PE signing',
config_key = 'UKI/SecureBootCertificateName',
),
ConfigItem(
'--secureboot-certificate-validity',
metavar = 'DAYS',
dest = 'sb_cert_validity',
default = 365 * 10,
help = "period of validity (in days) for a certificate created by 'genkey'",
config_key = 'UKI/SecureBootCertificateValidity',
),
ConfigItem(
'--sign-kernel',
@ -1128,12 +1281,25 @@ def config_example():
yield f'{key} = {value}'
class PagerHelpAction(argparse._HelpAction): # pylint: disable=protected-access
def __call__(
self,
parser: argparse.ArgumentParser,
namespace: argparse.Namespace,
values: Union[str, Sequence[Any], None] = None,
option_string: Optional[str] = None
) -> None:
page(parser.format_help(), True)
parser.exit()
def create_parser():
p = argparse.ArgumentParser(
description='Build and sign Unified Kernel Images',
allow_abbrev=False,
add_help=False,
usage='''\
ukify [options] [LINUX INITRD]
ukify [options] VERB
''',
epilog='\n '.join(('config file:', *config_example())),
formatter_class=argparse.RawDescriptionHelpFormatter,
@ -1145,10 +1311,42 @@ ukify [options…] [LINUX INITRD…]
# Suppress printing of usage synopsis on errors
p.error = lambda message: p.exit(2, f'{p.prog}: error: {message}\n')
# Make --help paged
p.add_argument(
'-h', '--help',
action=PagerHelpAction,
help='show this help message and exit',
)
return p
def finalize_options(opts):
# Figure out which syntax is being used, one of:
# ukify verb --arg --arg --arg
# ukify linux initrd…
if len(opts.positional) == 1 and opts.positional[0] in VERBS:
opts.verb = opts.positional[0]
elif opts.linux or opts.initrd:
raise ValueError('--linux/--initrd options cannot be used with positional arguments')
else:
print("Assuming obsolete commandline syntax with no verb. Please use 'build'.")
if opts.positional:
opts.linux = pathlib.Path(opts.positional[0])
# If we have initrds from parsing config files, append our positional args at the end
opts.initrd = (opts.initrd or []) + [pathlib.Path(arg) for arg in opts.positional[1:]]
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.
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:
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=')
if opts.cmdline and opts.cmdline.startswith('@'):
opts.cmdline = pathlib.Path(opts.cmdline[1:])
elif opts.cmdline:
@ -1190,7 +1388,7 @@ def finalize_options(opts):
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')
if opts.output is None:
if opts.verb == 'build' and opts.output is None:
if opts.linux is None:
raise ValueError('--output= must be specified when building a PE addon')
suffix = '.efi' if opts.sb_key or opts.sb_cert_name else '.unsigned.efi'
@ -1206,45 +1404,22 @@ def finalize_options(opts):
def parse_args(args=None):
p = create_parser()
opts = p.parse_args(args)
# Figure out which syntax is being used, one of:
# ukify verb --arg --arg --arg
# ukify linux initrd…
if len(opts.positional) == 1 and opts.positional[0] in VERBS:
opts.verb = opts.positional[0]
elif opts.linux or opts.initrd:
raise ValueError('--linux/--initrd options cannot be used with positional arguments')
else:
print("Assuming obsolete commandline syntax with no verb. Please use 'build'.")
if opts.positional:
opts.linux = pathlib.Path(opts.positional[0])
opts.initrd = [pathlib.Path(arg) for arg in opts.positional[1:]]
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.
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:
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=')
opts = create_parser().parse_args(args)
apply_config(opts)
finalize_options(opts)
return opts
def main():
opts = parse_args()
check_inputs(opts)
assert opts.verb == 'build'
make_uki(opts)
if opts.verb == 'build':
check_inputs(opts)
make_uki(opts)
elif opts.verb == 'genkey':
check_cert_and_keys_nonexistent(opts)
generate_keys(opts)
else:
assert False
if __name__ == '__main__':