The idea is that genkey is called with either
--secureboot-private-key= + --secureboot-certificate=, and then it
writes those, or with --pcr-private-key + optionally --pcr-public-key
and then it writes those, or both. But when called with no arguments
whatsover, it did nothing.
There is no implicit value for any of those parameters as input (unlike in
mkosi), so we also don't want to have implicit values when used as output.
But we shouldn't return success if no work was done, this is quite confusing.
Before:
$ python src/ukify/ukify.py genkey --secureboot-private-key=sb2.key --secureboot-certificate=sb2.cert --secureboot-certificate-validity=111
Traceback (most recent call last):
File "/home/zbyszek/src/systemd-work/src/ukify/ukify.py", line 1660, in <module>
main()
File "/home/zbyszek/src/systemd-work/src/ukify/ukify.py", line 1652, in main
generate_keys(opts)
File "/home/zbyszek/src/systemd-work/src/ukify/ukify.py", line 943, in generate_keys
key_pem, cert_pem = generate_key_cert_pair(
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/zbyszek/src/systemd-work/src/ukify/ukify.py", line 891, in generate_key_cert_pair
now + ONE_DAY * valid_days
~~~~~~~~^~~~~~~~~~~~
TypeError: can't multiply sequence by non-int of type 'datetime.timedelta'
Now:
$ python src/ukify/ukify.py genkey --secureboot-private-key=sb2.key --secureboot-certificate=sb2.cert --secureboot-certificate-validity=111
Writing SecureBoot private key to sb2.key
Writing SecureBoot certificate to sb2.cert
The new code is also clearer.
DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for
removal in a future version. Use timezone-aware objects to
represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
The difference between the two is that .now(datetime.UTC) returns an object with
a timezone attached, "the numbers" are the same.
>>> datetime.datetime.utcnow(), datetime.datetime.now(datetime.UTC)
(datetime.datetime(2023, 12, 1, 9, 37, 53, 891669),
datetime.datetime(2023, 12, 1, 9, 37, 53, 891688, tzinfo=datetime.timezone.utc))
This value is fed to cryptography's x509.CertificateBuilder object, so as long
as it can accept a datetime object with tzinfo, the result should be identical.
Fixup for e87dec82be:
I misunderstood the format. It's actually CBOR, i.e. some binary format.
When trying to show show text we would first check if it's valid UTF-8,
so we would handle this gracefully, i.e. emit a warning and not print
the contents.
There are draft proposals to embed SBOM metadata in the .sbom section of PE
binaries [1], in the coSWID XML format. Some details of how this is actually
implemented might change, but it seems very likely that both section name and
it being text will stay. Let's show the section as text to make such binaries
easier to inspect. ([1] recommends using 'objcopy -j .sbom' which isn't
particularly readable.) Once there's more standarization of the actual
format, we can add pretty-printing and/or syntax highlighting.
[1] https://uefi.org/blog/firmware-sbom-proposal
[2] https://www.ietf.org/archive/id/draft-ietf-sacm-coswid-21.html
The details of formatting are copied from mkosi. This results in the following:
usage:
ukify build [--linux=LINUX] [--initrd=INITRD] [options…]
ukify genkey [options…]
ukify inspect FILE… [options…]
Build and sign Unified Kernel Images
options:
--version show program's version number and exit
...
I put "[options…]" at the end, because that's what one would generally do
with long options like "--cmdline" and others.
If an option like SecureBootCertificateDir is given, it should override
the default '/etc/pki/pesign'. Until now the config file option were
always ignored if they had a default.
So from now on, every ConfigItem with a config_key and default field
should also give config_push = ConfigItem.config_set.
--signtool is actually useless: it can be inferred depending on if
--secureboot-certificate-name (pesign) is given, or
--secureboot-private-key and --secureboot-certificate (sbsign) is given.
Leave the option just for backwards compatibility.
If the user does not specify a config file to use, ukify will try looking for one at {/run,/etc,/usr/local/lib,/usr/lib}/systemd/ukify.conf in order and then use the first one found. Also made sure the --config input is a pathlib.Path by specifying its type in its CONFIG_ITEMS entry.
Big cheers to Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> for helping!
ignore_padding= was only added in a recent version of pefile. Let's
set length= to the virtual size instead which is what ignore_padding
does behind the scenes so we're compatible with older versions of
pefile.
Handle the case when all the arguments are passed in through a
configuration file:
$ cat ukify.conf
[UKI]
Linux = /boot/vmlinuz-linux
Initrd = /boot/initramfs-linux.img
Before:
$ src/ukify/ukify.py --config ukify.conf build
Traceback (most recent call last):
File "/root/systemd/src/ukify/ukify.py", line 1604, in <module>
main()
File "/root/systemd/src/ukify/ukify.py", line 1590, in main
opts = parse_args()
^^^^^^^^^^^^
File "/root/systemd/src/ukify/ukify.py", line 1584, in parse_args
apply_config(opts)
File "/root/systemd/src/ukify/ukify.py", line 1431, in apply_config
item.apply_config(namespace, section_name, group, key, value)
File "/root/systemd/src/ukify/ukify.py", line 1123, in apply_config
self.config_push(namespace, group, dest, value)
File "/root/systemd/src/ukify/ukify.py", line 1019, in config_list_prepend
setattr(namespace, dest, value + old)
~~~~~~^~~~~
TypeError: can only concatenate list (not "NoneType") to list
After:
$ src/ukify/ukify.py --config ukify.conf build
Kernel version not specified, starting autodetection 😖.
Found uname version: 6.4.7-arch1-3
Wrote unsigned vmlinuz-linux.unsigned.efi
Resolves: #28688
We were running systemd-measure before adding the sbat section,
let's fix that.
Also make sure we only pass --linux to systemd-measure once instead
of twice.
$ uname -m
armv7l
Traceback (most recent call last):
File "/<<PKGBUILDDIR>>/obj-arm-linux-gnueabihf/src/kernel-install/60-ukify.install", line 223, in <module>
main()
File "/<<PKGBUILDDIR>>/obj-arm-linux-gnueabihf/src/kernel-install/60-ukify.install", line 219, in main
call_ukify(opts)
File "/<<PKGBUILDDIR>>/obj-arm-linux-gnueabihf/src/kernel-install/60-ukify.install", line 205, in call_ukify
ukify['finalize_options'](opts2)
File "/<<PKGBUILDDIR>>/obj-arm-linux-gnueabihf/ukify", line 1423, in finalize_options
opts.efi_arch = guess_efi_arch()
^^^^^^^^^^^^^^^^
File "/<<PKGBUILDDIR>>/obj-arm-linux-gnueabihf/ukify", line 78, in guess_efi_arch
raise ValueError(f'Unsupported architecture {arch}')
ValueError: Unsupported architecture armv7l
Since PE can't be read with a normal editor, provide functionality to read PE
sections and output their name and when possible the content too.
ukify inspect <file> describes the sections and print the content of the
well-known sections to stdout.
Co-authored-by: Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
For confidential computing they want to be able to revoke initrds too, so allow
passing a specific --sbat section when building a UKI too, not just an addon.
Merge it with the stub and kernel sections.
If the kernel contains a .sbat section (they should start soon) then merge
it with the stub's so that revocations can apply to either component.
Fixes https://github.com/systemd/systemd/issues/27866
The idea is to make it easy to generate all the signing key and certs
that can be used for local signing. The verb is the modeled after
'mkosi genkey', but there are some important differences: we generate
the keys to the paths where they will be read from, both pcr signing
keys and the SecureBoot certificate+key.
If any of the outputs exist, operation is refused. Maybe we could add a
--force option in the future, but this operation should be rare, so I think
it's better to refuse to overwrite anything initially.
I'm only doing a token man page change here.
https://github.com/systemd/systemd/pull/27621 reworks the man page,
and the changes done here would conflict heavily with that work. I'll
submit a follow-up patch later.
This simplifies the logic: finalize_options() is the step that does the
checks and the mangling. The checks for consistency are done in more places,
so we need to pass a verb (we only have 'build', but once we add other verbs,
any would do).
The output is now too long to fit on one page, let's use a pager automatically
like in other places.
The implementation is copied from mkosi, but adjusted to follow what other
systemd tools do.
The old syntax with linux + initrds as positional arguments is still accepted,
but a warning is emitted. We should remove the support for this after the
next release or so.
Adding a single verb by itself is not very useful, but opens the door to adding
other verbs.
The linter is imperfect, but it is useful as a very quick
check for typos and other silly mistakes. Add a few annotations
and do one small change to make it think the code is perfect.
The .linux section would contain the path to the signed kernel (instead of the signed kernel itself), since the python type of the variable is used to determine how it is handled when adding the pe sections.
Co-authored-by: Otto Bittner <cobittner@posteo.net>
In order to ensure addons can always be revoked via SBAT, and it is not
left out by mistake, have a default metadata entry if none is specified
by the caller.
https://github.com/rhboot/shim/blob/main/SBAT.md
sbsign is not available everywhere, for example RHEL does not have it.
Add pesign as alternative to it.
pesign will use options "--secureboot-certificate-name" (mandatory) and
"--secureboot-certificate-dir" (optional), while sbsign will use
"--secureboot-private-key" and "--secureboot-certificate".
By default, use sbsign. If no key/cert is provided or sbsign is not found,
try pesign.
Signed-off-by: Emanuele Giuseppe Esposito <eesposit@redhat.com>
If a package is missing, a subprocess is started with None as
command argument. Error raised by subprocess is therefore not helpful
at all to understand what needs to be done to fix that error.
Also fix doc since systemd-stub will look for .cmdline files, and not
.cmdline.efi files.
Signed-off-by: Emanuele Giuseppe Esposito <eesposit@redhat.com>
Note to self: PEP 585 introduced using collection types as types,
and is available since 3.9. PEP 604 allows writing unions with "|",
but is only available since 3.10, so not yet here because we maintain
compat with 3.9.
In some ways this is similar to mkosi: we have a argparse.ArgumentParser()
with a bunch of options, and a configparser.ConfigParser() with an
overlapping set of options. Many options are settable in both places, but
not all. In mkosi, we define this in three places (a dataclass, and a
function for argparse, and a function for configparser). Here, we have one
huge list of ConfigItem instances. Each instance specifies the full metadata
for both parsers. Argparse generates a --help string for all the options,
and we also append a config file sample to --help based on the ConfigItem
data:
$ python src/ukify/ukify.py --help|tail -n 25
config file:
[UKI]
Linux = LINUX
Initrd = INITRD…
Cmdline = TEXT|@PATH
OSRelease = TEXT|@PATH
DeviceTree = PATH
Splash = BMP
PCRPKey = KEY
Uname = VERSION
EFIArch = ia32|x64|arm|aa64|riscv64
Stub = STUB
PCRBanks = BANK…
SigningEngine = ENGINE
SecureBootPrivateKey = SB_KEY
SecureBootCertificate = SB_CERT
SignKernel = SIGN_KERNEL
[PCRSignature:NAME]
PCRPrivateKey = PATH
PCRPublicKey = PATH
Phases = PHASE-PATH…
While writing this I needed to check the argument parsing, so I added
a --summary switch. It just pretty-prints the resulting option dictionary:
$ python src/ukify/ukify.py /efi//3a9d668b4db749398a4a5e78a03bffa5/6.2.11-300.fc38.x86_64/linux /efi//3a9d668b4db749398a4a5e78a03bffa5/6.2.11-300.fc38.x86_64/initrd --pcr-private-key=PRIV.key --pcr-public-key=PUB.key --config=man/ukify-example.conf --summary
Host arch 'x86_64', EFI arch 'x64'
{'_groups': [0, 'initrd', 'system'],
'cmdline': 'A1 B2 C3',
'config': 'man/ukify-example.conf',
'devicetree': None,
'efi_arch': 'x64',
'initrd': [PosixPath('initrd1'),
PosixPath('initrd2'),
PosixPath('initrd3'),
PosixPath('/efi/3a9d668b4db749398a4a5e78a03bffa5/6.2.11-300.fc38.x86_64/initrd')],
'linux': PosixPath('/efi/3a9d668b4db749398a4a5e78a03bffa5/6.2.11-300.fc38.x86_64/linux'),
'measure': None,
'os_release': PosixPath('/etc/os-release'),
'output': 'linux.efi',
'pcr_banks': ['sha1', 'sha384'],
'pcr_private_keys': [PosixPath('PRIV.key'),
PosixPath('pcr-private-initrd-key.pem'),
PosixPath('pcr-private-system-key.pem')],
'pcr_public_keys': [PosixPath('PUB.key'),
PosixPath('pcr-public-initrd-key.pem'),
PosixPath('pcr-public-system-key.pem')],
'pcrpkey': None,
'phase_path_groups': [None,
['enter-initrd'],
['enter-initrd:leave-initrd',
'enter-initrd:leave-initrd:sysinit',
'enter-initrd:leave-initrd:sysinit:ready']],
'sb_cert': PosixPath('mkosi.secure-boot.crt'),
'sb_key': PosixPath('mkosi.secure-boot.key'),
'sections': [],
'sign_kernel': None,
'signing_engine': None,
'splash': None,
'stub': PosixPath('/usr/lib/systemd/boot/efi/linuxx64.efi.stub'),
'summary': True,
'tools': None,
'uname': None}
With --summary, existence of input paths is not checked. I think we'll
want to show them, instead of throwing an error, but in red, similarly to
'bootctl list'.
This also fixes tests which were failing with e.g.
E FileNotFoundError: [Errno 2] No such file or directory: '/ARG1'
=========================== short test summary info ============================
FAILED ../src/ukify/test/test_ukify.py::test_parse_args_minimal - FileNotFoun...
FAILED ../src/ukify/test/test_ukify.py::test_parse_args_many - FileNotFoundEr...
FAILED ../src/ukify/test/test_ukify.py::test_parse_sections - FileNotFoundErr...
=================== 3 failed, 10 passed, 3 skipped in 1.51s ====================