diff --git a/man/uki.conf.example b/man/uki.conf.example
new file mode 100644
index 00000000000..84a9f77b8d7
--- /dev/null
+++ b/man/uki.conf.example
@@ -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
diff --git a/man/ukify.xml b/man/ukify.xml
index 098dacfb99f..06ae550530d 100644
--- a/man/ukify.xml
+++ b/man/ukify.xml
@@ -25,6 +25,7 @@
/usr/lib/systemd/ukifyOPTIONSbuild
+ genkey
@@ -34,60 +35,83 @@
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.
- ukify is a tool that combines components (usually a kernel, an initrd, and a
- UEFI boot stub) to create a
+ ukify is a tool whose primary purpose is to combine components (usually a
+ kernel, an initrd, and a UEFI boot stub) to create a
Unified Kernel Image (UKI)
— a PE binary that can be executed by the firmware to start the embedded linux kernel.
See systemd-stub7
for details about the stub.
+
- The two primary options that should be specified for the build verb are
- Linux=/, and
- Initrd=/. Initrd= accepts multiple
- whitespace-separated paths and can be specified multiple times.
+
+ Commands
- Additional sections will be inserted into the UKI, either automatically or only if a specific
- option is provided. See the discussions of
- Cmdline=/,
- OSRelease=/,
- DeviceTree=/,
- Splash=/,
- PCRPKey=/,
- Uname=/,
- SBAT=/,
- and
- below.
+ The following commands are understood:
- ukify can also be used to assemble a PE binary that is not executable but
- contains auxiliary data, for example additional kernel command line entries.
+
+ build
- If PCR signing keys are provided via the
- PCRPrivateKey=/ and
- PCRPublicKey=/ 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.
- systemd-measure1 is
- used to perform this calculation and signing.
+ This command creates a Unified Kernel Image. The two primary options that should be specified for
+ the build verb are Linux=/, and
+ Initrd=/. Initrd= accepts multiple
+ whitespace-separated paths and can be specified multiple times.
- The calculation of PCR values is done for specific boot phase paths. Those can be specified with
- the Phases=/ option. If not specified, the default provided
- by systemd-measure is used. It is also possible to specify the
- PCRPrivateKey=/,
- PCRPublicKey=/, and
- Phases=/ arguments more than once. Signatures will then be
- performed with each of the specified keys. On the command line, when both and
- 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, PCRPrivateKey=,
- PCRPublicKey=, and Phases= are grouped into separate sections,
- describing separate boot phases.
+ Additional sections will be inserted into the UKI, either automatically or only if a specific
+ option is provided. See the discussions of
+ Cmdline=/,
+ OSRelease=/,
+ DeviceTree=/,
+ Splash=/,
+ PCRPKey=/,
+ Uname=/,
+ SBAT=/,
+ and
+ below.
- If a SecureBoot signing key is provided via the
- SecureBootPrivateKey=/ 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
- systemd-boot7.
-
+ ukify can also be used to assemble a PE binary that is not executable but
+ contains auxiliary data, for example additional kernel command line entries.
+
+ If PCR signing keys are provided via the
+ PCRPrivateKey=/ and
+ PCRPublicKey=/ 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.
+ systemd-measure1 is
+ used to perform this calculation and signing.
+
+ The calculation of PCR values is done for specific boot phase paths. Those can be specified with
+ the Phases=/ option. If not specified, the default provided
+ by systemd-measure is used. It is also possible to specify the
+ PCRPrivateKey=/,
+ PCRPublicKey=/, and
+ Phases=/ arguments more than once. Signatures will then be
+ performed with each of the specified keys. On the command line, when both and
+ 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, PCRPrivateKey=,
+ PCRPublicKey=, and Phases= are grouped into separate sections,
+ describing separate boot phases.
+
+ If a SecureBoot signing key is provided via the
+ SecureBootPrivateKey=/ 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
+ systemd-boot7.
+
+
+
+
+ genkey
+
+ 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 build is used, here determine which keys will be created. See the
+ discussion of PCRPrivateKey=/,
+ PCRPublicKey=/, and
+ SecureBootPrivateKey=/ below.
+
+ The output files must not exist.
+
@@ -305,6 +329,14 @@
This option is required by SecureBootSigningTool=pesign/.
+
+ SecureBootCertificateValidity=DAYS
+
+
+ Period of validity (in days) for a certificate created by
+ genkey. Defaults to 3650, i.e. 10 years.
+
+
SigningEngine=ENGINE
@@ -415,7 +447,7 @@
All the bells and whistles
- # /usr/lib/systemd/ukify build \
+ $ /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
@@ -498,6 +530,36 @@ Phases=enter-initrd:leave-initrd
This creates a signed PE binary that contains the additional kernel command line parameter
debug with SBAT metadata referring to the owner of the addon.
+
+
+ Decide signing policy and create certificate and keys
+
+ First, let's create an config file that specifies what signatures shall be made:
+
+ # cat >/etc/kernel/uki.conf <<EOF
+EOF
+
+ Next, we can generate the certificate and keys:
+ # /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
+
+
+ (Both operations need to be done as root to allow write access
+ to /etc/kernel/.)
+
+ Subsequent invocations of using the config file
+ (/usr/lib/systemd/ukify build --config=/etc/kernel/uki.conf)
+ will use this certificate and key files. Note that the
+ kernel-install8
+ plugin 60-ukify.install uses /etc/kernel/uki.conf
+ by default, so after this file has been created, installations of kernels that create a UKI on the
+ local machine using kernel-install would perform signing using this config.
+
diff --git a/src/kernel-install/60-ukify.install.in b/src/kernel-install/60-ukify.install.in
index 0927bd7a2e8..96ca2482b06 100755
--- a/src/kernel-install/60-ukify.install.in
+++ b/src/kernel-install/60-ukify.install.in
@@ -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
diff --git a/src/ukify/test/test_ukify.py b/src/ukify/test/test_ukify.py
index eae82c7f88f..a6778bb694d 100755
--- a/src/ukify/test/test_ukify.py
+++ b/src/ukify/test/test_ukify.py
@@ -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))
diff --git a/src/ukify/ukify.py b/src/ukify/ukify.py
index a9c21601df9..3db2bac384a 100755
--- a/src/ukify/ukify.py
+++ b/src/ukify/ukify.py
@@ -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 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__':