mirror of
https://github.com/systemd/systemd-stable.git
synced 2025-03-08 20:58:20 +03:00
Merge pull request #25180 from keszybz/ukify
ukify: add helper to create UKIs
This commit is contained in:
commit
a579990277
1
.github/workflows/build_test.sh
vendored
1
.github/workflows/build_test.sh
vendored
@ -53,6 +53,7 @@ PACKAGES=(
|
|||||||
python3-evdev
|
python3-evdev
|
||||||
python3-jinja2
|
python3-jinja2
|
||||||
python3-lxml
|
python3-lxml
|
||||||
|
python3-pefile
|
||||||
python3-pip
|
python3-pip
|
||||||
python3-pyparsing
|
python3-pyparsing
|
||||||
python3-setuptools
|
python3-setuptools
|
||||||
|
1
.github/workflows/unit_tests.sh
vendored
1
.github/workflows/unit_tests.sh
vendored
@ -21,6 +21,7 @@ ADDITIONAL_DEPS=(
|
|||||||
libzstd-dev
|
libzstd-dev
|
||||||
perl
|
perl
|
||||||
python3-libevdev
|
python3-libevdev
|
||||||
|
python3-pefile
|
||||||
python3-pyparsing
|
python3-pyparsing
|
||||||
rpm
|
rpm
|
||||||
zstd
|
zstd
|
||||||
|
1
README
1
README
@ -207,6 +207,7 @@ REQUIREMENTS:
|
|||||||
docbook-xsl (optional, required for documentation)
|
docbook-xsl (optional, required for documentation)
|
||||||
xsltproc (optional, required for documentation)
|
xsltproc (optional, required for documentation)
|
||||||
python-jinja2
|
python-jinja2
|
||||||
|
python-pefile
|
||||||
python-lxml (optional, required to build the indices)
|
python-lxml (optional, required to build the indices)
|
||||||
python >= 3.5
|
python >= 3.5
|
||||||
meson >= 0.53.2
|
meson >= 0.53.2
|
||||||
|
@ -1191,6 +1191,7 @@ manpages = [
|
|||||||
''],
|
''],
|
||||||
['udev_new', '3', ['udev_ref', 'udev_unref'], ''],
|
['udev_new', '3', ['udev_ref', 'udev_unref'], ''],
|
||||||
['udevadm', '8', [], ''],
|
['udevadm', '8', [], ''],
|
||||||
|
['ukify', '1', [], 'HAVE_GNU_EFI'],
|
||||||
['user@.service',
|
['user@.service',
|
||||||
'5',
|
'5',
|
||||||
['systemd-user-runtime-dir', 'user-runtime-dir@.service'],
|
['systemd-user-runtime-dir', 'user-runtime-dir@.service'],
|
||||||
|
312
man/ukify.xml
Normal file
312
man/ukify.xml
Normal file
@ -0,0 +1,312 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!--*-nxml-*-->
|
||||||
|
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
|
||||||
|
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
|
||||||
|
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
|
||||||
|
<refentry id="ukify" xmlns:xi="http://www.w3.org/2001/XInclude" conditional='HAVE_GNU_EFI'>
|
||||||
|
|
||||||
|
<refentryinfo>
|
||||||
|
<title>ukify</title>
|
||||||
|
<productname>systemd</productname>
|
||||||
|
</refentryinfo>
|
||||||
|
|
||||||
|
<refmeta>
|
||||||
|
<refentrytitle>ukify</refentrytitle>
|
||||||
|
<manvolnum>1</manvolnum>
|
||||||
|
</refmeta>
|
||||||
|
|
||||||
|
<refnamediv>
|
||||||
|
<refname>ukify</refname>
|
||||||
|
<refpurpose>Combine kernel and initrd into a signed Unified Kernel Image</refpurpose>
|
||||||
|
</refnamediv>
|
||||||
|
|
||||||
|
<refsynopsisdiv>
|
||||||
|
<cmdsynopsis>
|
||||||
|
<command>/usr/lib/systemd/ukify</command>
|
||||||
|
<arg choice="plain"><replaceable>LINUX</replaceable></arg>
|
||||||
|
<arg choice="plain" rep="repeat"><replaceable>INITRD</replaceable></arg>
|
||||||
|
<arg choice="opt" rep="repeat">OPTIONS</arg>
|
||||||
|
</cmdsynopsis>
|
||||||
|
</refsynopsisdiv>
|
||||||
|
|
||||||
|
<refsect1>
|
||||||
|
<title>Description</title>
|
||||||
|
|
||||||
|
<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 a kernel and an initrd with
|
||||||
|
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>
|
||||||
|
|
||||||
|
<para>Additional sections will be inserted into the UKI, either automatically or only if a specific
|
||||||
|
option is provided. See the discussions of
|
||||||
|
<option>--cmdline=</option>,
|
||||||
|
<option>--os-release=</option>,
|
||||||
|
<option>--devicetree=</option>,
|
||||||
|
<option>--splash=</option>,
|
||||||
|
<option>--pcrpkey=</option>,
|
||||||
|
<option>--uname=</option>,
|
||||||
|
and <option>--section=</option>
|
||||||
|
below.</para>
|
||||||
|
|
||||||
|
<para>If PCR signing keys are provided via the <option>--pcr-public-key=</option> and
|
||||||
|
<option>--pcr-private-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
|
||||||
|
<option>--phases=</option> option. If not specified, the default provided by
|
||||||
|
<command>systemd-measure</command> is used. It is also possible to specify the
|
||||||
|
<option>--pcr-private-key=</option>, <option>--pcr-public-key=</option>, and <option>--phases=</option>
|
||||||
|
arguments more than once. Signatures will be then performed with each of the specified keys. 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.</para>
|
||||||
|
|
||||||
|
<para>If a SecureBoot signing key is provided via the <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>
|
||||||
|
</refsect1>
|
||||||
|
|
||||||
|
<refsect1>
|
||||||
|
<title>Options</title>
|
||||||
|
|
||||||
|
<para>Note that the <replaceable>LINUX</replaceable> positional argument is mandatory. The
|
||||||
|
<replaceable>INITRD</replaceable> positional arguments are optional. If more than one is specified, they
|
||||||
|
will all be combined into a single PE section. This is useful to for example prepend microcode before the
|
||||||
|
actual initrd.</para>
|
||||||
|
|
||||||
|
<para>The following options are understood:</para>
|
||||||
|
|
||||||
|
<variablelist>
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>--cmdline=<replaceable>TEXT</replaceable>|<replaceable>@PATH</replaceable></option></term>
|
||||||
|
|
||||||
|
<listitem><para>Specify the kernel command line (the <literal>.cmdline</literal> section). The
|
||||||
|
argument may be a literal string, or <literal>@</literal> followed by a path name. If not specified,
|
||||||
|
no command line will be embedded.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>--os-release=<replaceable>TEXT</replaceable>|<replaceable>@PATH</replaceable></option></term>
|
||||||
|
|
||||||
|
<listitem><para>Specify the os-release description (the <literal>.osrel</literal> section). The
|
||||||
|
argument may be a literal string, or <literal>@</literal> followed by a path name. If not specified,
|
||||||
|
the <citerefentry><refentrytitle>os-release</refentrytitle><manvolnum>5</manvolnum></citerefentry>
|
||||||
|
file will be picked up from the host system.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>--devicetree=<replaceable>PATH</replaceable></option></term>
|
||||||
|
|
||||||
|
<listitem><para>Specify the devicetree description (the <literal>.dtb</literal> section). The
|
||||||
|
argument is a path to a compiled binary DeviceTree file. If not specified, the section will not be
|
||||||
|
present.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>--splash=<replaceable>PATH</replaceable></option></term>
|
||||||
|
|
||||||
|
<listitem><para>Specify a picture to display during boot (the <literal>.splash</literal> section).
|
||||||
|
The argument is a path to a BMP file. If not specified, the section will not be present.
|
||||||
|
</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>--pcrpkey=<replaceable>PATH</replaceable></option></term>
|
||||||
|
|
||||||
|
<listitem><para>Specify a path to a public key to embed in the <literal>.pcrpkey</literal> section.
|
||||||
|
If not specified, and there's exactly one <option>--pcr-public-key=</option> argument, that key will
|
||||||
|
be used. Otherwise, the section will not be present.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>--uname=<replaceable>VERSION</replaceable></option></term>
|
||||||
|
|
||||||
|
<listitem><para>Specify the kernel version (as in <command>uname -r</command>, the
|
||||||
|
<literal>.uname</literal> section). If not specified, an attempt will be made to extract the version
|
||||||
|
string from the kernel image. It is recommended to pass this explicitly if known, because the
|
||||||
|
extraction is based on heuristics and not very reliable. If not specified and extraction fails, the
|
||||||
|
section will not be present.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>--section=<replaceable>NAME</replaceable>:<replaceable>TEXT</replaceable>|<replaceable>@PATH</replaceable></option></term>
|
||||||
|
|
||||||
|
<listitem><para>Specify an arbitrary additional section
|
||||||
|
<literal><replaceable>NAME</replaceable></literal>. Note that the name is used as-is, and if the
|
||||||
|
section name should start with a dot, it must be included in <replaceable>NAME</replaceable>. The
|
||||||
|
argument may be a literal string, or <literal>@</literal> followed by a path name. This option may be
|
||||||
|
specified more than once. Any sections specified in this fashion will be inserted (in order) before
|
||||||
|
the <literal>.linux</literal> section which is always last.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>--pcr-private-key=<replaceable>PATH</replaceable></option></term>
|
||||||
|
|
||||||
|
<listitem><para>Specify a private key to use for signing PCR policies. This option may be specified
|
||||||
|
more than once, in which case multiple signatures will be made.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>--pcr-public-key=<replaceable>PATH</replaceable></option></term>
|
||||||
|
|
||||||
|
<listitem><para>Specify a public key to use for signing PCR policies. 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. If present, the this option must be specified
|
||||||
|
the same number of times as the <option>--pcr-private-key=</option> option.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>--phases=<replaceable>LIST</replaceable></option></term>
|
||||||
|
|
||||||
|
<listitem><para>A comma or space-separated list of colon-separated phase paths to sign a policy for.
|
||||||
|
If not present, the default of
|
||||||
|
<citerefentry><refentrytitle>systemd-measure</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||||
|
will be used. When this argument is present, it must appear the same number of times as the
|
||||||
|
<option>--pcr-private-key=</option> option. Each set of boot phase paths will be signed with the
|
||||||
|
corresponding private key.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>--pcr-banks=<replaceable>PATH</replaceable></option></term>
|
||||||
|
|
||||||
|
<listitem><para>A comma or space-separated list of PCR banks to sign a policy for. If not present,
|
||||||
|
all known banks will be used (<literal>sha1</literal>, <literal>sha256</literal>,
|
||||||
|
<literal>sha384</literal>, <literal>sha512</literal>), which will fail if not supported by the
|
||||||
|
system.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>--secureboot-private-key=<replaceable>SB_KEY</replaceable></option></term>
|
||||||
|
|
||||||
|
<listitem><para>A path to a private key to use for signing of the resulting binary. If the
|
||||||
|
<option>--signing-engine=</option> option is used, this may also be an engine-specific
|
||||||
|
designation.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>--secureboot-certificate=<replaceable>SB_CERT</replaceable></option></term>
|
||||||
|
|
||||||
|
<listitem><para>A path to a certificate to use for signing of the resulting binary. If the
|
||||||
|
<option>--signing-engine=</option> option is used, this may also be an engine-specific
|
||||||
|
designation.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>--signing-engine=<replaceable>ENGINE</replaceable></option></term>
|
||||||
|
|
||||||
|
<listitem><para>An "engine" to for signing of the resulting binary. This option is currently passed
|
||||||
|
verbatim to the <option>--engine=</option> option of
|
||||||
|
<citerefentry><refentrytitle>sbsign</refentrytitle><manvolnum>1</manvolnum></citerefentry>.
|
||||||
|
</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>--sign-kernel</option></term>
|
||||||
|
<term><option>--no-sign-kernel</option></term>
|
||||||
|
|
||||||
|
<listitem><para>Override the detection of whether to sign the Linux binary itself before it is
|
||||||
|
embedded in the combined image. If not specified, it will be signed if a SecureBoot signing key is
|
||||||
|
provided via the <option>--secureboot-private-key=</option> option and the binary has not already
|
||||||
|
been signed. If <option>--sign-kernel</option> is specified, and the binary has already been signed,
|
||||||
|
the signature will be appended anyway.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>--tools=<replaceable>DIR</replaceable></option></term>
|
||||||
|
|
||||||
|
<listitem><para>Specify a directory with helper tools. <command>ukify</command> will look for helper
|
||||||
|
tools in that directory first, and if not found, try to load them from <varname>$PATH</varname> in
|
||||||
|
the usual fashion.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>--measure</option></term>
|
||||||
|
<term><option>--no-measure</option></term>
|
||||||
|
|
||||||
|
<listitem><para>Enable or disable a call to <command>systmed-measure</command> to print
|
||||||
|
pre-calculated PCR values. Defaults to false.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>--output=<replaceable>FILENAME</replaceable></option></term>
|
||||||
|
|
||||||
|
<listitem><para>The output filename. If not specified, the name of the
|
||||||
|
<replaceable>LINUX</replaceable> argument, with the suffix <literal>.unsigned.efi</literal> or
|
||||||
|
<literal>.signed.efi</literal> will be used, depending on whether signing for SecureBoot was
|
||||||
|
performed.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<xi:include href="standard-options.xml" xpointer="help" />
|
||||||
|
<xi:include href="standard-options.xml" xpointer="version" />
|
||||||
|
</variablelist>
|
||||||
|
</refsect1>
|
||||||
|
|
||||||
|
<refsect1>
|
||||||
|
<title>Examples</title>
|
||||||
|
|
||||||
|
<example>
|
||||||
|
<title>Minimal invocation</title>
|
||||||
|
|
||||||
|
<programlisting>ukify \
|
||||||
|
/lib/modules/6.0.9-300.fc37.x86_64/vmlinuz \
|
||||||
|
/some/path/initramfs-6.0.9-300.fc37.x86_64.img \
|
||||||
|
--cmdline='quiet rw'
|
||||||
|
</programlisting>
|
||||||
|
|
||||||
|
<para>This creates an unsigned UKI <filename>./vmlinuz.unsigned.efi</filename>.</para>
|
||||||
|
</example>
|
||||||
|
|
||||||
|
<example>
|
||||||
|
<title>All the bells and whistles</title>
|
||||||
|
|
||||||
|
<programlisting>/usr/lib/systemd/ukify \
|
||||||
|
/lib/modules/6.0.9-300.fc37.x86_64/vmlinuz \
|
||||||
|
early_cpio \
|
||||||
|
/some/path/initramfs-6.0.9-300.fc37.x86_64.img \
|
||||||
|
--pcr-private-key=pcr-private-initrd-key.pem \
|
||||||
|
--pcr-public-key=pcr-public-initrd-key.pem \
|
||||||
|
--phases='enter-initrd' \
|
||||||
|
--pcr-private-key=pcr-private-system-key.pem \
|
||||||
|
--pcr-public-key=pcr-public-system-key.pem \
|
||||||
|
--phases='enter-initrd:leave-initrd enter-initrd:leave-initrd:sysinit \
|
||||||
|
enter-initrd:leave-initrd:sysinit:ready' \
|
||||||
|
--pcr-banks=sha384,sha512 \
|
||||||
|
--secureboot-private-key=sb.key \
|
||||||
|
--secureboot-certificate=sb.cert \
|
||||||
|
--sign-kernel \
|
||||||
|
--cmdline='quiet rw rhgb'
|
||||||
|
</programlisting>
|
||||||
|
|
||||||
|
<para>This creates a signed UKI <filename index='false'>./vmlinuz.signed.efi</filename>.
|
||||||
|
The initrd section contains two concatenated parts, <filename index='false'>early_cpio</filename>
|
||||||
|
and <filename index='false'>initramfs-6.0.9-300.fc37.x86_64.img</filename>.
|
||||||
|
The policy embedded in the <literal>.pcrsig</literal> section will be signed for the initrd (the
|
||||||
|
<constant>enter-initrd</constant> phase) with the key
|
||||||
|
<filename index='false'>pcr-private-initrd-key.pem</filename>, and for the main system (phases
|
||||||
|
<constant>leave-initrd</constant>, <constant>sysinit</constant>, <constant>ready</constant>) with the
|
||||||
|
key <filename index='false'>pcr-private-system-key.pem</filename>. The Linux binary and the resulting
|
||||||
|
combined image will be signed with the SecureBoot key <filename index='false'>sb.key</filename>.</para>
|
||||||
|
</example>
|
||||||
|
</refsect1>
|
||||||
|
|
||||||
|
<refsect1>
|
||||||
|
<title>See Also</title>
|
||||||
|
<para>
|
||||||
|
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||||
|
<citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
|
||||||
|
<citerefentry><refentrytitle>systemd-boot</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
|
||||||
|
<citerefentry project='man-pages'><refentrytitle>objcopy</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||||
|
<citerefentry><refentrytitle>systemd-pcrphase.service</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||||
|
</para>
|
||||||
|
</refsect1>
|
||||||
|
|
||||||
|
</refentry>
|
34
meson.build
34
meson.build
@ -716,6 +716,17 @@ if run_command(python, '-c', 'import jinja2', check : false).returncode() != 0
|
|||||||
error('python3 jinja2 missing')
|
error('python3 jinja2 missing')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
python_310 = run_command(python, '-c',
|
||||||
|
'import sys; sys.exit(0 if sys.version_info >= (3,10) else 1)',
|
||||||
|
check : false).returncode() == 0
|
||||||
|
if get_option('ukify') == 'auto'
|
||||||
|
want_ukify = python_310
|
||||||
|
elif get_option('ukify') == 'true' and not python310
|
||||||
|
error('ukify requires Python >= 3.10')
|
||||||
|
else
|
||||||
|
want_ukify = get_option('ukify') == 'true'
|
||||||
|
endif
|
||||||
|
|
||||||
############################################################
|
############################################################
|
||||||
|
|
||||||
gperf = find_program('gperf')
|
gperf = find_program('gperf')
|
||||||
@ -2191,6 +2202,7 @@ subdir('src/test')
|
|||||||
subdir('src/fuzz')
|
subdir('src/fuzz')
|
||||||
subdir('rules.d')
|
subdir('rules.d')
|
||||||
subdir('test')
|
subdir('test')
|
||||||
|
subdir('src/ukify/test') # needs to be last for test_env variable
|
||||||
|
|
||||||
############################################################
|
############################################################
|
||||||
|
|
||||||
@ -2587,7 +2599,7 @@ if conf.get('HAVE_BLKID') == 1 and conf.get('HAVE_GNU_EFI') == 1
|
|||||||
boot_link_with = [libsystemd_static, libshared_static]
|
boot_link_with = [libsystemd_static, libshared_static]
|
||||||
endif
|
endif
|
||||||
|
|
||||||
public_programs += executable(
|
exe = executable(
|
||||||
'bootctl',
|
'bootctl',
|
||||||
'src/boot/bootctl.c',
|
'src/boot/bootctl.c',
|
||||||
include_directories : includes,
|
include_directories : includes,
|
||||||
@ -2596,6 +2608,14 @@ if conf.get('HAVE_BLKID') == 1 and conf.get('HAVE_GNU_EFI') == 1
|
|||||||
versiondep],
|
versiondep],
|
||||||
install_rpath : rootpkglibdir,
|
install_rpath : rootpkglibdir,
|
||||||
install : true)
|
install : true)
|
||||||
|
public_programs += exe
|
||||||
|
|
||||||
|
if want_tests != 'false'
|
||||||
|
test('test-bootctl-json',
|
||||||
|
test_bootctl_json_sh,
|
||||||
|
args : exe.full_path(),
|
||||||
|
depends : exe)
|
||||||
|
endif
|
||||||
|
|
||||||
public_programs += executable(
|
public_programs += executable(
|
||||||
'systemd-bless-boot',
|
'systemd-bless-boot',
|
||||||
@ -4008,6 +4028,18 @@ if want_tests != 'false' and want_kernel_install
|
|||||||
args : [exe.full_path(), loaderentry_install])
|
args : [exe.full_path(), loaderentry_install])
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if want_ukify
|
||||||
|
exe = custom_target(
|
||||||
|
'ukify',
|
||||||
|
input : 'src/ukify/ukify.py',
|
||||||
|
output : 'ukify',
|
||||||
|
command : [jinja2_cmdline, '@INPUT@', '@OUTPUT@'],
|
||||||
|
install : true,
|
||||||
|
install_mode : 'rwxr-xr-x',
|
||||||
|
install_dir : rootlibexecdir)
|
||||||
|
public_programs += exe
|
||||||
|
endif
|
||||||
|
|
||||||
############################################################
|
############################################################
|
||||||
|
|
||||||
runtest_env = custom_target(
|
runtest_env = custom_target(
|
||||||
|
@ -501,6 +501,8 @@ option('llvm-fuzz', type : 'boolean', value : false,
|
|||||||
description : 'build against LLVM libFuzzer')
|
description : 'build against LLVM libFuzzer')
|
||||||
option('kernel-install', type: 'boolean', value: true,
|
option('kernel-install', type: 'boolean', value: true,
|
||||||
description : 'install kernel-install and associated files')
|
description : 'install kernel-install and associated files')
|
||||||
|
option('ukify', type : 'combo', choices : ['auto', 'true', 'false'],
|
||||||
|
description : 'install ukify')
|
||||||
option('analyze', type: 'boolean', value: true,
|
option('analyze', type: 'boolean', value: true,
|
||||||
description : 'install systemd-analyze')
|
description : 'install systemd-analyze')
|
||||||
|
|
||||||
|
@ -1404,6 +1404,8 @@ int show_boot_entries(const BootConfig *config, JsonFormatFlags json_format) {
|
|||||||
assert(config);
|
assert(config);
|
||||||
|
|
||||||
if (!FLAGS_SET(json_format, JSON_FORMAT_OFF)) {
|
if (!FLAGS_SET(json_format, JSON_FORMAT_OFF)) {
|
||||||
|
_cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
|
||||||
|
|
||||||
for (size_t i = 0; i < config->n_entries; i++) {
|
for (size_t i = 0; i < config->n_entries; i++) {
|
||||||
_cleanup_free_ char *opts = NULL;
|
_cleanup_free_ char *opts = NULL;
|
||||||
const BootEntry *e = config->entries + i;
|
const BootEntry *e = config->entries + i;
|
||||||
@ -1443,9 +1445,13 @@ int show_boot_entries(const BootConfig *config, JsonFormatFlags json_format) {
|
|||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_oom();
|
return log_oom();
|
||||||
|
|
||||||
json_variant_dump(v, json_format, stdout, NULL);
|
r = json_variant_append_array(&array, v);
|
||||||
|
if (r < 0)
|
||||||
|
return log_oom();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
json_variant_dump(array, json_format | JSON_FORMAT_EMPTY_ARRAY, NULL, NULL);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
for (size_t n = 0; n < config->n_entries; n++) {
|
for (size_t n = 0; n < config->n_entries; n++) {
|
||||||
r = show_boot_entry(
|
r = show_boot_entry(
|
||||||
|
@ -553,9 +553,34 @@ static void json_variant_copy_source(JsonVariant *v, JsonVariant *from) {
|
|||||||
v->source = json_source_ref(from->source);
|
v->source = json_source_ref(from->source);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int _json_variant_array_put_element(JsonVariant *array, JsonVariant *element) {
|
||||||
|
assert(array);
|
||||||
|
JsonVariant *w = array + 1 + array->n_elements;
|
||||||
|
|
||||||
|
uint16_t d = json_variant_depth(element);
|
||||||
|
if (d >= DEPTH_MAX) /* Refuse too deep nesting */
|
||||||
|
return -ELNRNG;
|
||||||
|
if (d >= array->depth)
|
||||||
|
array->depth = d + 1;
|
||||||
|
array->n_elements ++;
|
||||||
|
|
||||||
|
*w = (JsonVariant) {
|
||||||
|
.is_embedded = true,
|
||||||
|
.parent = array,
|
||||||
|
};
|
||||||
|
|
||||||
|
json_variant_set(w, element);
|
||||||
|
json_variant_copy_source(w, element);
|
||||||
|
|
||||||
|
if (!json_variant_is_normalized(element))
|
||||||
|
array->normalized = false;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int json_variant_new_array(JsonVariant **ret, JsonVariant **array, size_t n) {
|
int json_variant_new_array(JsonVariant **ret, JsonVariant **array, size_t n) {
|
||||||
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
|
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
|
||||||
bool normalized = true;
|
int r;
|
||||||
|
|
||||||
assert_return(ret, -EINVAL);
|
assert_return(ret, -EINVAL);
|
||||||
if (n == 0) {
|
if (n == 0) {
|
||||||
@ -571,33 +596,15 @@ int json_variant_new_array(JsonVariant **ret, JsonVariant **array, size_t n) {
|
|||||||
*v = (JsonVariant) {
|
*v = (JsonVariant) {
|
||||||
.n_ref = 1,
|
.n_ref = 1,
|
||||||
.type = JSON_VARIANT_ARRAY,
|
.type = JSON_VARIANT_ARRAY,
|
||||||
|
.normalized = true,
|
||||||
};
|
};
|
||||||
|
|
||||||
for (v->n_elements = 0; v->n_elements < n; v->n_elements++) {
|
while (v->n_elements < n) {
|
||||||
JsonVariant *w = v + 1 + v->n_elements,
|
r = _json_variant_array_put_element(v, array[v->n_elements]);
|
||||||
*c = array[v->n_elements];
|
if (r < 0)
|
||||||
uint16_t d;
|
return r;
|
||||||
|
|
||||||
d = json_variant_depth(c);
|
|
||||||
if (d >= DEPTH_MAX) /* Refuse too deep nesting */
|
|
||||||
return -ELNRNG;
|
|
||||||
if (d >= v->depth)
|
|
||||||
v->depth = d + 1;
|
|
||||||
|
|
||||||
*w = (JsonVariant) {
|
|
||||||
.is_embedded = true,
|
|
||||||
.parent = v,
|
|
||||||
};
|
|
||||||
|
|
||||||
json_variant_set(w, c);
|
|
||||||
json_variant_copy_source(w, c);
|
|
||||||
|
|
||||||
if (!json_variant_is_normalized(c))
|
|
||||||
normalized = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
v->normalized = normalized;
|
|
||||||
|
|
||||||
*ret = TAKE_PTR(v);
|
*ret = TAKE_PTR(v);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -823,6 +830,19 @@ static void json_variant_free_inner(JsonVariant *v, bool force_sensitive) {
|
|||||||
explicit_bzero_safe(v, json_variant_size(v));
|
explicit_bzero_safe(v, json_variant_size(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static unsigned json_variant_n_ref(const JsonVariant *v) {
|
||||||
|
/* Return the number of references to v.
|
||||||
|
* 0 => NULL or not a regular object or embedded.
|
||||||
|
* >0 => number of references
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!v || !json_variant_is_regular(v) || v->is_embedded)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
assert(v->n_ref > 0);
|
||||||
|
return v->n_ref;
|
||||||
|
}
|
||||||
|
|
||||||
JsonVariant *json_variant_ref(JsonVariant *v) {
|
JsonVariant *json_variant_ref(JsonVariant *v) {
|
||||||
if (!v)
|
if (!v)
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -1790,8 +1810,12 @@ int json_variant_format(JsonVariant *v, JsonFormatFlags flags, char **ret) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int json_variant_dump(JsonVariant *v, JsonFormatFlags flags, FILE *f, const char *prefix) {
|
int json_variant_dump(JsonVariant *v, JsonFormatFlags flags, FILE *f, const char *prefix) {
|
||||||
if (!v)
|
if (!v) {
|
||||||
return 0;
|
if (flags & JSON_FORMAT_EMPTY_ARRAY)
|
||||||
|
v = JSON_VARIANT_MAGIC_EMPTY_ARRAY;
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (!f)
|
if (!f)
|
||||||
f = stdout;
|
f = stdout;
|
||||||
@ -2072,28 +2096,54 @@ int json_variant_append_array(JsonVariant **v, JsonVariant *element) {
|
|||||||
|
|
||||||
if (!*v || json_variant_is_null(*v))
|
if (!*v || json_variant_is_null(*v))
|
||||||
blank = true;
|
blank = true;
|
||||||
else if (!json_variant_is_array(*v))
|
else if (json_variant_is_array(*v))
|
||||||
return -EINVAL;
|
|
||||||
else
|
|
||||||
blank = json_variant_elements(*v) == 0;
|
blank = json_variant_elements(*v) == 0;
|
||||||
|
else
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
if (blank)
|
if (blank) {
|
||||||
r = json_variant_new_array(&nv, (JsonVariant*[]) { element }, 1);
|
r = json_variant_new_array(&nv, (JsonVariant*[]) { element }, 1);
|
||||||
else {
|
if (r < 0)
|
||||||
_cleanup_free_ JsonVariant **array = new(JsonVariant*, json_variant_elements(*v) + 1);
|
return r;
|
||||||
|
} else if (json_variant_n_ref(*v) == 1) {
|
||||||
|
/* Let's bump the reference count on element. We can't do the realloc if we're appending *v
|
||||||
|
* to itself, or one of the objects embedded in *v to *v. If the reference count grows, we
|
||||||
|
* need to fall back to the other method below. */
|
||||||
|
|
||||||
|
_unused_ _cleanup_(json_variant_unrefp) JsonVariant *dummy = json_variant_ref(element);
|
||||||
|
if (json_variant_n_ref(*v) == 1) {
|
||||||
|
/* We hold the only reference. Let's mutate the object. */
|
||||||
|
size_t size = json_variant_elements(*v);
|
||||||
|
void *old = *v;
|
||||||
|
|
||||||
|
if (!GREEDY_REALLOC(*v, size + 1 + 1))
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
if (old != *v)
|
||||||
|
/* Readjust the parent pointers to the new address */
|
||||||
|
for (size_t i = 1; i < size; i++)
|
||||||
|
(*v)[1 + i].parent = *v;
|
||||||
|
|
||||||
|
return _json_variant_array_put_element(*v, element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!blank) {
|
||||||
|
size_t size = json_variant_elements(*v);
|
||||||
|
|
||||||
|
_cleanup_free_ JsonVariant **array = new(JsonVariant*, size + 1);
|
||||||
if (!array)
|
if (!array)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
size_t size = json_variant_elements(*v);
|
|
||||||
for (size_t i = 0; i < size; i++)
|
for (size_t i = 0; i < size; i++)
|
||||||
array[i] = json_variant_by_index(*v, i);
|
array[i] = json_variant_by_index(*v, i);
|
||||||
|
|
||||||
array[size] = element;
|
array[size] = element;
|
||||||
|
|
||||||
r = json_variant_new_array(&nv, array, size + 1);
|
r = json_variant_new_array(&nv, array, size + 1);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
}
|
}
|
||||||
if (r < 0)
|
|
||||||
return r;
|
|
||||||
|
|
||||||
json_variant_propagate_sensitive(*v, nv);
|
json_variant_propagate_sensitive(*v, nv);
|
||||||
JSON_VARIANT_REPLACE(*v, TAKE_PTR(nv));
|
JSON_VARIANT_REPLACE(*v, TAKE_PTR(nv));
|
||||||
@ -3180,16 +3230,53 @@ finish:
|
|||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
int json_parse(const char *input, JsonParseFlags flags, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column) {
|
int json_parse_with_source(
|
||||||
return json_parse_internal(&input, NULL, flags, ret, ret_line, ret_column, false);
|
const char *input,
|
||||||
|
const char *source,
|
||||||
|
JsonParseFlags flags,
|
||||||
|
JsonVariant **ret,
|
||||||
|
unsigned *ret_line,
|
||||||
|
unsigned *ret_column) {
|
||||||
|
|
||||||
|
_cleanup_(json_source_unrefp) JsonSource *s = NULL;
|
||||||
|
|
||||||
|
if (source) {
|
||||||
|
s = json_source_new(source);
|
||||||
|
if (!s)
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
return json_parse_internal(&input, s, flags, ret, ret_line, ret_column, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
int json_parse_continue(const char **p, JsonParseFlags flags, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column) {
|
int json_parse_with_source_continue(
|
||||||
return json_parse_internal(p, NULL, flags, ret, ret_line, ret_column, true);
|
const char **p,
|
||||||
|
const char *source,
|
||||||
|
JsonParseFlags flags,
|
||||||
|
JsonVariant **ret,
|
||||||
|
unsigned *ret_line,
|
||||||
|
unsigned *ret_column) {
|
||||||
|
|
||||||
|
_cleanup_(json_source_unrefp) JsonSource *s = NULL;
|
||||||
|
|
||||||
|
if (source) {
|
||||||
|
s = json_source_new(source);
|
||||||
|
if (!s)
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
return json_parse_internal(p, s, flags, ret, ret_line, ret_column, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
int json_parse_file_at(FILE *f, int dir_fd, const char *path, JsonParseFlags flags, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column) {
|
int json_parse_file_at(
|
||||||
_cleanup_(json_source_unrefp) JsonSource *source = NULL;
|
FILE *f,
|
||||||
|
int dir_fd,
|
||||||
|
const char *path,
|
||||||
|
JsonParseFlags flags,
|
||||||
|
JsonVariant **ret,
|
||||||
|
unsigned *ret_line,
|
||||||
|
unsigned *ret_column) {
|
||||||
|
|
||||||
_cleanup_free_ char *text = NULL;
|
_cleanup_free_ char *text = NULL;
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
@ -3205,14 +3292,7 @@ int json_parse_file_at(FILE *f, int dir_fd, const char *path, JsonParseFlags fla
|
|||||||
if (isempty(text))
|
if (isempty(text))
|
||||||
return -ENODATA;
|
return -ENODATA;
|
||||||
|
|
||||||
if (path) {
|
return json_parse_with_source(text, path, flags, ret, ret_line, ret_column);
|
||||||
source = json_source_new(path);
|
|
||||||
if (!source)
|
|
||||||
return -ENOMEM;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *p = text;
|
|
||||||
return json_parse_internal(&p, source, flags, ret, ret_line, ret_column, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int json_buildv(JsonVariant **ret, va_list ap) {
|
int json_buildv(JsonVariant **ret, va_list ap) {
|
||||||
|
@ -194,7 +194,8 @@ typedef enum JsonFormatFlags {
|
|||||||
JSON_FORMAT_SSE = 1 << 6, /* prefix/suffix with W3C server-sent events */
|
JSON_FORMAT_SSE = 1 << 6, /* prefix/suffix with W3C server-sent events */
|
||||||
JSON_FORMAT_SEQ = 1 << 7, /* prefix/suffix with RFC 7464 application/json-seq */
|
JSON_FORMAT_SEQ = 1 << 7, /* prefix/suffix with RFC 7464 application/json-seq */
|
||||||
JSON_FORMAT_FLUSH = 1 << 8, /* call fflush() after dumping JSON */
|
JSON_FORMAT_FLUSH = 1 << 8, /* call fflush() after dumping JSON */
|
||||||
JSON_FORMAT_OFF = 1 << 9, /* make json_variant_format() fail with -ENOEXEC */
|
JSON_FORMAT_EMPTY_ARRAY = 1 << 9, /* output "[]" for empty input */
|
||||||
|
JSON_FORMAT_OFF = 1 << 10, /* make json_variant_format() fail with -ENOEXEC */
|
||||||
} JsonFormatFlags;
|
} JsonFormatFlags;
|
||||||
|
|
||||||
int json_variant_format(JsonVariant *v, JsonFormatFlags flags, char **ret);
|
int json_variant_format(JsonVariant *v, JsonFormatFlags flags, char **ret);
|
||||||
@ -222,8 +223,16 @@ typedef enum JsonParseFlags {
|
|||||||
JSON_PARSE_SENSITIVE = 1 << 0, /* mark variant as "sensitive", i.e. something containing secret key material or such */
|
JSON_PARSE_SENSITIVE = 1 << 0, /* mark variant as "sensitive", i.e. something containing secret key material or such */
|
||||||
} JsonParseFlags;
|
} JsonParseFlags;
|
||||||
|
|
||||||
int json_parse(const char *string, JsonParseFlags flags, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column);
|
int json_parse_with_source(const char *string, const char *source, JsonParseFlags flags, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column);
|
||||||
int json_parse_continue(const char **p, JsonParseFlags flags, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column);
|
int json_parse_with_source_continue(const char **p, const char *source, JsonParseFlags flags, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column);
|
||||||
|
|
||||||
|
static inline int json_parse(const char *string, JsonParseFlags flags, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column) {
|
||||||
|
return json_parse_with_source(string, NULL, flags, ret, ret_line, ret_column);
|
||||||
|
}
|
||||||
|
static inline int json_parse_continue(const char **p, JsonParseFlags flags, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column) {
|
||||||
|
return json_parse_with_source_continue(p, NULL, flags, ret, ret_line, ret_column);
|
||||||
|
}
|
||||||
|
|
||||||
int json_parse_file_at(FILE *f, int dir_fd, const char *path, JsonParseFlags flags, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column);
|
int json_parse_file_at(FILE *f, int dir_fd, const char *path, JsonParseFlags flags, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column);
|
||||||
|
|
||||||
static inline int json_parse_file(FILE *f, const char *path, JsonParseFlags flags, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column) {
|
static inline int json_parse_file(FILE *f, const char *path, JsonParseFlags flags, JsonVariant **ret, unsigned *ret_line, unsigned *ret_column) {
|
||||||
|
@ -663,4 +663,67 @@ TEST(json_append) {
|
|||||||
assert_se(json_variant_equal(v, w));
|
assert_se(json_variant_equal(v, w));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void json_array_append_with_source_one(bool source) {
|
||||||
|
_cleanup_(json_variant_unrefp) JsonVariant *a, *b;
|
||||||
|
|
||||||
|
/* Parse two sources, each with a different name and line/column numbers */
|
||||||
|
|
||||||
|
assert_se(json_parse_with_source(" [41]", source ? "string 1" : NULL, 0,
|
||||||
|
&a, NULL, NULL) >= 0);
|
||||||
|
assert_se(json_parse_with_source("\n\n [42]", source ? "string 2" : NULL, 0,
|
||||||
|
&b, NULL, NULL) >= 0);
|
||||||
|
|
||||||
|
assert_se(json_variant_is_array(a));
|
||||||
|
assert_se(json_variant_elements(a) == 1);
|
||||||
|
assert_se(json_variant_is_array(b));
|
||||||
|
assert_se(json_variant_elements(b) == 1);
|
||||||
|
|
||||||
|
/* Verify source information */
|
||||||
|
|
||||||
|
const char *s1, *s2;
|
||||||
|
unsigned line1, col1, line2, col2;
|
||||||
|
assert_se(json_variant_get_source(a, &s1, &line1, &col1) >= 0);
|
||||||
|
assert_se(json_variant_get_source(b, &s2, &line2, &col2) >= 0);
|
||||||
|
|
||||||
|
assert_se(streq_ptr(s1, source ? "string 1" : NULL));
|
||||||
|
assert_se(streq_ptr(s2, source ? "string 2" : NULL));
|
||||||
|
assert_se(line1 == 1);
|
||||||
|
assert_se(col1 == 2);
|
||||||
|
assert_se(line2 == 3);
|
||||||
|
assert_se(col2 == 4);
|
||||||
|
|
||||||
|
/* Append one elem from the second array (and source) to the first. */
|
||||||
|
|
||||||
|
JsonVariant *elem;
|
||||||
|
assert_se(elem = json_variant_by_index(b, 0));
|
||||||
|
assert_se(json_variant_is_integer(elem));
|
||||||
|
assert_se(json_variant_elements(elem) == 0);
|
||||||
|
|
||||||
|
assert_se(json_variant_append_array(&a, elem) >= 0);
|
||||||
|
|
||||||
|
assert_se(json_variant_is_array(a));
|
||||||
|
assert_se(json_variant_elements(a) == 2);
|
||||||
|
|
||||||
|
/* Verify that source information was propagated correctly */
|
||||||
|
|
||||||
|
assert_se(json_variant_get_source(elem, &s1, &line1, &col1) >= 0);
|
||||||
|
assert_se(elem = json_variant_by_index(a, 1));
|
||||||
|
assert_se(json_variant_get_source(elem, &s2, &line2, &col2) >= 0);
|
||||||
|
|
||||||
|
assert_se(streq_ptr(s1, source ? "string 2" : NULL));
|
||||||
|
assert_se(streq_ptr(s2, source ? "string 2" : NULL));
|
||||||
|
assert_se(line1 == 3);
|
||||||
|
assert_se(col1 == 5);
|
||||||
|
assert_se(line2 == 3);
|
||||||
|
assert_se(col2 == 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(json_array_append_with_source) {
|
||||||
|
json_array_append_with_source_one(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(json_array_append_without_source) {
|
||||||
|
json_array_append_with_source_one(false);
|
||||||
|
}
|
||||||
|
|
||||||
DEFINE_TEST_MAIN(LOG_DEBUG);
|
DEFINE_TEST_MAIN(LOG_DEBUG);
|
||||||
|
23
src/ukify/test/example.signing.crt.base64
Normal file
23
src/ukify/test/example.signing.crt.base64
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURsVENDQW4yZ0F3SUJBZ0lVTzlqUWhhblhj
|
||||||
|
b3ViOERzdXlMMWdZbksrR1lvd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1dURUxNQWtHQTFVRUJoTUNX
|
||||||
|
Rmd4RlRBVEJnTlZCQWNNREVSbFptRjFiSFFnUTJsMGVURWNNQm9HQTFVRQpDZ3dUUkdWbVlYVnNk
|
||||||
|
Q0JEYjIxd1lXNTVJRXgwWkRFVk1CTUdBMVVFQXd3TWEyVjVJSE5wWjI1cGJtbG5NQ0FYCkRUSXlN
|
||||||
|
VEF5T1RFM01qY3dNVm9ZRHpNd01qSXdNekF4TVRjeU56QXhXakJaTVFzd0NRWURWUVFHRXdKWVdE
|
||||||
|
RVYKTUJNR0ExVUVCd3dNUkdWbVlYVnNkQ0JEYVhSNU1Sd3dHZ1lEVlFRS0RCTkVaV1poZFd4MElF
|
||||||
|
TnZiWEJoYm5rZwpUSFJrTVJVd0V3WURWUVFEREF4clpYa2djMmxuYm1sdWFXY3dnZ0VpTUEwR0NT
|
||||||
|
cUdTSWIzRFFFQkFRVUFBNElCCkR3QXdnZ0VLQW9JQkFRREtVeHR4Y0d1aGYvdUp1SXRjWEhvdW0v
|
||||||
|
RE9RL1RJM3BzUWlaR0ZWRkJzbHBicU5wZDUKa2JDaUFMNmgrY1FYaGRjUmlOT1dBR0wyMFZ1T2Rv
|
||||||
|
VTZrYzlkdklGQnFzKzc2NHhvWGY1UGd2SlhvQUxSUGxDZAp4YVdPQzFsOFFIRHpxZ09SdnREMWNI
|
||||||
|
WFoveTkvZ1YxVU1GK1FlYm12aUhRN0U4eGw1T2h5MG1TQVZYRDhBTitsCjdpMUR6N0NuTzhrMVph
|
||||||
|
alhqYXlpNWV1WEV0TnFSZXNuVktRRElTQ0t2STFueUxySWxHRU1GZmFuUmRLQWthZ3MKalJnTmVh
|
||||||
|
T3N3aklHNjV6UzFVdjJTZXcxVFpIaFhtUmd5TzRVT0JySHZlSml2T2hObzU3UlRKd0M2K2lGY0FG
|
||||||
|
aApSSnorVmM2QUlSSkI1ZWtJUmdCN3VDNEI5ZmwydXdZKytMODNBZ01CQUFHalV6QlJNQjBHQTFV
|
||||||
|
ZERnUVdCQlFqCllIMnpzVFlPQU51MkcweXk1QkxlOHBvbWZUQWZCZ05WSFNNRUdEQVdnQlFqWUgy
|
||||||
|
enNUWU9BTnUyRzB5eTVCTGUKOHBvbWZUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01BMEdDU3FHU0li
|
||||||
|
M0RRRUJDd1VBQTRJQkFRQ2dxcmFXaE51dQptUmZPUjVxcURVcC83RkpIL1N6Zk1vaDBHL2lWRkhv
|
||||||
|
OUpSS0tqMUZ2Q0VZc1NmeThYTmdaUDI5eS81Z0h4cmcrCjhwZWx6bWJLczdhUTRPK01TcmIzTm11
|
||||||
|
V1IzT0M0alBoNENrM09ZbDlhQy9iYlJqSWFvMDJ6K29XQWNZZS9xYTEKK2ZsemZWVEUwMHJ5V1RM
|
||||||
|
K0FJdDFEZEVqaG01WXNtYlgvbWtacUV1TjBtSVhhRXhSVE9walczUWRNeVRQaURTdApvanQvQWMv
|
||||||
|
R2RUWDd0QkhPTk44Z3djaC91V293aVNORERMUm1wM2VScnlOZ3RPKzBISUd5Qm16ZWNsM0VlVEo2
|
||||||
|
CnJzOGRWUFhqR1Z4dlZDb2tqQllrOWdxbkNGZEJCMGx4VXVNZldWdVkyRUgwSjI3aGh4SXNFc3ls
|
||||||
|
VTNIR1EyK2MKN1JicVY4VTNSRzA4Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
|
30
src/ukify/test/example.signing.key.base64
Normal file
30
src/ukify/test/example.signing.key.base64
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2Z0lCQURBTkJna3Foa2lHOXcwQkFRRUZB
|
||||||
|
QVNDQktnd2dnU2tBZ0VBQW9JQkFRREtVeHR4Y0d1aGYvdUoKdUl0Y1hIb3VtL0RPUS9USTNwc1Fp
|
||||||
|
WkdGVkZCc2xwYnFOcGQ1a2JDaUFMNmgrY1FYaGRjUmlOT1dBR0wyMFZ1Twpkb1U2a2M5ZHZJRkJx
|
||||||
|
cys3NjR4b1hmNVBndkpYb0FMUlBsQ2R4YVdPQzFsOFFIRHpxZ09SdnREMWNIWFoveTkvCmdWMVVN
|
||||||
|
RitRZWJtdmlIUTdFOHhsNU9oeTBtU0FWWEQ4QU4rbDdpMUR6N0NuTzhrMVphalhqYXlpNWV1WEV0
|
||||||
|
TnEKUmVzblZLUURJU0NLdkkxbnlMcklsR0VNRmZhblJkS0FrYWdzalJnTmVhT3N3aklHNjV6UzFV
|
||||||
|
djJTZXcxVFpIaApYbVJneU80VU9Cckh2ZUppdk9oTm81N1JUSndDNitpRmNBRmhSSnorVmM2QUlS
|
||||||
|
SkI1ZWtJUmdCN3VDNEI5ZmwyCnV3WSsrTDgzQWdNQkFBRUNnZ0VBQkhZQ28rU3JxdHJzaStQU3hz
|
||||||
|
MlBNQm5tSEZZcFBvaVIrTEpmMEFYRTVEQUoKMGM0MFZzemNqU1hoRGljNHFLQWQxdGdpZWlzMkEy
|
||||||
|
VW9WS0xPV3pVOTBqNUd4MURoMWEzaTRhWTQ1ajNuNUFDMgpMekRsakNVQWVucExsYzdCN3MxdjJM
|
||||||
|
WFJXNmdJSVM5Y043NTlkVTYvdktyQ2FsbGkzcTZZRWlNUzhQMHNsQnZFCkZtdEc1elFsOVJjV0gr
|
||||||
|
cHBqdzlIMTJSZ3BldUVJVEQ2cE0vd2xwcXZHRlUwcmZjM0NjMHhzaWdNTnh1Z1FJNGgKbnpjWDVs
|
||||||
|
OEs0SHdvbmhOTG9TYkh6OU5BK3p3QkpuUlZVSWFaaEVjSThtaEVPWHRaRkpYc01aRnhjS2l3SHFS
|
||||||
|
dApqUUVHOHJRa3lPLytXMmR5Z2czV1lNYXE1OWpUWVdIOUsrQmFyeEMzRVFLQmdRRFBNSFMycjgz
|
||||||
|
ZUpRTTlreXpkCndDdnlmWGhQVlVtbVJnOGwyWng0aC9tci9mNUdDeW5SdzRzT2JuZGVQd29tZ1Iz
|
||||||
|
cFBleFFGWlFFSExoZ1RGY3UKVk5uYXcrTzBFL1VnL01pRGswZDNXU0hVZXZPZnM1cEM2b3hYNjNT
|
||||||
|
VENwNkVLT2VEZlpVMW9OeHRsZ0YyRVhjcgpmVlZpSzFKRGk3N2dtaENLcFNGcjBLK3gyUUtCZ1FE
|
||||||
|
NS9VUC9hNU52clExdUhnKzR0SzJZSFhSK1lUOFREZG00Ck8xZmh5TU5lOHRYSkd5UUJjTktVTWg2
|
||||||
|
M2VyR1MwWlRWdGdkNHlGS3RuOGtLU2U4TmlacUl1aitVUVIyZ3pEQVAKQ2VXcXl2Y2pRNmovU1Yw
|
||||||
|
WjVvKzlTNytiOStpWWx5RTg2bGZobHh5Z21aNnptYisxUUNteUtNVUdBNis5VmUvMgo1MHhDMXBB
|
||||||
|
L2p3S0JnUUNEOHA4UnpVcDFZK3I1WnVaVzN0RGVJSXZqTWpTeVFNSGE0QWhuTm1tSjREcjBUcDIy
|
||||||
|
CmFpci82TmY2WEhsUlpqOHZVSEZUMnpvbG1FalBneTZ1WWZsUCtocmtqeVU0ZWVRVTcxRy9Mek45
|
||||||
|
UjBRcCs4Nk4KT1NSaHhhQzdHRE0xaFh0VFlVSUtJa1RmUVgzeXZGTEJqcE0yN3RINEZHSmVWWitk
|
||||||
|
UEdiWmE5REltUUtCZ1FENQpHTU5qeExiQnhhY25QYThldG5KdnE1SUR5RFRJY0xtc1dQMkZ6cjNX
|
||||||
|
WTVSZzhybGE4aWZ5WVVxNE92cXNPRWZjCjk2ZlVVNUFHejd2TWs4VXZNUmtaK3JRVnJ4aXR2Q2g3
|
||||||
|
STdxRkIvOWdWVEFWU080TE8vR29oczBqeGRBd0ZBK2IKbWtyOVQ4ekh2cXNqZlNWSW51bXRTL0Nl
|
||||||
|
d0plaHl2cjBoSjg1em9Fbnd3S0JnR1h6UXVDSjJDb3NHVVhEdnlHKwpyRzVBd3pUZGd0bHg4YTBK
|
||||||
|
NTg1OWtZbVd0cW5WYUJmbFdrRmNUcHNEaGZ2ZWVDUkswc29VRlNPWkcranpsbWJrCkpRL09aVkZJ
|
||||||
|
dG9MSVZCeE9qeWVXNlhUSkJXUzFRSkVHckkwY0tTbXNKcENtUXVPdUxMVnZYczV0U21CVmc5RXQK
|
||||||
|
MjZzUkZwcjVWWmsrZlNRa3RhbkM4NGV1Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
|
30
src/ukify/test/example.tpm2-pcr-private.pem.base64
Normal file
30
src/ukify/test/example.tpm2-pcr-private.pem.base64
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2Z0lCQURBTkJna3Foa2lHOXcwQkFRRUZB
|
||||||
|
QVNDQktnd2dnU2tBZ0VBQW9JQkFRQzVuOHFhbzVNZ1BJUVcKc0F5Y2R3dnB1bjdNNHlRSW9FL3I3
|
||||||
|
ekFGTG1hZlBXclo3d2JaaUIyTkY1MVdHOEo4bnlDQkI3M0RLcmZaeWs5cwphQXdXVW5RR2t0dGFv
|
||||||
|
RXpXRzZSRTM3dXdQOUpVM09YdklTNTBhcy9KSHVHNlJPYmE2V0NOOFp2TTdkZGpvTDFKCkZlYnBS
|
||||||
|
SXI1Vi82VStMTFhrUnRNYVczUnZ6T0xYeU1NT2QzOEcxZ0d0VlRHcm90ejVldFgrTUNVU2lOVGFE
|
||||||
|
OVUKN1dEZXVsZXVpMlRnK1I3TGRoSXg3ZTQ5cEhRM3d6a1NxeFQ4SGpoU3ZURWpITWVSNjIwaUhF
|
||||||
|
ZW9uYzdsMXVnagpzY1pwTktHdk13bXUvU2ptWFp6UkpOdjVOU0txcEVnQll2RnFkS3dUdlc4MWl6
|
||||||
|
SUFvN3paMkx6NDJYb25zSWJ2CjNrbGZqTG1mQWdNQkFBRUNnZ0VBQXozYm8yeTAzb3kvLzhkdVNQ
|
||||||
|
TTVSWWtvdXJwQ3dGWFFYMzNyV0VQUnJmazgKR3ZjMkp1bGVIcjhwVTc0alhOcklqZ2hORTVIMDZQ
|
||||||
|
eEQrOUFyV2Q1eHdVV2lTQWhobnlHWGNrNTM4Q0dGTWs4egpRc1JSRTk1anA0Ny9BU28vMzlYUWhs
|
||||||
|
b1FUdmxlV0JLUUM2MHl2YU1oVEM1eHR6ZEtwRUlYK0hNazVGTlMrcDJVCmxtL3AzVE1YWDl1bmc5
|
||||||
|
Mk9pTzUzV1VreFpQN2cwTVJHbGJrNzhqc1dkdjFYY0tLRjhuVmU5WC9NR1lTYlVLNy8KM2NYazFR
|
||||||
|
WTRUdVZaQlBFSE12RFRpWWwxbmdDd1ZuL2MyY3JQU3hJRFdFWlhEdm90SFUwQkNQZURVckxGa0F5
|
||||||
|
cQpDaloza3MzdEh4am42STkraEVNcUJDMzY1MHFjdDNkZ0RVV2loc2MzdVFLQmdRRG1mVTNKc29K
|
||||||
|
QWFOdmxCbXgyClhzRDRqbXlXV1F2Z244cVNVNG03a2JKdmprcUJ6VnB0T0ZsYmk2ejZHOXR6ZHNX
|
||||||
|
a0dJSjh3T0ZRb1hlM0dKOFIKSlVpeEFXTWZOM1JURGo5VjVXbzZJdE5EbzM1N3dNbVVYOW1qeThF
|
||||||
|
YXp0RE1BckdSNGJva0Q5RjY3clhqSGdSMQpaZVcvSDlUWHFUV1l4VHl6UDB3ZDBQeUZ4d0tCZ1FE
|
||||||
|
T0swWHVQS0o0WG00WmFCemN0OTdETXdDcFBSVmVvUWU3CmkzQjRJQ3orWFZ4cVM2amFTY2xNeEVm
|
||||||
|
Nk5tM2tLNERDR1dwVkpXcm9qNjlMck1KWnQzTlI2VUJ5NzNqUVBSamsKRXk5N3YrR04yVGwwNjFw
|
||||||
|
ZUxUM0dRS2RhT2VxWldpdElOcFc1dUxHL1poMGhoRUY5c1lSVTRtUFYwUWpla2kvdgp1bnVmcWx0
|
||||||
|
TmFRS0JnQTl6TE1pdFg0L0R0NkcxZVlYQnVqdXZDRlpYcDdVcDRPRklHajVwZU1XRGl6a0NNK0tJ
|
||||||
|
CldXMEtndERORnp1NUpXeG5mQyt5bWlmV2V2alovS2Vna1N2VVJQbXR0TzF3VWd5RzhVVHVXcXo1
|
||||||
|
QTV4MkFzMGcKVTYxb0ZneWUrbDRDZkRha0k5OFE5R0RDS1kwTTBRMnhnK0g0MTBLUmhCYzJlV2dt
|
||||||
|
Z1FxcW5KSzNBb0dCQU1rZgpnOWZXQlBVQndjdzlPYkxFR0tjNkVSSUlTZG1IbytCOE5kcXFJTnAv
|
||||||
|
djFEZXdEazZ0QXFVakZiMlZCdTdxSjh4ClpmN3NRcS9ldzdaQ01WS09XUXgyVEc0VFdUdGo3dTFJ
|
||||||
|
SGhGTjdiNlFRN0hnaXNiR3diV3VpdFBGSGl3OXYyMXgKK253MFJnb2VscHFFeDlMVG92R2Y3SjdB
|
||||||
|
ampONlR4TkJTNnBGNlUzSkFvR0JBT0tnbHlRWDJpVG5oMXd4RG1TVQo4RXhoQVN3S09iNS8yRmx4
|
||||||
|
aUhtUHVjNTZpS0tHY0lkV1cxMUdtbzdJakNzSTNvRm9iRkFjKzBXZkMvQTdMNWlmCjNBYVNWcmh0
|
||||||
|
cThRRklRaUtyYUQ0YlRtRk9Famg5QVVtUHMrWnd1OE9lSXJBSWtwZDV3YmlhTEJLd0pRbVdtSFAK
|
||||||
|
dUNBRTA3cXlSWXJ0c3QvcnVSSG5IdFA1Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
|
30
src/ukify/test/example.tpm2-pcr-private2.pem.base64
Normal file
30
src/ukify/test/example.tpm2-pcr-private2.pem.base64
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2QUlCQURBTkJna3Foa2lHOXcwQkFRRUZB
|
||||||
|
QVNDQktZd2dnU2lBZ0VBQW9JQkFRQzJ2Nk1oZHg3a3VjUHIKbmtFNFIrY3FnV2Y5T3B1c2h2M2o3
|
||||||
|
SG50K08wdi84d2l2T1BFNTlLMHYvRWJOOG94TDZEWUNXU0JCRU4vREJ5MgpMUTYwbldSdHBZN2Ju
|
||||||
|
bEcrcEtVeTRvSDRNZXZCR2JqZUhrak9LU3dNYVVWNGs4UmVSSjg4cVZ1U1MxSnVORW1NCmd5SERF
|
||||||
|
NGFPNG5ndG5UUFZZdzUydVBIcG1rN0E4VFdXN2lLZE5JWWZWOCtuR1pENXIzRWllekRsUUNORG54
|
||||||
|
UkcKdm5uSFZ6VFhZR3RwY2xaeWlJclpVekpBNFFPZnRueXB5UDVrQS94NVM1MU9QeGFxWlA3eGtP
|
||||||
|
S0NicUUvZmZvMApFTi9rTno0N0ZoUGUxbVBHUkZZWldHZXg0aWFPdHlLdHhnU1FYYkdlNEVoeVR4
|
||||||
|
SjJlT3U4QUVoVklTdjh6UU9nClNtbWx2UGQvQWdNQkFBRUNnZ0VBUUFpRERRRlR3bG96QTVhMmpK
|
||||||
|
VnBNdlFYNzF0L1c2TUxTRGMrZS90cWhKU1IKUHlUSGZHR3NhMmdMLy9qNjhHUWJiRWRTUDRDeWM4
|
||||||
|
eFhMU0E1bEdESDVVR0svbm9KYzQ3MlVZK2JjYzl3SjMrdgpUcWoyNHNIN2JMZmdQMEVybjhwVXIy
|
||||||
|
azZMRmNYSVlWUnRobm1sUmQ4NFFrS2loVVlxZTdsRFFWOXdsZ3V1eHpRCnBmVEtDTWk1bXJlYjIx
|
||||||
|
OExHS0QrMUxjVmVYZjExamc3Z2JnMllLZ1dOQ2R3VmIyUzJ5V0hTTjBlT3hPd21kWXIKSUVCekpG
|
||||||
|
eEc2MFJxSlJ1RzVIam9iemc2cy9ycUo1THFta3JhUWh6bHFPQVZLblpGOHppbG9vcDhXUXBQY3RN
|
||||||
|
cwp0cHBjczhtYkFkWHpoSTVjN0U1VVpDM2NJcEd6SE4raDZLK0F3R3ZEeVFLQmdRRDRBOTdQM29v
|
||||||
|
dGhoMHZHQmFWCnZWOXhHTm1YbW5TeUg0b29HcmJvaG1JWkkwVlhZdms5dWViSUJjbDZRMUx4WnN3
|
||||||
|
UFpRMVh5TUhpTjY1Z0E1emgKai9HZGcrdDlvcU5CZml0TUFrUTl1aWxvaXlKVWhYblk5REMvRitl
|
||||||
|
ZksycEpNbHdkci9qWEttRHpkQUZBVDgyWQpWRmJ3MlpLVi9GNEJNMUtCdDBZN0RPTmlad0tCZ1FD
|
||||||
|
OG9kZk0waytqL25VSzQ4TEV2MGtGbVNMdWdnTVlkM3hVCmZibmx0cUhFTVpJZU45OFVHK2hBWEdw
|
||||||
|
dU1Ya0JPM2Mwcm5ZRDVXZkNBRzFxT1V2ZTZzdHd6N0VuK3hWdlkvcWEKU3ZTaDRzMzhnZlBIeXhR
|
||||||
|
aGJvNWRwQTZUT3pwT0MyVi9rVXBVRUdJSmVVVllhQ05uWXNpUjRWUGVWL1lvR1htSwpQV29KbnAw
|
||||||
|
REtRS0JnQlk3cXBheDJXczVVWlp1TDJBZkNOWkhwd0hySzdqb0VPZUZkWTRrdGRpUkM5OUlsUlZP
|
||||||
|
CmUvekVZQXBnektldFVtK3kzRjVaTmVCRW81SWg0TWRyc3ZvdTRFWno5UFNqRGRpVGYzQ1ZKcThq
|
||||||
|
Z2VGWDBkTjgKR0g2WTh2K1cwY0ZjRFZ2djhYdkFaYzZOUUt0Mk8vVUM0b1JXek1nN1JtWVBKcjlR
|
||||||
|
SWJDYmVDclRBb0dBTjdZbApJbDFMSUVoYkVTaExzZ2c4N09aWnBzL0hVa2FYOWV4Y0p6aFZkcmlk
|
||||||
|
UzBkOUgxZE90Uk9XYTQwNUMrQWdTUEx0CjhDQ2xFR3RINVlPZW9Pdi93Z1hWY05WN2N6YTRJVEhh
|
||||||
|
SnFYeDZJNEpEZzB3bU44cU5RWHJPQmphRTRyU0kyY3AKNk1JZDhtWmEwTTJSQjB2cHFRdy8xUDl0
|
||||||
|
dUZJdHoySnNHd001cEdFQ2dZQVVnQVV3WENBcEtZVkZFRmxHNlBhYwpvdTBhdzdGNm1aMi9NNUcv
|
||||||
|
ek9tMHFDYnNXdGFNU09TdUEvNmlVOXB0NDBaWUFONFUvd2ZxbncyVkVoRnA3dzFNCnpZWmJCRDBx
|
||||||
|
ZVlkcDRmc1NuWXFMZmJBVmxQLzB6dmEzdkwwMlJFa25WalBVSnAvaGpKVWhBK21WN252VDZ5VjQK
|
||||||
|
cTg4SWVvOEx3Q1c1c2Jtd2lyU3Btdz09Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
|
8
src/ukify/test/example.tpm2-pcr-public.pem.base64
Normal file
8
src/ukify/test/example.tpm2-pcr-public.pem.base64
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FR
|
||||||
|
OEFNSUlCQ2dLQ0FRRUF1Wi9LbXFPVElEeUVGckFNbkhjTAo2YnArek9Na0NLQlA2Kzh3QlM1bW56
|
||||||
|
MXEyZThHMllnZGpSZWRWaHZDZko4Z2dRZTl3eXEzMmNwUGJHZ01GbEowCkJwTGJXcUJNMWh1a1JO
|
||||||
|
KzdzRC9TVk56bDd5RXVkR3JQeVI3aHVrVG0ydWxnamZHYnpPM1hZNkM5U1JYbTZVU0sKK1ZmK2xQ
|
||||||
|
aXkxNUViVEdsdDBiOHppMThqRERuZC9CdFlCclZVeHE2TGMrWHJWL2pBbEVvalUyZy9WTzFnM3Jw
|
||||||
|
WApyb3RrNFBrZXkzWVNNZTN1UGFSME44TTVFcXNVL0I0NFVyMHhJeHpIa2V0dEloeEhxSjNPNWRi
|
||||||
|
b0k3SEdhVFNoCnJ6TUpydjBvNWwyYzBTVGIrVFVpcXFSSUFXTHhhblNzRTcxdk5Zc3lBS084MmRp
|
||||||
|
OCtObDZKN0NHNzk1Slg0eTUKbndJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==
|
8
src/ukify/test/example.tpm2-pcr-public2.pem.base64
Normal file
8
src/ukify/test/example.tpm2-pcr-public2.pem.base64
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FR
|
||||||
|
OEFNSUlCQ2dLQ0FRRUF0citqSVhjZTVMbkQ2NTVCT0VmbgpLb0ZuL1RxYnJJYjk0K3g1N2ZqdEwv
|
||||||
|
L01JcnpqeE9mU3RML3hHemZLTVMrZzJBbGtnUVJEZnd3Y3RpME90SjFrCmJhV08yNTVSdnFTbE11
|
||||||
|
S0IrREhyd1JtNDNoNUl6aWtzREdsRmVKUEVYa1NmUEtsYmtrdFNialJKaklNaHd4T0cKanVKNExa
|
||||||
|
MHoxV01PZHJqeDZacE93UEUxbHU0aW5UU0dIMWZQcHhtUSthOXhJbnN3NVVBalE1OFVScjU1eDFj
|
||||||
|
MAoxMkJyYVhKV2NvaUsyVk15UU9FRG43WjhxY2orWkFQOGVVdWRUajhXcW1UKzhaRGlnbTZoUDMz
|
||||||
|
Nk5CRGY1RGMrCk94WVQzdFpqeGtSV0dWaG5zZUltanJjaXJjWUVrRjJ4bnVCSWNrOFNkbmpydkFC
|
||||||
|
SVZTRXIvTTBEb0VwcHBiejMKZndJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==
|
7
src/ukify/test/meson.build
Normal file
7
src/ukify/test/meson.build
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
|
||||||
|
if want_ukify and want_tests != 'false'
|
||||||
|
test('test-ukify',
|
||||||
|
files('test_ukify.py'),
|
||||||
|
env : test_env)
|
||||||
|
endif
|
2
src/ukify/test/setup.cfg
Normal file
2
src/ukify/test/setup.cfg
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
[tool:pytest]
|
||||||
|
addopts = --flakes
|
392
src/ukify/test/test_ukify.py
Executable file
392
src/ukify/test/test_ukify.py
Executable file
@ -0,0 +1,392 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# SPDX-License-Identifier: LGPL-2.1+
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import pathlib
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
try:
|
||||||
|
import pytest
|
||||||
|
except ImportError:
|
||||||
|
sys.exit(77)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# pyflakes: noqa
|
||||||
|
import pefile # noqa
|
||||||
|
except ImportError:
|
||||||
|
sys.exit(77)
|
||||||
|
|
||||||
|
# We import ukify.py, which is a template file. But only __version__ is
|
||||||
|
# substituted, which we don't care about here. Having the .py suffix makes it
|
||||||
|
# easier to import the file.
|
||||||
|
sys.path.append(os.path.dirname(__file__) + '/..')
|
||||||
|
import ukify
|
||||||
|
|
||||||
|
|
||||||
|
def test_guess_efi_arch():
|
||||||
|
arch = ukify.guess_efi_arch()
|
||||||
|
assert arch in ukify.EFI_ARCHES
|
||||||
|
|
||||||
|
def test_shell_join():
|
||||||
|
assert ukify.shell_join(['a', 'b', ' ']) == "a b ' '"
|
||||||
|
|
||||||
|
def test_round_up():
|
||||||
|
assert ukify.round_up(0) == 0
|
||||||
|
assert ukify.round_up(4095) == 4096
|
||||||
|
assert ukify.round_up(4096) == 4096
|
||||||
|
assert ukify.round_up(4097) == 8192
|
||||||
|
|
||||||
|
def test_parse_args_minimal():
|
||||||
|
opts = ukify.parse_args('arg1 arg2'.split())
|
||||||
|
assert opts.linux == pathlib.Path('arg1')
|
||||||
|
assert opts.initrd == [pathlib.Path('arg2')]
|
||||||
|
assert opts.os_release in (pathlib.Path('/etc/os-release'),
|
||||||
|
pathlib.Path('/usr/lib/os-release'))
|
||||||
|
|
||||||
|
def test_parse_args_many():
|
||||||
|
opts = ukify.parse_args(
|
||||||
|
['/ARG1', '///ARG2', '/ARG3 WITH SPACE',
|
||||||
|
'--cmdline=a b c',
|
||||||
|
'--os-release=K1=V1\nK2=V2',
|
||||||
|
'--devicetree=DDDDTTTT',
|
||||||
|
'--splash=splash',
|
||||||
|
'--pcrpkey=PATH',
|
||||||
|
'--uname=1.2.3',
|
||||||
|
'--stub=STUBPATH',
|
||||||
|
'--pcr-private-key=PKEY1',
|
||||||
|
'--pcr-public-key=PKEY2',
|
||||||
|
'--pcr-banks=SHA1,SHA256',
|
||||||
|
'--signing-engine=ENGINE',
|
||||||
|
'--secureboot-private-key=SBKEY',
|
||||||
|
'--secureboot-certificate=SBCERT',
|
||||||
|
'--sign-kernel',
|
||||||
|
'--no-sign-kernel',
|
||||||
|
'--tools=TOOLZ///',
|
||||||
|
'--output=OUTPUT',
|
||||||
|
'--measure',
|
||||||
|
'--no-measure',
|
||||||
|
])
|
||||||
|
assert opts.linux == pathlib.Path('/ARG1')
|
||||||
|
assert opts.initrd == [pathlib.Path('/ARG2'), pathlib.Path('/ARG3 WITH SPACE')]
|
||||||
|
assert opts.os_release == 'K1=V1\nK2=V2'
|
||||||
|
assert opts.devicetree == pathlib.Path('DDDDTTTT')
|
||||||
|
assert opts.splash == pathlib.Path('splash')
|
||||||
|
assert opts.pcrpkey == pathlib.Path('PATH')
|
||||||
|
assert opts.uname == '1.2.3'
|
||||||
|
assert opts.stub == pathlib.Path('STUBPATH')
|
||||||
|
assert opts.pcr_private_keys == [pathlib.Path('PKEY1')]
|
||||||
|
assert opts.pcr_public_keys == [pathlib.Path('PKEY2')]
|
||||||
|
assert opts.pcr_banks == ['SHA1', 'SHA256']
|
||||||
|
assert opts.signing_engine == 'ENGINE'
|
||||||
|
assert opts.sb_key == 'SBKEY'
|
||||||
|
assert opts.sb_cert == 'SBCERT'
|
||||||
|
assert opts.sign_kernel is False
|
||||||
|
assert opts.tools == pathlib.Path('TOOLZ/')
|
||||||
|
assert opts.output == pathlib.Path('OUTPUT')
|
||||||
|
assert opts.measure is False
|
||||||
|
|
||||||
|
def test_parse_sections():
|
||||||
|
opts = ukify.parse_args(
|
||||||
|
['/ARG1', '/ARG2',
|
||||||
|
'--section=test:TESTTESTTEST',
|
||||||
|
'--section=test2:@FILE',
|
||||||
|
])
|
||||||
|
|
||||||
|
assert opts.linux == pathlib.Path('/ARG1')
|
||||||
|
assert opts.initrd == [pathlib.Path('/ARG2')]
|
||||||
|
assert len(opts.sections) == 2
|
||||||
|
|
||||||
|
assert opts.sections[0].name == 'test'
|
||||||
|
assert isinstance(opts.sections[0].content, pathlib.Path)
|
||||||
|
assert opts.sections[0].tmpfile
|
||||||
|
assert opts.sections[0].offset is None
|
||||||
|
assert opts.sections[0].measure is False
|
||||||
|
|
||||||
|
assert opts.sections[1].name == 'test2'
|
||||||
|
assert opts.sections[1].content == pathlib.Path('FILE')
|
||||||
|
assert opts.sections[1].tmpfile is None
|
||||||
|
assert opts.sections[1].offset is None
|
||||||
|
assert opts.sections[1].measure is False
|
||||||
|
|
||||||
|
def test_help(capsys):
|
||||||
|
with pytest.raises(SystemExit):
|
||||||
|
ukify.parse_args(['--help'])
|
||||||
|
out = capsys.readouterr()
|
||||||
|
assert '--section' in out.out
|
||||||
|
assert not out.err
|
||||||
|
|
||||||
|
def test_help_error(capsys):
|
||||||
|
with pytest.raises(SystemExit):
|
||||||
|
ukify.parse_args(['a', 'b', '--no-such-option'])
|
||||||
|
out = capsys.readouterr()
|
||||||
|
assert not out.out
|
||||||
|
assert '--no-such-option' in out.err
|
||||||
|
assert len(out.err.splitlines()) == 1
|
||||||
|
|
||||||
|
@pytest.fixture(scope='session')
|
||||||
|
def kernel_initrd():
|
||||||
|
try:
|
||||||
|
text = subprocess.check_output(['bootctl', 'list', '--json=short'],
|
||||||
|
text=True)
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
items = json.loads(text)
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
try:
|
||||||
|
linux = f"{item['root']}{item['linux']}"
|
||||||
|
initrd = f"{item['root']}{item['initrd'][0]}"
|
||||||
|
except (KeyError, IndexError):
|
||||||
|
pass
|
||||||
|
return [linux, initrd]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def test_check_splash():
|
||||||
|
try:
|
||||||
|
# pyflakes: noqa
|
||||||
|
import PIL # noqa
|
||||||
|
except ImportError:
|
||||||
|
pytest.skip('PIL not available')
|
||||||
|
|
||||||
|
with pytest.raises(OSError):
|
||||||
|
ukify.check_splash(os.devnull)
|
||||||
|
|
||||||
|
def test_basic_operation(kernel_initrd, tmpdir):
|
||||||
|
if kernel_initrd is None:
|
||||||
|
pytest.skip('linux+initrd not found')
|
||||||
|
|
||||||
|
output = f'{tmpdir}/basic.efi'
|
||||||
|
opts = ukify.parse_args(kernel_initrd + [f'--output={output}'])
|
||||||
|
try:
|
||||||
|
ukify.check_inputs(opts)
|
||||||
|
except OSError as e:
|
||||||
|
pytest.skip(str(e))
|
||||||
|
|
||||||
|
ukify.make_uki(opts)
|
||||||
|
|
||||||
|
# let's check that objdump likes the resulting file
|
||||||
|
subprocess.check_output(['objdump', '-h', output])
|
||||||
|
|
||||||
|
def test_sections(kernel_initrd, tmpdir):
|
||||||
|
if kernel_initrd is None:
|
||||||
|
pytest.skip('linux+initrd not found')
|
||||||
|
|
||||||
|
output = f'{tmpdir}/basic.efi'
|
||||||
|
opts = ukify.parse_args([
|
||||||
|
*kernel_initrd,
|
||||||
|
f'--output={output}',
|
||||||
|
'--uname=1.2.3',
|
||||||
|
'--cmdline=ARG1 ARG2 ARG3',
|
||||||
|
'--os-release=K1=V1\nK2=V2\n',
|
||||||
|
'--section=.test:CONTENTZ',
|
||||||
|
])
|
||||||
|
|
||||||
|
try:
|
||||||
|
ukify.check_inputs(opts)
|
||||||
|
except OSError as e:
|
||||||
|
pytest.skip(str(e))
|
||||||
|
|
||||||
|
ukify.make_uki(opts)
|
||||||
|
|
||||||
|
# let's check that objdump likes the resulting file
|
||||||
|
dump = subprocess.check_output(['objdump', '-h', output], text=True)
|
||||||
|
|
||||||
|
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 unbase64(filename):
|
||||||
|
tmp = tempfile.NamedTemporaryFile()
|
||||||
|
base64.decode(filename.open('rb'), tmp)
|
||||||
|
tmp.flush()
|
||||||
|
return tmp
|
||||||
|
|
||||||
|
|
||||||
|
def test_uname_scraping(kernel_initrd):
|
||||||
|
if kernel_initrd is None:
|
||||||
|
pytest.skip('linux+initrd not found')
|
||||||
|
|
||||||
|
uname = ukify.Uname.scrape(kernel_initrd[0])
|
||||||
|
assert re.match(r'\d+\.\d+\.\d+', uname)
|
||||||
|
|
||||||
|
|
||||||
|
def test_efi_signing(kernel_initrd, tmpdir):
|
||||||
|
if kernel_initrd is None:
|
||||||
|
pytest.skip('linux+initrd not found')
|
||||||
|
if not shutil.which('sbsign'):
|
||||||
|
pytest.skip('sbsign not found')
|
||||||
|
|
||||||
|
ourdir = pathlib.Path(__file__).parent
|
||||||
|
cert = unbase64(ourdir / 'example.signing.crt.base64')
|
||||||
|
key = unbase64(ourdir / 'example.signing.key.base64')
|
||||||
|
|
||||||
|
output = f'{tmpdir}/signed.efi'
|
||||||
|
opts = ukify.parse_args([
|
||||||
|
*kernel_initrd,
|
||||||
|
f'--output={output}',
|
||||||
|
'--uname=1.2.3',
|
||||||
|
'--cmdline=ARG1 ARG2 ARG3',
|
||||||
|
f'--secureboot-certificate={cert.name}',
|
||||||
|
f'--secureboot-private-key={key.name}',
|
||||||
|
])
|
||||||
|
|
||||||
|
try:
|
||||||
|
ukify.check_inputs(opts)
|
||||||
|
except OSError as e:
|
||||||
|
pytest.skip(str(e))
|
||||||
|
|
||||||
|
ukify.make_uki(opts)
|
||||||
|
|
||||||
|
if shutil.which('sbverify'):
|
||||||
|
# let's check that sbverify likes the resulting file
|
||||||
|
dump = subprocess.check_output([
|
||||||
|
'sbverify',
|
||||||
|
'--cert', cert.name,
|
||||||
|
output,
|
||||||
|
], text=True)
|
||||||
|
|
||||||
|
assert 'Signature verification OK' in dump
|
||||||
|
|
||||||
|
def test_pcr_signing(kernel_initrd, tmpdir):
|
||||||
|
if kernel_initrd is None:
|
||||||
|
pytest.skip('linux+initrd not found')
|
||||||
|
if os.getuid() != 0:
|
||||||
|
pytest.skip('must be root to access tpm2')
|
||||||
|
if subprocess.call(['systemd-creds', 'has-tpm2', '-q']) != 0:
|
||||||
|
pytest.skip('tpm2 is not available')
|
||||||
|
|
||||||
|
ourdir = pathlib.Path(__file__).parent
|
||||||
|
pub = unbase64(ourdir / 'example.tpm2-pcr-public.pem.base64')
|
||||||
|
priv = unbase64(ourdir / 'example.tpm2-pcr-private.pem.base64')
|
||||||
|
|
||||||
|
output = f'{tmpdir}/signed.efi'
|
||||||
|
opts = ukify.parse_args([
|
||||||
|
*kernel_initrd,
|
||||||
|
f'--output={output}',
|
||||||
|
'--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
|
||||||
|
f'--pcrpkey={pub.name}',
|
||||||
|
f'--pcr-public-key={pub.name}',
|
||||||
|
f'--pcr-private-key={priv.name}',
|
||||||
|
])
|
||||||
|
|
||||||
|
try:
|
||||||
|
ukify.check_inputs(opts)
|
||||||
|
except OSError as e:
|
||||||
|
pytest.skip(str(e))
|
||||||
|
|
||||||
|
ukify.make_uki(opts)
|
||||||
|
|
||||||
|
# let's check that objdump likes the resulting file
|
||||||
|
dump = subprocess.check_output(['objdump', '-h', output], text=True)
|
||||||
|
|
||||||
|
for sect in 'text osrel cmdline linux initrd uname pcrsig'.split():
|
||||||
|
assert re.search(fr'^\s*\d+\s+.{sect}\s+0', dump, re.MULTILINE)
|
||||||
|
|
||||||
|
# objcopy fails when called without an output argument (EPERM).
|
||||||
|
# It also fails when called with /dev/null (file truncated).
|
||||||
|
# It also fails when called with /dev/zero (because it reads the
|
||||||
|
# output file, infinitely in this case.)
|
||||||
|
# So let's just call it with a dummy output argument.
|
||||||
|
subprocess.check_call([
|
||||||
|
'objcopy',
|
||||||
|
*(f'--dump-section=.{n}={tmpdir}/out.{n}' for n in (
|
||||||
|
'pcrpkey', 'pcrsig', 'osrel', 'uname', 'cmdline')),
|
||||||
|
output,
|
||||||
|
tmpdir / 'dummy',
|
||||||
|
],
|
||||||
|
text=True)
|
||||||
|
|
||||||
|
assert open(tmpdir / 'out.pcrpkey').read() == open(pub.name).read()
|
||||||
|
assert open(tmpdir / 'out.osrel').read() == 'ID=foobar\n'
|
||||||
|
assert open(tmpdir / 'out.uname').read() == '1.2.3'
|
||||||
|
assert open(tmpdir / 'out.cmdline').read() == 'ARG1 ARG2 ARG3'
|
||||||
|
sig = open(tmpdir / 'out.pcrsig').read()
|
||||||
|
sig = json.loads(sig)
|
||||||
|
assert list(sig.keys()) == ['sha1']
|
||||||
|
assert len(sig['sha1']) == 4 # four items for four phases
|
||||||
|
|
||||||
|
def test_pcr_signing2(kernel_initrd, tmpdir):
|
||||||
|
if kernel_initrd is None:
|
||||||
|
pytest.skip('linux+initrd not found')
|
||||||
|
if os.getuid() != 0:
|
||||||
|
pytest.skip('must be root to access tpm2')
|
||||||
|
if subprocess.call(['systemd-creds', 'has-tpm2', '-q']) != 0:
|
||||||
|
pytest.skip('tpm2 is not available')
|
||||||
|
|
||||||
|
ourdir = pathlib.Path(__file__).parent
|
||||||
|
pub = unbase64(ourdir / 'example.tpm2-pcr-public.pem.base64')
|
||||||
|
priv = unbase64(ourdir / 'example.tpm2-pcr-private.pem.base64')
|
||||||
|
pub2 = unbase64(ourdir / 'example.tpm2-pcr-public2.pem.base64')
|
||||||
|
priv2 = unbase64(ourdir / 'example.tpm2-pcr-private2.pem.base64')
|
||||||
|
|
||||||
|
# simulate a microcode file
|
||||||
|
with open(f'{tmpdir}/microcode', 'wb') as microcode:
|
||||||
|
microcode.write(b'1234567890')
|
||||||
|
|
||||||
|
output = f'{tmpdir}/signed.efi'
|
||||||
|
opts = ukify.parse_args([
|
||||||
|
kernel_initrd[0], microcode.name, kernel_initrd[1],
|
||||||
|
f'--output={output}',
|
||||||
|
'--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
|
||||||
|
f'--pcrpkey={pub2.name}',
|
||||||
|
f'--pcr-public-key={pub.name}',
|
||||||
|
f'--pcr-private-key={priv.name}',
|
||||||
|
'--phases=enter-initrd enter-initrd:leave-initrd',
|
||||||
|
f'--pcr-public-key={pub2.name}',
|
||||||
|
f'--pcr-private-key={priv2.name}',
|
||||||
|
'--phases=sysinit ready shutdown final', # yes, those phase paths are not reachable
|
||||||
|
])
|
||||||
|
|
||||||
|
try:
|
||||||
|
ukify.check_inputs(opts)
|
||||||
|
except OSError as e:
|
||||||
|
pytest.skip(str(e))
|
||||||
|
|
||||||
|
ukify.make_uki(opts)
|
||||||
|
|
||||||
|
# let's check that objdump likes the resulting file
|
||||||
|
dump = subprocess.check_output(['objdump', '-h', output], text=True)
|
||||||
|
|
||||||
|
for sect in 'text osrel cmdline linux initrd uname pcrsig'.split():
|
||||||
|
assert re.search(fr'^\s*\d+\s+.{sect}\s+0', dump, re.MULTILINE)
|
||||||
|
|
||||||
|
subprocess.check_call([
|
||||||
|
'objcopy',
|
||||||
|
*(f'--dump-section=.{n}={tmpdir}/out.{n}' for n in (
|
||||||
|
'pcrpkey', 'pcrsig', 'osrel', 'uname', 'cmdline', 'initrd')),
|
||||||
|
output,
|
||||||
|
tmpdir / 'dummy',
|
||||||
|
],
|
||||||
|
text=True)
|
||||||
|
|
||||||
|
assert open(tmpdir / 'out.pcrpkey').read() == open(pub2.name).read()
|
||||||
|
assert open(tmpdir / 'out.osrel').read() == 'ID=foobar\n'
|
||||||
|
assert open(tmpdir / 'out.uname').read() == '1.2.3'
|
||||||
|
assert open(tmpdir / 'out.cmdline').read() == 'ARG1 ARG2 ARG3'
|
||||||
|
assert open(tmpdir / 'out.initrd', 'rb').read(10) == b'1234567890'
|
||||||
|
|
||||||
|
sig = open(tmpdir / 'out.pcrsig').read()
|
||||||
|
sig = json.loads(sig)
|
||||||
|
assert list(sig.keys()) == ['sha1']
|
||||||
|
assert len(sig['sha1']) == 6 # six items for six phases paths
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
pytest.main([__file__, '-v'])
|
727
src/ukify/ukify.py
Executable file
727
src/ukify/ukify.py
Executable file
@ -0,0 +1,727 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# SPDX-License-Identifier: LGPL-2.1+
|
||||||
|
|
||||||
|
# pylint: disable=missing-docstring,invalid-name,import-outside-toplevel
|
||||||
|
# pylint: disable=consider-using-with,unspecified-encoding,line-too-long
|
||||||
|
# pylint: disable=too-many-locals,too-many-statements,too-many-return-statements
|
||||||
|
# pylint: disable=too-many-branches
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import collections
|
||||||
|
import dataclasses
|
||||||
|
import fnmatch
|
||||||
|
import itertools
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import pathlib
|
||||||
|
import re
|
||||||
|
import shlex
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
import typing
|
||||||
|
|
||||||
|
|
||||||
|
__version__ = '{{GIT_VERSION}}'
|
||||||
|
|
||||||
|
EFI_ARCH_MAP = {
|
||||||
|
# host_arch glob : [efi_arch, 32_bit_efi_arch if mixed mode is supported]
|
||||||
|
'x86_64' : ['x64', 'ia32'],
|
||||||
|
'i[3456]86' : ['ia32'],
|
||||||
|
'aarch64' : ['aa64'],
|
||||||
|
'arm[45678]*l' : ['arm'],
|
||||||
|
'riscv64' : ['riscv64'],
|
||||||
|
}
|
||||||
|
EFI_ARCHES: list[str] = sum(EFI_ARCH_MAP.values(), [])
|
||||||
|
|
||||||
|
def guess_efi_arch():
|
||||||
|
arch = os.uname().machine
|
||||||
|
|
||||||
|
for glob, mapping in EFI_ARCH_MAP.items():
|
||||||
|
if fnmatch.fnmatch(arch, glob):
|
||||||
|
efi_arch, *fallback = mapping
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise ValueError(f'Unsupported architecture {arch}')
|
||||||
|
|
||||||
|
# This makes sense only on some architectures, but it also probably doesn't
|
||||||
|
# hurt on others, so let's just apply the check everywhere.
|
||||||
|
if fallback:
|
||||||
|
fw_platform_size = pathlib.Path('/sys/firmware/efi/fw_platform_size')
|
||||||
|
try:
|
||||||
|
size = fw_platform_size.read_text().strip()
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if int(size) == 32:
|
||||||
|
efi_arch = fallback[0]
|
||||||
|
|
||||||
|
print(f'Host arch {arch!r}, EFI arch {efi_arch!r}')
|
||||||
|
return efi_arch
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
def pe_executable_size(filename):
|
||||||
|
import pefile
|
||||||
|
|
||||||
|
pe = pefile.PE(filename)
|
||||||
|
section = pe.sections[-1]
|
||||||
|
return section.VirtualAddress + section.Misc_VirtualSize
|
||||||
|
|
||||||
|
|
||||||
|
def round_up(x, blocksize=4096):
|
||||||
|
return (x + blocksize - 1) // blocksize * blocksize
|
||||||
|
|
||||||
|
|
||||||
|
def maybe_decompress(filename):
|
||||||
|
"""Decompress file if compressed. Return contents."""
|
||||||
|
f = open(filename, 'rb')
|
||||||
|
start = f.read(4)
|
||||||
|
f.seek(0)
|
||||||
|
|
||||||
|
if start.startswith(b'\x7fELF'):
|
||||||
|
# not compressed
|
||||||
|
return f.read()
|
||||||
|
|
||||||
|
if start.startswith(b'\x1f\x8b'):
|
||||||
|
import gzip
|
||||||
|
return gzip.open(f).read()
|
||||||
|
|
||||||
|
if start.startswith(b'\x28\xb5\x2f\xfd'):
|
||||||
|
import zstd
|
||||||
|
return zstd.uncompress(f.read())
|
||||||
|
|
||||||
|
if start.startswith(b'\x02\x21\x4c\x18'):
|
||||||
|
import lz4.frame
|
||||||
|
return lz4.frame.decompress(f.read())
|
||||||
|
|
||||||
|
if start.startswith(b'\x04\x22\x4d\x18'):
|
||||||
|
print('Newer lz4 stream format detected! This may not boot!')
|
||||||
|
import lz4.frame
|
||||||
|
return lz4.frame.decompress(f.read())
|
||||||
|
|
||||||
|
if start.startswith(b'\x89LZO'):
|
||||||
|
# python3-lzo is not packaged for Fedora
|
||||||
|
raise NotImplementedError('lzo decompression not implemented')
|
||||||
|
|
||||||
|
if start.startswith(b'BZh'):
|
||||||
|
import bz2
|
||||||
|
return bz2.open(f).read()
|
||||||
|
|
||||||
|
if start.startswith(b'\x5d\x00\x00'):
|
||||||
|
import lzma
|
||||||
|
return lzma.open(f).read()
|
||||||
|
|
||||||
|
raise NotImplementedError(f'unknown file format (starts with {start})')
|
||||||
|
|
||||||
|
|
||||||
|
class Uname:
|
||||||
|
# This class is here purely as a namespace for the functions
|
||||||
|
|
||||||
|
VERSION_PATTERN = r'(?P<version>[a-z0-9._-]+) \([^ )]+\) (?:#.*)'
|
||||||
|
|
||||||
|
NOTES_PATTERN = r'^\s+Linux\s+0x[0-9a-f]+\s+OPEN\n\s+description data: (?P<version>[0-9a-f ]+)\s*$'
|
||||||
|
|
||||||
|
# Linux version 6.0.8-300.fc37.ppc64le (mockbuild@buildvm-ppc64le-03.iad2.fedoraproject.org)
|
||||||
|
# (gcc (GCC) 12.2.1 20220819 (Red Hat 12.2.1-2), GNU ld version 2.38-24.fc37)
|
||||||
|
# #1 SMP Fri Nov 11 14:39:11 UTC 2022
|
||||||
|
TEXT_PATTERN = rb'Linux version (?P<version>\d\.\S+) \('
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def scrape_x86(cls, filename, opts=None):
|
||||||
|
# Based on https://gitlab.archlinux.org/archlinux/mkinitcpio/mkinitcpio/-/blob/master/functions#L136
|
||||||
|
# and https://www.kernel.org/doc/html/latest/x86/boot.html#the-real-mode-kernel-header
|
||||||
|
with open(filename, 'rb') as f:
|
||||||
|
f.seek(0x202)
|
||||||
|
magic = f.read(4)
|
||||||
|
if magic != b'HdrS':
|
||||||
|
raise ValueError('Real-Mode Kernel Header magic not found')
|
||||||
|
f.seek(0x20E)
|
||||||
|
offset = f.read(1)[0] + f.read(1)[0]*256 # Pointer to kernel version string
|
||||||
|
f.seek(0x200 + offset)
|
||||||
|
text = f.read(128)
|
||||||
|
text = text.split(b'\0', maxsplit=1)[0]
|
||||||
|
text = text.decode()
|
||||||
|
|
||||||
|
if not (m := re.match(cls.VERSION_PATTERN, text)):
|
||||||
|
raise ValueError(f'Cannot parse version-host-release uname string: {text!r}')
|
||||||
|
return m.group('version')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def scrape_elf(cls, filename, opts=None):
|
||||||
|
readelf = find_tool('readelf', opts=opts)
|
||||||
|
|
||||||
|
cmd = [
|
||||||
|
readelf,
|
||||||
|
'--notes',
|
||||||
|
filename,
|
||||||
|
]
|
||||||
|
|
||||||
|
print('+', shell_join(cmd))
|
||||||
|
notes = subprocess.check_output(cmd, text=True)
|
||||||
|
|
||||||
|
if not (m := re.search(cls.NOTES_PATTERN, notes, re.MULTILINE)):
|
||||||
|
raise ValueError('Cannot find Linux version note')
|
||||||
|
|
||||||
|
text = ''.join(chr(int(c, 16)) for c in m.group('version').split())
|
||||||
|
return text.rstrip('\0')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def scrape_generic(cls, filename, opts=None):
|
||||||
|
# import libarchive
|
||||||
|
# libarchive-c fails with
|
||||||
|
# ArchiveError: Unrecognized archive format (errno=84, retcode=-30, archive_p=94705420454656)
|
||||||
|
|
||||||
|
# Based on https://gitlab.archlinux.org/archlinux/mkinitcpio/mkinitcpio/-/blob/master/functions#L209
|
||||||
|
|
||||||
|
text = maybe_decompress(filename)
|
||||||
|
if not (m := re.search(cls.TEXT_PATTERN, text)):
|
||||||
|
raise ValueError(f'Cannot find {cls.TEXT_PATTERN!r} in {filename}')
|
||||||
|
|
||||||
|
return m.group('version').decode()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def scrape(cls, filename, opts=None):
|
||||||
|
for func in (cls.scrape_x86, cls.scrape_elf, cls.scrape_generic):
|
||||||
|
try:
|
||||||
|
version = func(filename, opts=opts)
|
||||||
|
print(f'Found uname version: {version}')
|
||||||
|
return version
|
||||||
|
except ValueError as e:
|
||||||
|
print(str(e))
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class Section:
|
||||||
|
name: str
|
||||||
|
content: pathlib.Path
|
||||||
|
tmpfile: typing.IO | None = None
|
||||||
|
flags: list[str] | None = dataclasses.field(default=None)
|
||||||
|
offset: int | None = None
|
||||||
|
measure: bool = False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create(cls, name, contents, flags=None, measure=False):
|
||||||
|
if isinstance(contents, str | bytes):
|
||||||
|
mode = 'wt' if isinstance(contents, str) else 'wb'
|
||||||
|
tmp = tempfile.NamedTemporaryFile(mode=mode, prefix=f'tmp{name}')
|
||||||
|
tmp.write(contents)
|
||||||
|
tmp.flush()
|
||||||
|
contents = pathlib.Path(tmp.name)
|
||||||
|
else:
|
||||||
|
tmp = None
|
||||||
|
|
||||||
|
return cls(name, contents, tmpfile=tmp, flags=flags, measure=measure)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse_arg(cls, s):
|
||||||
|
try:
|
||||||
|
name, contents, *rest = s.split(':')
|
||||||
|
except ValueError as e:
|
||||||
|
raise ValueError(f'Cannot parse section spec (name or contents missing): {s!r}') from e
|
||||||
|
if rest:
|
||||||
|
raise ValueError(f'Cannot parse section spec (extraneous parameters): {s!r}')
|
||||||
|
|
||||||
|
if contents.startswith('@'):
|
||||||
|
contents = pathlib.Path(contents[1:])
|
||||||
|
|
||||||
|
return cls.create(name, contents)
|
||||||
|
|
||||||
|
def size(self):
|
||||||
|
return self.content.stat().st_size
|
||||||
|
|
||||||
|
def check_name(self):
|
||||||
|
# PE section names with more than 8 characters are legal, but our stub does
|
||||||
|
# not support them.
|
||||||
|
if not self.name.isascii() or not self.name.isprintable():
|
||||||
|
raise ValueError(f'Bad section name: {self.name!r}')
|
||||||
|
if len(self.name) > 8:
|
||||||
|
raise ValueError(f'Section name too long: {self.name!r}')
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class UKI:
|
||||||
|
executable: list[pathlib.Path|str]
|
||||||
|
sections: list[Section] = dataclasses.field(default_factory=list, init=False)
|
||||||
|
offset: int | None = dataclasses.field(default=None, init=False)
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
self.offset = round_up(pe_executable_size(self.executable))
|
||||||
|
|
||||||
|
def add_section(self, section):
|
||||||
|
assert self.offset
|
||||||
|
assert section.offset is None
|
||||||
|
|
||||||
|
if section.name in [s.name for s in self.sections]:
|
||||||
|
raise ValueError(f'Duplicate section {section.name}')
|
||||||
|
|
||||||
|
section.offset = self.offset
|
||||||
|
self.offset += round_up(section.size())
|
||||||
|
self.sections += [section]
|
||||||
|
|
||||||
|
|
||||||
|
def parse_banks(s):
|
||||||
|
banks = re.split(r',|\s+', s)
|
||||||
|
# TODO: do some sanity checking here
|
||||||
|
return banks
|
||||||
|
|
||||||
|
|
||||||
|
KNOWN_PHASES = (
|
||||||
|
'enter-initrd',
|
||||||
|
'leave-initrd',
|
||||||
|
'sysinit',
|
||||||
|
'ready',
|
||||||
|
'shutdown',
|
||||||
|
'final',
|
||||||
|
)
|
||||||
|
|
||||||
|
def parse_phase_paths(s):
|
||||||
|
# Split on commas or whitespace here. Commas might be hard to parse visually.
|
||||||
|
paths = re.split(r',|\s+', s)
|
||||||
|
|
||||||
|
for path in paths:
|
||||||
|
for phase in path.split(':'):
|
||||||
|
if phase not in KNOWN_PHASES:
|
||||||
|
raise argparse.ArgumentTypeError(f'Unknown boot phase {phase!r} ({path=})')
|
||||||
|
|
||||||
|
return paths
|
||||||
|
|
||||||
|
|
||||||
|
def check_splash(filename):
|
||||||
|
if filename is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
# import is delayed, to avoid import when the splash image is not used
|
||||||
|
try:
|
||||||
|
from PIL import Image
|
||||||
|
except ImportError:
|
||||||
|
return
|
||||||
|
|
||||||
|
img = Image.open(filename, formats=['BMP'])
|
||||||
|
print(f'Splash image {filename} is {img.width}×{img.height} pixels')
|
||||||
|
|
||||||
|
|
||||||
|
def check_inputs(opts):
|
||||||
|
for name, value in vars(opts).items():
|
||||||
|
if name in {'output', 'tools'}:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not isinstance(value, pathlib.Path):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Open file to check that we can read it, or generate an exception
|
||||||
|
value.open().close()
|
||||||
|
|
||||||
|
check_splash(opts.splash)
|
||||||
|
|
||||||
|
|
||||||
|
def find_tool(name, fallback=None, opts=None):
|
||||||
|
if opts and opts.tools:
|
||||||
|
tool = opts.tools / name
|
||||||
|
if tool.exists():
|
||||||
|
return tool
|
||||||
|
|
||||||
|
return fallback or name
|
||||||
|
|
||||||
|
|
||||||
|
def combine_signatures(pcrsigs):
|
||||||
|
combined = collections.defaultdict(list)
|
||||||
|
for pcrsig in pcrsigs:
|
||||||
|
for bank, sigs in pcrsig.items():
|
||||||
|
for sig in sigs:
|
||||||
|
if sig not in combined[bank]:
|
||||||
|
combined[bank] += [sig]
|
||||||
|
return json.dumps(combined)
|
||||||
|
|
||||||
|
|
||||||
|
def call_systemd_measure(uki, linux, opts):
|
||||||
|
measure_tool = find_tool('systemd-measure',
|
||||||
|
'/usr/lib/systemd/systemd-measure',
|
||||||
|
opts=opts)
|
||||||
|
|
||||||
|
banks = opts.pcr_banks or ()
|
||||||
|
|
||||||
|
# PCR measurement
|
||||||
|
|
||||||
|
if opts.measure:
|
||||||
|
pp_groups = opts.phase_path_groups or []
|
||||||
|
|
||||||
|
cmd = [
|
||||||
|
measure_tool,
|
||||||
|
'calculate',
|
||||||
|
f'--linux={linux}',
|
||||||
|
*(f"--{s.name.removeprefix('.')}={s.content}"
|
||||||
|
for s in uki.sections
|
||||||
|
if s.measure),
|
||||||
|
*(f'--bank={bank}'
|
||||||
|
for bank in banks),
|
||||||
|
# For measurement, the keys are not relevant, so we can lump all the phase paths
|
||||||
|
# into one call to systemd-measure calculate.
|
||||||
|
*(f'--phase={phase_path}'
|
||||||
|
for phase_path in itertools.chain.from_iterable(pp_groups)),
|
||||||
|
]
|
||||||
|
|
||||||
|
print('+', shell_join(cmd))
|
||||||
|
subprocess.check_call(cmd)
|
||||||
|
|
||||||
|
# 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 = [
|
||||||
|
measure_tool,
|
||||||
|
'sign',
|
||||||
|
f'--linux={linux}',
|
||||||
|
*(f"--{s.name.removeprefix('.')}={s.content}"
|
||||||
|
for s in uki.sections
|
||||||
|
if s.measure),
|
||||||
|
*(f'--bank={bank}'
|
||||||
|
for bank in banks),
|
||||||
|
]
|
||||||
|
|
||||||
|
for priv_key, pub_key, group in zip(opts.pcr_private_keys,
|
||||||
|
pub_keys,
|
||||||
|
pp_groups):
|
||||||
|
extra = [f'--private-key={priv_key}']
|
||||||
|
if pub_key:
|
||||||
|
extra += [f'--public-key={pub_key}']
|
||||||
|
extra += [f'--phase={phase_path}' for phase_path in group or ()]
|
||||||
|
|
||||||
|
print('+', shell_join(cmd + extra))
|
||||||
|
pcrsig = subprocess.check_output(cmd + extra, text=True)
|
||||||
|
pcrsig = json.loads(pcrsig)
|
||||||
|
pcrsigs += [pcrsig]
|
||||||
|
|
||||||
|
combined = combine_signatures(pcrsigs)
|
||||||
|
uki.add_section(Section.create('.pcrsig', combined))
|
||||||
|
|
||||||
|
|
||||||
|
def join_initrds(initrds):
|
||||||
|
match initrds:
|
||||||
|
case []:
|
||||||
|
return None
|
||||||
|
case [initrd]:
|
||||||
|
return initrd
|
||||||
|
case multiple:
|
||||||
|
seq = []
|
||||||
|
for file in multiple:
|
||||||
|
initrd = file.read_bytes()
|
||||||
|
padding = b'\0' * round_up(len(initrd), 4) # pad to 32 bit alignment
|
||||||
|
seq += [initrd, padding]
|
||||||
|
|
||||||
|
return b''.join(seq)
|
||||||
|
|
||||||
|
assert False
|
||||||
|
|
||||||
|
|
||||||
|
def make_uki(opts):
|
||||||
|
# kernel payload signing
|
||||||
|
|
||||||
|
sbsign_tool = find_tool('sbsign', opts=opts)
|
||||||
|
sbsign_invocation = [
|
||||||
|
sbsign_tool,
|
||||||
|
'--key', opts.sb_key,
|
||||||
|
'--cert', opts.sb_cert,
|
||||||
|
]
|
||||||
|
|
||||||
|
if opts.signing_engine is not None:
|
||||||
|
sbsign_invocation += ['--engine', opts.signing_engine]
|
||||||
|
|
||||||
|
sign_kernel = opts.sign_kernel
|
||||||
|
if sign_kernel is None and opts.sb_key:
|
||||||
|
# figure out if we should sign the kernel
|
||||||
|
sbverify_tool = find_tool('sbverify', opts=opts)
|
||||||
|
|
||||||
|
cmd = [
|
||||||
|
sbverify_tool,
|
||||||
|
'--list',
|
||||||
|
opts.linux,
|
||||||
|
]
|
||||||
|
|
||||||
|
print('+', shell_join(cmd))
|
||||||
|
info = subprocess.check_output(cmd, text=True)
|
||||||
|
|
||||||
|
# sbverify has wonderful API
|
||||||
|
if 'No signature table present' in info:
|
||||||
|
sign_kernel = True
|
||||||
|
|
||||||
|
if sign_kernel:
|
||||||
|
linux_signed = tempfile.NamedTemporaryFile(prefix='linux-signed')
|
||||||
|
linux = linux_signed.name
|
||||||
|
|
||||||
|
cmd = [
|
||||||
|
*sbsign_invocation,
|
||||||
|
opts.linux,
|
||||||
|
'--output', linux,
|
||||||
|
]
|
||||||
|
|
||||||
|
print('+', shell_join(cmd))
|
||||||
|
subprocess.check_call(cmd)
|
||||||
|
else:
|
||||||
|
linux = opts.linux
|
||||||
|
|
||||||
|
if opts.uname is None:
|
||||||
|
print('Kernel version not specified, starting autodetection 😖.')
|
||||||
|
opts.uname = Uname.scrape(opts.linux, opts=opts)
|
||||||
|
|
||||||
|
uki = UKI(opts.stub)
|
||||||
|
initrd = join_initrds(opts.initrd)
|
||||||
|
|
||||||
|
# TODO: derive public key from from opts.pcr_private_keys?
|
||||||
|
pcrpkey = opts.pcrpkey
|
||||||
|
if pcrpkey is None:
|
||||||
|
if opts.pcr_public_keys and len(opts.pcr_public_keys) == 1:
|
||||||
|
pcrpkey = opts.pcr_public_keys[0]
|
||||||
|
|
||||||
|
sections = [
|
||||||
|
# name, content, measure?
|
||||||
|
('.osrel', opts.os_release, True ),
|
||||||
|
('.cmdline', opts.cmdline, True ),
|
||||||
|
('.dtb', opts.devicetree, True ),
|
||||||
|
('.splash', opts.splash, True ),
|
||||||
|
('.pcrpkey', pcrpkey, True ),
|
||||||
|
('.initrd', initrd, True ),
|
||||||
|
('.uname', opts.uname, False),
|
||||||
|
|
||||||
|
# linux shall be last to leave breathing room for decompression.
|
||||||
|
# We'll add it later.
|
||||||
|
]
|
||||||
|
|
||||||
|
for name, content, measure in sections:
|
||||||
|
if content:
|
||||||
|
uki.add_section(Section.create(name, content, measure=measure))
|
||||||
|
|
||||||
|
# systemd-measure doesn't know about those extra sections
|
||||||
|
for section in opts.sections:
|
||||||
|
uki.add_section(section)
|
||||||
|
|
||||||
|
# PCR measurement and signing
|
||||||
|
|
||||||
|
call_systemd_measure(uki, linux, opts=opts)
|
||||||
|
|
||||||
|
# UKI creation
|
||||||
|
|
||||||
|
uki.add_section(
|
||||||
|
Section.create('.linux', linux, measure=True,
|
||||||
|
flags=['code', 'readonly']))
|
||||||
|
|
||||||
|
if opts.sb_key:
|
||||||
|
unsigned = tempfile.NamedTemporaryFile(prefix='uki')
|
||||||
|
output = unsigned.name
|
||||||
|
else:
|
||||||
|
output = opts.output
|
||||||
|
|
||||||
|
objcopy_tool = find_tool('objcopy', opts=opts)
|
||||||
|
|
||||||
|
cmd = [
|
||||||
|
objcopy_tool,
|
||||||
|
opts.stub,
|
||||||
|
*itertools.chain.from_iterable(
|
||||||
|
('--add-section', f'{s.name}={s.content}',
|
||||||
|
'--change-section-vma', f'{s.name}=0x{s.offset:x}')
|
||||||
|
for s in uki.sections),
|
||||||
|
*itertools.chain.from_iterable(
|
||||||
|
('--set-section-flags', f"{s.name}={','.join(s.flags)}")
|
||||||
|
for s in uki.sections
|
||||||
|
if s.flags is not None),
|
||||||
|
output,
|
||||||
|
]
|
||||||
|
print('+', shell_join(cmd))
|
||||||
|
subprocess.check_call(cmd)
|
||||||
|
|
||||||
|
# UKI signing
|
||||||
|
|
||||||
|
if opts.sb_key:
|
||||||
|
cmd = [
|
||||||
|
*sbsign_invocation,
|
||||||
|
unsigned.name,
|
||||||
|
'--output', opts.output,
|
||||||
|
]
|
||||||
|
print('+', shell_join(cmd))
|
||||||
|
subprocess.check_call(cmd)
|
||||||
|
|
||||||
|
# We end up with no executable bits, let's reapply them
|
||||||
|
os.umask(umask := os.umask(0))
|
||||||
|
os.chmod(opts.output, 0o777 & ~umask)
|
||||||
|
|
||||||
|
print(f"Wrote {'signed' if opts.sb_key else 'unsigned'} {opts.output}")
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args(args=None):
|
||||||
|
p = argparse.ArgumentParser(
|
||||||
|
description='Build and sign Unified Kernel Images',
|
||||||
|
allow_abbrev=False,
|
||||||
|
usage='''\
|
||||||
|
usage: ukify [options…] linux initrd…
|
||||||
|
ukify -h | --help
|
||||||
|
''')
|
||||||
|
|
||||||
|
# Suppress printing of usage synopsis on errors
|
||||||
|
p.error = lambda message: p.exit(2, f'{p.prog}: error: {message}\n')
|
||||||
|
|
||||||
|
p.add_argument('linux',
|
||||||
|
type=pathlib.Path,
|
||||||
|
help='vmlinuz file [.linux section]')
|
||||||
|
p.add_argument('initrd',
|
||||||
|
type=pathlib.Path,
|
||||||
|
nargs='*',
|
||||||
|
help='initrd files [.initrd section]')
|
||||||
|
|
||||||
|
p.add_argument('--cmdline',
|
||||||
|
metavar='TEXT|@PATH',
|
||||||
|
help='kernel command line [.cmdline section]')
|
||||||
|
|
||||||
|
p.add_argument('--os-release',
|
||||||
|
metavar='TEXT|@PATH',
|
||||||
|
help='path to os-release file [.osrel section]')
|
||||||
|
|
||||||
|
p.add_argument('--devicetree',
|
||||||
|
metavar='PATH',
|
||||||
|
type=pathlib.Path,
|
||||||
|
help='Device Tree file [.dtb section]')
|
||||||
|
p.add_argument('--splash',
|
||||||
|
metavar='BMP',
|
||||||
|
type=pathlib.Path,
|
||||||
|
help='splash image bitmap file [.splash section]')
|
||||||
|
p.add_argument('--pcrpkey',
|
||||||
|
metavar='KEY',
|
||||||
|
type=pathlib.Path,
|
||||||
|
help='embedded public key to seal secrets to [.pcrpkey section]')
|
||||||
|
p.add_argument('--uname',
|
||||||
|
metavar='VERSION',
|
||||||
|
help='"uname -r" information [.uname section]')
|
||||||
|
|
||||||
|
p.add_argument('--efi-arch',
|
||||||
|
metavar='ARCH',
|
||||||
|
choices=('ia32', 'x64', 'arm', 'aa64', 'riscv64'),
|
||||||
|
help='target EFI architecture')
|
||||||
|
|
||||||
|
p.add_argument('--stub',
|
||||||
|
type=pathlib.Path,
|
||||||
|
help='path the the sd-stub file [.text,.data,… sections]')
|
||||||
|
|
||||||
|
p.add_argument('--section',
|
||||||
|
dest='sections',
|
||||||
|
metavar='NAME:TEXT|@PATH',
|
||||||
|
type=Section.parse_arg,
|
||||||
|
action='append',
|
||||||
|
default=[],
|
||||||
|
help='additional section as name and contents [NAME section]')
|
||||||
|
|
||||||
|
p.add_argument('--pcr-private-key',
|
||||||
|
dest='pcr_private_keys',
|
||||||
|
metavar='PATH',
|
||||||
|
type=pathlib.Path,
|
||||||
|
action='append',
|
||||||
|
help='private part of the keypair for signing PCR signatures')
|
||||||
|
p.add_argument('--pcr-public-key',
|
||||||
|
dest='pcr_public_keys',
|
||||||
|
metavar='PATH',
|
||||||
|
type=pathlib.Path,
|
||||||
|
action='append',
|
||||||
|
help='public part of the keypair for signing PCR signatures')
|
||||||
|
p.add_argument('--phases',
|
||||||
|
dest='phase_path_groups',
|
||||||
|
metavar='PHASE-PATH…',
|
||||||
|
type=parse_phase_paths,
|
||||||
|
action='append',
|
||||||
|
help='phase-paths to create signatures for')
|
||||||
|
|
||||||
|
p.add_argument('--pcr-banks',
|
||||||
|
metavar='BANK…',
|
||||||
|
type=parse_banks)
|
||||||
|
|
||||||
|
p.add_argument('--signing-engine',
|
||||||
|
metavar='ENGINE',
|
||||||
|
help='OpenSSL engine to use for signing')
|
||||||
|
p.add_argument('--secureboot-private-key',
|
||||||
|
dest='sb_key',
|
||||||
|
help='path to key file or engine-specific designation for SB signing')
|
||||||
|
p.add_argument('--secureboot-certificate',
|
||||||
|
dest='sb_cert',
|
||||||
|
help='path to certificate file or engine-specific designation for SB signing')
|
||||||
|
|
||||||
|
p.add_argument('--sign-kernel',
|
||||||
|
action=argparse.BooleanOptionalAction,
|
||||||
|
help='Sign the embedded kernel')
|
||||||
|
|
||||||
|
p.add_argument('--tools',
|
||||||
|
type=pathlib.Path,
|
||||||
|
help='a directory with systemd-measure and other tools')
|
||||||
|
|
||||||
|
p.add_argument('--output', '-o',
|
||||||
|
type=pathlib.Path,
|
||||||
|
help='output file path')
|
||||||
|
|
||||||
|
p.add_argument('--measure',
|
||||||
|
action=argparse.BooleanOptionalAction,
|
||||||
|
help='print systemd-measure output for the UKI')
|
||||||
|
|
||||||
|
p.add_argument('--version',
|
||||||
|
action='version',
|
||||||
|
version=f'ukify {__version__}')
|
||||||
|
|
||||||
|
opts = p.parse_args(args)
|
||||||
|
|
||||||
|
if opts.cmdline and opts.cmdline.startswith('@'):
|
||||||
|
opts.cmdline = pathlib.Path(opts.cmdline[1:])
|
||||||
|
|
||||||
|
if opts.os_release is not None and opts.os_release.startswith('@'):
|
||||||
|
opts.os_release = pathlib.Path(opts.os_release[1:])
|
||||||
|
elif opts.os_release is None:
|
||||||
|
p = pathlib.Path('/etc/os-release')
|
||||||
|
if not p.exists():
|
||||||
|
p = pathlib.Path('/usr/lib/os-release')
|
||||||
|
opts.os_release = p
|
||||||
|
|
||||||
|
if opts.efi_arch is None:
|
||||||
|
opts.efi_arch = guess_efi_arch()
|
||||||
|
|
||||||
|
if opts.stub is None:
|
||||||
|
opts.stub = f'/usr/lib/systemd/boot/efi/linux{opts.efi_arch}.efi.stub'
|
||||||
|
|
||||||
|
if opts.signing_engine is None:
|
||||||
|
opts.sb_key = pathlib.Path(opts.sb_key) if opts.sb_key else None
|
||||||
|
opts.sb_cert = pathlib.Path(opts.sb_cert) if opts.sb_cert else None
|
||||||
|
|
||||||
|
if bool(opts.sb_key) ^ bool(opts.sb_cert):
|
||||||
|
raise ValueError('--secureboot-private-key= and --secureboot-certificate= must be specified together')
|
||||||
|
|
||||||
|
if opts.sign_kernel and not opts.sb_key:
|
||||||
|
raise ValueError('--sign-kernel requires --secureboot-private-key= and --secureboot-certificate= to be specified')
|
||||||
|
|
||||||
|
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.output is None:
|
||||||
|
suffix = '.efi' if opts.sb_key else '.unsigned.efi'
|
||||||
|
opts.output = opts.linux.name + suffix
|
||||||
|
|
||||||
|
for section in opts.sections:
|
||||||
|
section.check_name()
|
||||||
|
|
||||||
|
return opts
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
opts = parse_args()
|
||||||
|
check_inputs(opts)
|
||||||
|
make_uki(opts)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
@ -89,6 +89,7 @@ if install_tests
|
|||||||
install_dir : testdata_dir)
|
install_dir : testdata_dir)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
test_bootctl_json_sh = find_program('test-bootctl-json.sh')
|
||||||
test_fstab_generator_sh = find_program('test-fstab-generator.sh')
|
test_fstab_generator_sh = find_program('test-fstab-generator.sh')
|
||||||
test_network_generator_conversion_sh = find_program('test-network-generator-conversion.sh')
|
test_network_generator_conversion_sh = find_program('test-network-generator-conversion.sh')
|
||||||
test_systemctl_enable_sh = find_program('test-systemctl-enable.sh')
|
test_systemctl_enable_sh = find_program('test-systemctl-enable.sh')
|
||||||
|
24
test/test-bootctl-json.sh
Executable file
24
test/test-bootctl-json.sh
Executable file
@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
set -e
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
bootctl="${1:?}"
|
||||||
|
|
||||||
|
"$bootctl" --no-pager list >/dev/null || {
|
||||||
|
echo "$bootctl list failed, skipping tests" 1>&2
|
||||||
|
exit 77
|
||||||
|
}
|
||||||
|
|
||||||
|
set -x
|
||||||
|
|
||||||
|
"$bootctl" list --json=pretty | python3 -m json.tool >/dev/null
|
||||||
|
"$bootctl" list --json=short | python3 -m json.tool >/dev/null
|
||||||
|
|
||||||
|
command -v jq >/dev/null || {
|
||||||
|
echo "jq is not available, skipping jq tests" 1>&2
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
"$bootctl" list --json=pretty | jq . >/dev/null
|
||||||
|
"$bootctl" list --json=short | jq . >/dev/null
|
Loading…
x
Reference in New Issue
Block a user