mirror of
https://github.com/systemd/systemd.git
synced 2025-03-31 14:50:15 +03:00
Merge pull request #27262 from keszybz/ukify-install
Add kernel-install plugin that calls ukify
This commit is contained in:
commit
9dfed0d4cc
7
TODO
7
TODO
@ -840,9 +840,7 @@ Features:
|
||||
virtio-fs.
|
||||
|
||||
* for vendor-built signed initrds:
|
||||
- kernel-install should be able to install pre-built unified kernel images in
|
||||
type #2 drop-in dir in the ESP.
|
||||
- kernel-install should be able install encrypted creds automatically for
|
||||
- kernel-install should be able to install encrypted creds automatically for
|
||||
machine id, root pw, rootfs uuid, resume partition uuid, and place next to
|
||||
EFI kernel, for sd-stub to pick them up. These creds should be locked to
|
||||
the TPM, and bind to the right PCR the kernel is measured to.
|
||||
@ -1915,9 +1913,6 @@ Features:
|
||||
- teach it to prepare an ESP wholesale, i.e. with mkfs.vfat invocation
|
||||
- teach it to copy in unified kernel images and maybe type #1 boot loader spec entries from host
|
||||
|
||||
* kernel-install:
|
||||
- optionally, support generating type #2 entries instead of type #1, including signing them
|
||||
|
||||
* logind:
|
||||
- logind: optionally, ignore idle-hint logic for autosuspend, block suspend as long as a session is around
|
||||
- logind: wakelock/opportunistic suspend support
|
||||
|
439
man/ukify.xml
439
man/ukify.xml
@ -44,212 +44,302 @@
|
||||
|
||||
<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>,
|
||||
<varname>Cmdline=</varname>/<option>--cmdline=</option>,
|
||||
<varname>OSRelease=</varname>/<option>--os-release=</option>,
|
||||
<varname>DeviceTree=</varname>/<option>--devicetree=</option>,
|
||||
<varname>Splash=</varname>/<option>--splash=</option>,
|
||||
<varname>PCRPKey=</varname>/<option>--pcrpkey=</option>,
|
||||
<varname>Uname=</varname>/<option>--uname=</option>,
|
||||
and <option>--section=</option>
|
||||
below.</para>
|
||||
|
||||
<para><command>ukify</command> can also be used to assemble a PE binary that is not executable but
|
||||
contains auxiliary data, for example additional kernel command line entries.</para>
|
||||
|
||||
<para>If PCR signing keys are provided via the <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.
|
||||
<para>If PCR signing keys are provided via the
|
||||
<varname>PCRPrivateKey=</varname>/<option>--pcr-private-key=</option> and
|
||||
<varname>PCRPublicKey=</varname>/<option>--pcr-public-key=</option> options, PCR values that will be seen
|
||||
after booting with the given kernel, initrd, and other sections, will be calculated, signed, and embedded
|
||||
in the UKI.
|
||||
<citerefentry><refentrytitle>systemd-measure</refentrytitle><manvolnum>1</manvolnum></citerefentry> is
|
||||
used to perform this calculation and signing.</para>
|
||||
|
||||
<para>The calculation of PCR values is done for specific boot phase paths. Those can be specified with
|
||||
the <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>
|
||||
the <varname>Phases=</varname>/<option>--phases=</option> option. If not specified, the default provided
|
||||
by <command>systemd-measure</command> is used. It is also possible to specify the
|
||||
<varname>PCRPrivateKey=</varname>/<option>--pcr-private-key=</option>,
|
||||
<varname>PCRPublicKey=</varname>/<option>--pcr-public-key=</option>, and
|
||||
<varname>Phases=</varname>/<option>--phases=</option> arguments more than once. Signatures will then be
|
||||
performed with each of the specified keys. On the command line, when both <option>--phases=</option> and
|
||||
<option>--pcr-private-key=</option> are used, they must be specified the same number of times, and then
|
||||
the n-th boot phase path set will be signed by the n-th key. This can be used to build different trust
|
||||
policies for different phases of the boot. In the config file, <varname>PCRPrivateKey=</varname>,
|
||||
<varname>PCRPublicKey=</varname>, and <varname>Phases=</varname> are grouped into separate sections,
|
||||
describing separate boot phases.</para>
|
||||
|
||||
<para>If a SecureBoot signing key is provided via the <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
|
||||
<para>If a SecureBoot signing key is provided via the
|
||||
<varname>SecureBootPrivateKey=</varname>/<option>--secureboot-private-key=</option> option, the resulting
|
||||
PE binary will be signed as a whole, allowing the resulting UKI to be trusted by SecureBoot. Also see the
|
||||
discussion of automatic enrollment in
|
||||
<citerefentry><refentrytitle>systemd-boot</refentrytitle><manvolnum>7</manvolnum></citerefentry>.
|
||||
</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Options</title>
|
||||
<title>Configuration settings</title>
|
||||
|
||||
<para>The <replaceable>LINUX</replaceable> and <replaceable>INITRD</replaceable> positional arguments are
|
||||
optional. If more than one <replaceable>INITRD</replaceable> are 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>Settings can appear in configuration files (the syntax with <varname
|
||||
index='false'>SomeSetting=<replaceable>value</replaceable></varname>) and on the command line (the syntax
|
||||
with <option index='false'>--some-setting=<replaceable>value</replaceable></option>). For some command
|
||||
line parameters, a single-letter shortcut is also allowed. In the configuration files, the setting must
|
||||
be in the appropriate section, so the descriptions are grouped by section below. When the same setting
|
||||
appears in the configuration file and on the command line, generally the command line setting has higher
|
||||
priority and overwrites the config file setting completely. If some setting behaves differently, this is
|
||||
described below.</para>
|
||||
|
||||
<para>The following options are understood:</para>
|
||||
<para>The <replaceable>LINUX</replaceable> and <replaceable>INITRD</replaceable> positional arguments, or
|
||||
the equivalent <varname>Linux=</varname> and <varname>Initrd=</varname> settings, are optional. If more
|
||||
than one initrd 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>
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><option>--cmdline=<replaceable>TEXT</replaceable>|<replaceable>@PATH</replaceable></option></term>
|
||||
<para>The following options and settings are understood:</para>
|
||||
|
||||
<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>
|
||||
<refsect2>
|
||||
<title>Commandline-only options</title>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--os-release=<replaceable>TEXT</replaceable>|<replaceable>@PATH</replaceable></option></term>
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><option>--config=<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>
|
||||
<listitem><para>Load configuration from the given config file. In general, settings specified in
|
||||
the config file have lower precedence than the settings specified via options. In cases where the
|
||||
commandline option does not fully override the config file setting are explicitly mentioned in the
|
||||
descriptions of individual options.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--devicetree=<replaceable>PATH</replaceable></option></term>
|
||||
<varlistentry>
|
||||
<term><option>--measure</option></term>
|
||||
<term><option>--no-measure</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>
|
||||
<listitem><para>Enable or disable a call to <command>systemd-measure</command> to print
|
||||
pre-calculated PCR values. Defaults to false.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--splash=<replaceable>PATH</replaceable></option></term>
|
||||
<varlistentry>
|
||||
<term><option>--section=<replaceable>NAME</replaceable>:<replaceable>TEXT</replaceable>|<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>
|
||||
<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>--pcrpkey=<replaceable>PATH</replaceable></option></term>
|
||||
<varlistentry>
|
||||
<term><option>--tools=<replaceable>DIRS</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>
|
||||
<listitem><para>Specify one or more directories with helper tools. <command>ukify</command> will
|
||||
look for helper tools in those directories first, and if not found, try to load them from
|
||||
<varname>$PATH</varname> in the usual fashion.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--uname=<replaceable>VERSION</replaceable></option></term>
|
||||
<varlistentry>
|
||||
<term><option>--output=<replaceable>FILENAME</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>
|
||||
<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>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--section=<replaceable>NAME</replaceable>:<replaceable>TEXT</replaceable>|<replaceable>@PATH</replaceable></option></term>
|
||||
<varlistentry>
|
||||
<term><option>--summary</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>
|
||||
<listitem><para>Print a summary of loaded config and exit. This is useful to check how the options
|
||||
form the configuration file and the commandline are combined.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--pcr-private-key=<replaceable>PATH</replaceable></option></term>
|
||||
<xi:include href="standard-options.xml" xpointer="help" />
|
||||
<xi:include href="standard-options.xml" xpointer="version" />
|
||||
</variablelist>
|
||||
</refsect2>
|
||||
|
||||
<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>
|
||||
<refsect2>
|
||||
<title>[UKI] section</title>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--pcr-public-key=<replaceable>PATH</replaceable></option></term>
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><varname>Linux=<replaceable>LINUX</replaceable></varname></term>
|
||||
<term>positional argument <replaceable>LINUX</replaceable></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>
|
||||
<listitem><para>A path to the kernel binary.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--phases=<replaceable>LIST</replaceable></option></term>
|
||||
<varlistentry>
|
||||
<term><varname>Initrd=<replaceable>INITRD</replaceable>...</varname></term>
|
||||
<term>positional argument <replaceable>INITRD</replaceable></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>
|
||||
<listitem><para>Zero or more initrd paths. In the configuration file, items are separated by
|
||||
whitespace. The initrds are combined in the order of specification, with the initrds specified in
|
||||
the config file first.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--pcr-banks=<replaceable>PATH</replaceable></option></term>
|
||||
<varlistentry>
|
||||
<term><varname>Cmdline=<replaceable>TEXT</replaceable>|<replaceable>@PATH</replaceable></varname></term>
|
||||
<term><option>--cmdline=<replaceable>TEXT</replaceable>|<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>
|
||||
<listitem><para>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>--secureboot-private-key=<replaceable>SB_KEY</replaceable></option></term>
|
||||
<varlistentry>
|
||||
<term><varname>OSRelease=<replaceable>TEXT</replaceable>|<replaceable>@PATH</replaceable></varname></term>
|
||||
<term><option>--os-release=<replaceable>TEXT</replaceable>|<replaceable>@PATH</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>
|
||||
<listitem><para>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>--secureboot-certificate=<replaceable>SB_CERT</replaceable></option></term>
|
||||
<varlistentry>
|
||||
<term><varname>DeviceTree=<replaceable>PATH</replaceable></varname></term>
|
||||
<term><option>--devicetree=<replaceable>PATH</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>
|
||||
<listitem><para>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>--signing-engine=<replaceable>ENGINE</replaceable></option></term>
|
||||
<varlistentry>
|
||||
<term><varname>Splash=<replaceable>PATH</replaceable></varname></term>
|
||||
<term><option>--splash=<replaceable>PATH</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 project='archlinux'><refentrytitle>sbsign</refentrytitle><manvolnum>1</manvolnum></citerefentry>.
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
<listitem><para>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>--sign-kernel</option></term>
|
||||
<term><option>--no-sign-kernel</option></term>
|
||||
<varlistentry>
|
||||
<term><varname>PCRPKey=<replaceable>PATH</replaceable></varname></term>
|
||||
<term><option>--pcrpkey=<replaceable>PATH</replaceable></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>
|
||||
<listitem><para>A path to a public key to embed in the <literal>.pcrpkey</literal> section. If not
|
||||
specified, and there's exactly one
|
||||
<varname>PCRPublicKey=</varname>/<option>--pcr-public-key=</option> argument, that key will be used.
|
||||
Otherwise, the section will not be present.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--tools=<replaceable>DIRS</replaceable></option></term>
|
||||
<varlistentry>
|
||||
<term><varname>Uname=<replaceable>VERSION</replaceable></varname></term>
|
||||
<term><option>--uname=<replaceable>VERSION</replaceable></option></term>
|
||||
|
||||
<listitem><para>Specify one or more directories with helper tools. <command>ukify</command> will look
|
||||
for helper tools in those directories first, and if not found, try to load them from
|
||||
<varname>$PATH</varname> in the usual fashion.</para></listitem>
|
||||
</varlistentry>
|
||||
<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>--measure</option></term>
|
||||
<term><option>--no-measure</option></term>
|
||||
<varlistentry>
|
||||
<term><varname>PCRBanks=<replaceable>PATH</replaceable></varname></term>
|
||||
<term><option>--pcr-banks=<replaceable>PATH</replaceable></option></term>
|
||||
|
||||
<listitem><para>Enable or disable a call to <command>systemd-measure</command> to print
|
||||
pre-calculated PCR values. Defaults to false.</para></listitem>
|
||||
</varlistentry>
|
||||
<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>--output=<replaceable>FILENAME</replaceable></option></term>
|
||||
<varlistentry>
|
||||
<term><varname>SecureBootPrivateKey=<replaceable>SB_KEY</replaceable></varname></term>
|
||||
<term><option>--secureboot-private-key=<replaceable>SB_KEY</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>
|
||||
<listitem><para>A path to a private key to use for signing of the resulting binary. If the
|
||||
<varname>SigningEngine=</varname>/<option>--signing-engine=</option> option is used, this may also be
|
||||
an engine-specific designation.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<xi:include href="standard-options.xml" xpointer="help" />
|
||||
<xi:include href="standard-options.xml" xpointer="version" />
|
||||
</variablelist>
|
||||
<varlistentry>
|
||||
<term><varname>SecureBootCertificate=<replaceable>SB_CERT</replaceable></varname></term>
|
||||
<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
|
||||
<varname>SigningEngine=</varname>/<option>--signing-engine=</option> option is used, this may also
|
||||
be an engine-specific designation.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>SigningEngine=<replaceable>ENGINE</replaceable></varname></term>
|
||||
<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 project='archlinux'><refentrytitle>sbsign</refentrytitle><manvolnum>1</manvolnum></citerefentry>.
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>SignKernel=<replaceable>BOOL</replaceable></varname></term>
|
||||
<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
|
||||
<varname>SecureBootPrivateKey=</varname>/<option>--secureboot-private-key=</option> option and the
|
||||
binary has not already been signed. If
|
||||
<varname>SignKernel=</varname>/<option>--sign-kernel</option> is true, and the binary has already
|
||||
been signed, the signature will be appended anyway.</para></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect2>
|
||||
|
||||
<refsect2>
|
||||
<title>[PCRSignature:<replaceable>NAME</replaceable>] section</title>
|
||||
|
||||
<para>In the config file, those options are grouped by section. On the commandline, they
|
||||
must be specified in the same order. The sections specified in both sources are combined.
|
||||
</para>
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><varname>PCRPrivateKey=<replaceable>PATH</replaceable></varname></term>
|
||||
<term><option>--pcr-private-key=<replaceable>PATH</replaceable></option></term>
|
||||
|
||||
<listitem><para>A private key to use for signing PCR policies. On the commandline, this option may
|
||||
be specified more than once, in which case multiple signatures will be made.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>PCRPublicKey=<replaceable>PATH</replaceable></varname></term>
|
||||
<term><option>--pcr-public-key=<replaceable>PATH</replaceable></option></term>
|
||||
|
||||
<listitem><para>A public key to use for signing PCR policies.</para>
|
||||
|
||||
<para>On the commandline, this option may be specified more than once, similarly to the
|
||||
<option>--pcr-private-key=</option> option. If not present, the public keys will be extracted from
|
||||
the private keys. On the commandline, 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><varname>Phases=<replaceable>LIST</replaceable></varname></term>
|
||||
<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. Each set of boot phase paths will be signed with the corresponding private key. If not
|
||||
present, the default of
|
||||
<citerefentry><refentrytitle>systemd-measure</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||
will be used.</para>
|
||||
|
||||
<para>On the commandline, when this argument is present, it must appear the same number of times as
|
||||
the <option>--pcr-private-key=</option> option. </para></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect2>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
@ -258,7 +348,7 @@
|
||||
<example>
|
||||
<title>Minimal invocation</title>
|
||||
|
||||
<programlisting>ukify \
|
||||
<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'
|
||||
@ -270,7 +360,7 @@
|
||||
<example>
|
||||
<title>All the bells and whistles</title>
|
||||
|
||||
<programlisting>/usr/lib/systemd/ukify \
|
||||
<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 \
|
||||
@ -299,6 +389,45 @@
|
||||
combined image will be signed with the SecureBoot key <filename index='false'>sb.key</filename>.</para>
|
||||
</example>
|
||||
|
||||
<example>
|
||||
<title>All the bells and whistles, via a config file</title>
|
||||
|
||||
<para>This is the same as the previous example, but this time the configuration is stored in a
|
||||
file:</para>
|
||||
|
||||
<programlisting>$ cat ukify.conf
|
||||
[UKI]
|
||||
Initrd=early_cpio
|
||||
Cmdline=quiet rw rhgb
|
||||
|
||||
SecureBootPrivateKey=sb.key
|
||||
SecureBootCerificate=sb.cert
|
||||
SignKernel=yes
|
||||
PCRBanks=sha384,sha512
|
||||
|
||||
[PCRSignature:initrd]
|
||||
PCRPrivateKey=pcr-private-initrd-key.pem
|
||||
PCRPublicKey=pcr-public-initrd-key.pem
|
||||
Phases=enter-initrd
|
||||
|
||||
[PCRSignature:system]
|
||||
PCRPrivateKey=pcr-private-system-key.pem
|
||||
PCRPublicKey=pcr-public-system-key.pem
|
||||
Phases=enter-initrd:leave-initrd
|
||||
enter-initrd:leave-initrd:sysinit
|
||||
enter-initrd:leave-initrd:sysinit:ready
|
||||
|
||||
# /usr/lib/systemd/ukify -c ukify.conf \
|
||||
/lib/modules/6.0.9-300.fc37.x86_64/vmlinuz \
|
||||
/some/path/initramfs-6.0.9-300.fc37.x86_64.img
|
||||
</programlisting>
|
||||
|
||||
<para>One "initrd" (<filename index='false'>early_cpio</filename>) is specified in the config file, and
|
||||
the other initrd (<filename index='false'>initramfs-6.0.9-300.fc37.x86_64.img</filename>) is specified
|
||||
on the commandline. This may be useful for example when the first initrd contains microcode for the CPU
|
||||
and does not need to be updated when the kernel version changes, unlike the actual initrd.</para>
|
||||
</example>
|
||||
|
||||
<example>
|
||||
<title>Kernel command line auxiliary PE</title>
|
||||
|
||||
@ -309,7 +438,8 @@
|
||||
--output=debug.cmdline.efi
|
||||
</programlisting>
|
||||
|
||||
<para>This creates a signed PE binary that contains an additional kernel command line parameter.</para>
|
||||
<para>This creates a signed PE binary that contains the additional kernel command line parameter
|
||||
<literal>debug</literal>.</para>
|
||||
</example>
|
||||
</refsect1>
|
||||
|
||||
@ -319,6 +449,7 @@
|
||||
<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><refentrytitle>systemd-measure</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
|
||||
<citerefentry><refentrytitle>systemd-pcrphase.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||
</para>
|
||||
</refsect1>
|
||||
|
163
meson.build
163
meson.build
@ -2178,6 +2178,9 @@ public_programs = []
|
||||
# D-Bus introspection XML export
|
||||
dbus_programs = []
|
||||
|
||||
# A list of boot stubs. Required for testing of ukify.
|
||||
boot_stubs = []
|
||||
|
||||
basic_includes = include_directories(
|
||||
'src/basic',
|
||||
'src/fundamental',
|
||||
@ -2507,7 +2510,9 @@ exe = executable(
|
||||
versiondep],
|
||||
install_rpath : rootpkglibdir,
|
||||
install : conf.get('ENABLE_ANALYZE') == 1)
|
||||
public_programs += exe
|
||||
if conf.get('ENABLE_ANALYZE') == 1
|
||||
public_programs += exe
|
||||
endif
|
||||
|
||||
if want_tests != 'false'
|
||||
test('test-compare-versions',
|
||||
@ -4006,20 +4011,21 @@ if enable_sysusers
|
||||
args : exe.full_path())
|
||||
endif
|
||||
|
||||
exe = executable(
|
||||
'systemd-sysusers.standalone',
|
||||
'src/sysusers/sysusers.c',
|
||||
include_directories : includes,
|
||||
c_args : '-DSTANDALONE',
|
||||
link_with : [libshared_static,
|
||||
libbasic,
|
||||
libbasic_gcrypt,
|
||||
libsystemd_static],
|
||||
dependencies : [userspace,
|
||||
versiondep],
|
||||
build_by_default: have_standalone_binaries,
|
||||
install : have_standalone_binaries,
|
||||
install_dir : rootbindir)
|
||||
if have_standalone_binaries
|
||||
exe = executable(
|
||||
'systemd-sysusers.standalone',
|
||||
'src/sysusers/sysusers.c',
|
||||
include_directories : includes,
|
||||
c_args : '-DSTANDALONE',
|
||||
link_with : [libshared_static,
|
||||
libbasic,
|
||||
libbasic_gcrypt,
|
||||
libsystemd_static],
|
||||
dependencies : [userspace,
|
||||
versiondep],
|
||||
install : true,
|
||||
install_dir : rootbindir)
|
||||
public_programs += exe
|
||||
|
||||
if want_tests != 'false'
|
||||
@ -4052,21 +4058,22 @@ if conf.get('ENABLE_TMPFILES') == 1
|
||||
args : exe.full_path())
|
||||
endif
|
||||
|
||||
exe = executable(
|
||||
'systemd-tmpfiles.standalone',
|
||||
systemd_tmpfiles_sources,
|
||||
include_directories : includes,
|
||||
c_args : '-DSTANDALONE',
|
||||
link_with : [libshared_static,
|
||||
libbasic,
|
||||
libbasic_gcrypt,
|
||||
libsystemd_static],
|
||||
dependencies : [libacl,
|
||||
userspace,
|
||||
versiondep],
|
||||
build_by_default: have_standalone_binaries,
|
||||
install : have_standalone_binaries,
|
||||
install_dir : rootbindir)
|
||||
if have_standalone_binaries
|
||||
exe = executable(
|
||||
'systemd-tmpfiles.standalone',
|
||||
systemd_tmpfiles_sources,
|
||||
include_directories : includes,
|
||||
c_args : '-DSTANDALONE',
|
||||
link_with : [libshared_static,
|
||||
libbasic,
|
||||
libbasic_gcrypt,
|
||||
libsystemd_static],
|
||||
dependencies : [libacl,
|
||||
userspace,
|
||||
versiondep],
|
||||
install : true,
|
||||
install_dir : rootbindir)
|
||||
public_programs += exe
|
||||
|
||||
if want_tests != 'false'
|
||||
@ -4166,26 +4173,27 @@ if conf.get('ENABLE_REPART') == 1
|
||||
install_dir : rootbindir)
|
||||
public_programs += exe
|
||||
|
||||
exe = executable(
|
||||
'systemd-repart.standalone',
|
||||
systemd_repart_sources,
|
||||
include_directories : includes,
|
||||
c_args : '-DSTANDALONE',
|
||||
link_with : [libshared_static,
|
||||
libbasic,
|
||||
libbasic_gcrypt,
|
||||
libsystemd_static,
|
||||
libshared_fdisk],
|
||||
dependencies : [libblkid,
|
||||
libfdisk,
|
||||
libopenssl,
|
||||
threads,
|
||||
userspace,
|
||||
versiondep],
|
||||
build_by_default: have_standalone_binaries,
|
||||
install_rpath : rootpkglibdir,
|
||||
install : have_standalone_binaries,
|
||||
install_dir : rootbindir)
|
||||
if have_standalone_binaries
|
||||
exe = executable(
|
||||
'systemd-repart.standalone',
|
||||
systemd_repart_sources,
|
||||
include_directories : includes,
|
||||
c_args : '-DSTANDALONE',
|
||||
link_with : [libshared_static,
|
||||
libbasic,
|
||||
libbasic_gcrypt,
|
||||
libsystemd_static,
|
||||
libshared_fdisk],
|
||||
dependencies : [libblkid,
|
||||
libfdisk,
|
||||
libopenssl,
|
||||
threads,
|
||||
userspace,
|
||||
versiondep],
|
||||
install_rpath : rootpkglibdir,
|
||||
install : true,
|
||||
install_dir : rootbindir)
|
||||
public_programs += exe
|
||||
endif
|
||||
endif
|
||||
@ -4202,21 +4210,23 @@ executable(
|
||||
install : true,
|
||||
install_dir : rootlibexecdir)
|
||||
|
||||
executable(
|
||||
'systemd-shutdown.standalone',
|
||||
systemd_shutdown_sources,
|
||||
include_directories : includes,
|
||||
c_args : '-DSTANDALONE',
|
||||
link_with : [libshared_static,
|
||||
libbasic,
|
||||
libsystemd_static],
|
||||
dependencies : [libmount,
|
||||
userspace,
|
||||
versiondep],
|
||||
build_by_default: have_standalone_binaries,
|
||||
install_rpath : rootpkglibdir,
|
||||
install : have_standalone_binaries,
|
||||
install_dir : rootlibexecdir)
|
||||
if have_standalone_binaries
|
||||
executable(
|
||||
'systemd-shutdown.standalone',
|
||||
systemd_shutdown_sources,
|
||||
include_directories : includes,
|
||||
c_args : '-DSTANDALONE',
|
||||
link_with : [libshared_static,
|
||||
libbasic,
|
||||
libsystemd_static],
|
||||
dependencies : [libmount,
|
||||
userspace,
|
||||
versiondep],
|
||||
install_rpath : rootpkglibdir,
|
||||
install : true,
|
||||
install_dir : rootlibexecdir)
|
||||
public_programs += exe
|
||||
endif
|
||||
|
||||
executable(
|
||||
@ -4345,7 +4355,7 @@ executable(
|
||||
install : true,
|
||||
install_dir : rootlibexecdir)
|
||||
|
||||
exe = custom_target(
|
||||
kernel_install = custom_target(
|
||||
'kernel-install',
|
||||
input : kernel_install_in,
|
||||
output : 'kernel-install',
|
||||
@ -4353,25 +4363,32 @@ exe = custom_target(
|
||||
install : want_kernel_install,
|
||||
install_mode : 'rwxr-xr-x',
|
||||
install_dir : bindir)
|
||||
public_programs += exe
|
||||
|
||||
if want_tests != 'false' and want_kernel_install
|
||||
test('test-kernel-install',
|
||||
test_kernel_install_sh,
|
||||
env : test_env,
|
||||
args : [exe.full_path(), loaderentry_install])
|
||||
if want_kernel_install
|
||||
public_programs += exe
|
||||
endif
|
||||
|
||||
if want_ukify
|
||||
exe = custom_target(
|
||||
ukify = custom_target(
|
||||
'ukify',
|
||||
input : 'src/ukify/ukify.py',
|
||||
output : 'ukify',
|
||||
command : [jinja2_cmdline, '@INPUT@', '@OUTPUT@'],
|
||||
install : true,
|
||||
install : want_ukify,
|
||||
install_mode : 'rwxr-xr-x',
|
||||
install_dir : rootlibexecdir)
|
||||
public_programs += exe
|
||||
if want_ukify
|
||||
public_programs += ukify
|
||||
endif
|
||||
|
||||
if want_tests != 'false' and want_kernel_install
|
||||
args = [kernel_install.full_path(), loaderentry_install, uki_copy_install]
|
||||
if want_ukify and boot_stubs.length() > 0
|
||||
args += [ukify.full_path(), ukify_install, boot_stubs[0]]
|
||||
endif
|
||||
|
||||
test('test-kernel-install',
|
||||
test_kernel_install_sh,
|
||||
env : test_env,
|
||||
args : args)
|
||||
endif
|
||||
|
||||
############################################################
|
||||
|
@ -4,6 +4,9 @@
|
||||
Distribution=fedora
|
||||
|
||||
[Content]
|
||||
Packages=
|
||||
python3dist(pytest-flakes)
|
||||
|
||||
BuildPackages=
|
||||
pkgconfig(libgcrypt)
|
||||
pkgconfig(xencontrol)
|
||||
|
@ -27,6 +27,7 @@ Packages=
|
||||
libxkbcommon0
|
||||
libzstd1
|
||||
pam
|
||||
python3-pytest-flakes
|
||||
shadow
|
||||
tpm2-0-tss
|
||||
xz
|
||||
|
@ -334,7 +334,7 @@ foreach efi_elf_binary : efi_elf_binaries
|
||||
# FIXME: Use build_tgt.name() with meson >= 0.54.0
|
||||
name = fs.name(efi_elf_binary.full_path()).split('.')[0]
|
||||
name += name.startswith('linux') ? '.efi.stub' : '.efi'
|
||||
boot_targets += custom_target(
|
||||
exe = custom_target(
|
||||
name,
|
||||
output : name,
|
||||
input : efi_elf_binary,
|
||||
@ -351,6 +351,10 @@ foreach efi_elf_binary : efi_elf_binaries
|
||||
'@INPUT@',
|
||||
'@OUTPUT@',
|
||||
])
|
||||
boot_targets += exe
|
||||
if name.startswith('linux')
|
||||
boot_stubs += exe
|
||||
endif
|
||||
endforeach
|
||||
|
||||
alias_target('systemd-boot', boot_targets)
|
||||
|
224
src/kernel-install/60-ukify.install.in
Executable file
224
src/kernel-install/60-ukify.install.in
Executable file
@ -0,0 +1,224 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# -*- mode: python-mode -*-
|
||||
#
|
||||
# This file is part of systemd.
|
||||
#
|
||||
# systemd is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# systemd is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with systemd; If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# 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,redefined-builtin,fixme
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import runpy
|
||||
import shlex
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
__version__ = '{{PROJECT_VERSION}} ({{GIT_VERSION}})'
|
||||
|
||||
try:
|
||||
VERBOSE = int(os.environ['KERNEL_INSTALL_VERBOSE']) > 0
|
||||
except (KeyError, ValueError):
|
||||
VERBOSE = False
|
||||
|
||||
# Override location of ukify and the boot stub for testing and debugging.
|
||||
UKIFY = os.getenv('KERNEL_INSTALL_UKIFY', '/usr/lib/systemd/ukify')
|
||||
BOOT_STUB = os.getenv('KERNEL_INSTALL_BOOT_STUB')
|
||||
|
||||
|
||||
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 log(*args, **kwargs):
|
||||
if VERBOSE:
|
||||
print(*args, **kwargs)
|
||||
|
||||
def path_is_readable(p: Path, dir=False) -> None:
|
||||
"""Verify access to a file or directory."""
|
||||
try:
|
||||
p.open().close()
|
||||
except IsADirectoryError:
|
||||
if dir:
|
||||
return
|
||||
raise
|
||||
|
||||
def mandatory_variable(name):
|
||||
try:
|
||||
return os.environ[name]
|
||||
except KeyError as e:
|
||||
raise KeyError(f'${name} must be set in the environment') from e
|
||||
|
||||
def parse_args(args=None):
|
||||
p = argparse.ArgumentParser(
|
||||
description='kernel-install plugin to build a Unified Kernel Image',
|
||||
allow_abbrev=False,
|
||||
usage='60-ukify.install COMMAND KERNEL_VERSION ENTRY_DIR KERNEL_IMAGE INITRD…',
|
||||
)
|
||||
|
||||
# Suppress printing of usage synopsis on errors
|
||||
p.error = lambda message: p.exit(2, f'{p.prog}: error: {message}\n')
|
||||
|
||||
p.add_argument('command',
|
||||
metavar='COMMAND',
|
||||
help="The action to perform. Only 'add' is supported.")
|
||||
p.add_argument('kernel_version',
|
||||
metavar='KERNEL_VERSION',
|
||||
help='Kernel version string')
|
||||
p.add_argument('entry_dir',
|
||||
metavar='ENTRY_DIR',
|
||||
type=Path,
|
||||
nargs='?',
|
||||
help='Type#1 entry directory (ignored)')
|
||||
p.add_argument('kernel_image',
|
||||
metavar='KERNEL_IMAGE',
|
||||
type=Path,
|
||||
nargs='?',
|
||||
help='Kernel binary')
|
||||
p.add_argument('initrd',
|
||||
metavar='INITRD…',
|
||||
type=Path,
|
||||
nargs='*',
|
||||
help='Initrd files')
|
||||
p.add_argument('--version',
|
||||
action='version',
|
||||
version=f'systemd {__version__}')
|
||||
|
||||
opts = p.parse_args(args)
|
||||
|
||||
if opts.command == 'add':
|
||||
opts.staging_area = Path(mandatory_variable('KERNEL_INSTALL_STAGING_AREA'))
|
||||
path_is_readable(opts.staging_area, dir=True)
|
||||
|
||||
opts.entry_token = mandatory_variable('KERNEL_INSTALL_ENTRY_TOKEN')
|
||||
opts.machine_id = mandatory_variable('KERNEL_INSTALL_MACHINE_ID')
|
||||
|
||||
return opts
|
||||
|
||||
def we_are_wanted() -> bool:
|
||||
KERNEL_INSTALL_LAYOUT = os.getenv('KERNEL_INSTALL_LAYOUT')
|
||||
|
||||
if KERNEL_INSTALL_LAYOUT != 'uki':
|
||||
log(f'{KERNEL_INSTALL_LAYOUT=}, quitting.')
|
||||
return False
|
||||
|
||||
KERNEL_INSTALL_UKI_GENERATOR = os.getenv('KERNEL_INSTALL_UKI_GENERATOR')
|
||||
|
||||
if KERNEL_INSTALL_UKI_GENERATOR != 'ukify':
|
||||
log(f'{KERNEL_INSTALL_UKI_GENERATOR=}, quitting.')
|
||||
return False
|
||||
|
||||
log('KERNEL_INSTALL_LAYOUT and KERNEL_INSTALL_UKI_GENERATOR are good')
|
||||
return True
|
||||
|
||||
|
||||
def config_file_location() -> Optional[Path]:
|
||||
if root := os.getenv('KERNEL_INSTALL_CONF_ROOT'):
|
||||
p = Path(root) / 'uki.conf'
|
||||
else:
|
||||
p = Path('/etc/kernel/uki.conf')
|
||||
if p.exists():
|
||||
return p
|
||||
return None
|
||||
|
||||
|
||||
def kernel_cmdline_base() -> list[str]:
|
||||
if root := os.getenv('KERNEL_INSTALL_CONF_ROOT'):
|
||||
return Path(root).joinpath('cmdline').read_text().split()
|
||||
|
||||
for cmdline in ('/etc/kernel/cmdline',
|
||||
'/usr/lib/kernel/cmdline'):
|
||||
try:
|
||||
return Path(cmdline).read_text().split()
|
||||
except FileNotFoundError:
|
||||
continue
|
||||
|
||||
options = Path('/proc/cmdline').read_text().split()
|
||||
return [opt for opt in options
|
||||
if not opt.startswith(('BOOT_IMAGE=', 'initrd='))]
|
||||
|
||||
|
||||
def kernel_cmdline(opts) -> str:
|
||||
options = kernel_cmdline_base()
|
||||
|
||||
# If the boot entries are named after the machine ID, then suffix the kernel
|
||||
# command line with the machine ID we use, so that the machine ID remains
|
||||
# stable, even during factory reset, in the initrd (where the system's machine
|
||||
# ID is not directly accessible yet), and if the root file system is volatile.
|
||||
if (opts.entry_token == opts.machine_id and
|
||||
not any(opt.startswith('systemd.machine_id=') for opt in options)):
|
||||
options += [f'systemd.machine_id={opts.machine_id}']
|
||||
|
||||
# TODO: we unconditionally set the cmdline here, ignoring the setting in
|
||||
# the config file. Should we not do that?
|
||||
|
||||
# Prepend a space so that '@' does not get misinterpreted
|
||||
return ' ' + ' '.join(options)
|
||||
|
||||
|
||||
def call_ukify(opts):
|
||||
# Punish me harder.
|
||||
# We want this:
|
||||
# ukify = importlib.machinery.SourceFileLoader('ukify', UKIFY).load_module()
|
||||
# but it throws a DeprecationWarning.
|
||||
# https://stackoverflow.com/questions/67631/how-can-i-import-a-module-dynamically-given-the-full-path
|
||||
# https://github.com/python/cpython/issues/65635
|
||||
# offer "explanations", but to actually load a python file without a .py extension,
|
||||
# the "solution" is 4+ incomprehensible lines.
|
||||
# The solution with runpy gives a dictionary, which isn't great, but will do.
|
||||
ukify = runpy.run_path(UKIFY, run_name='ukify')
|
||||
|
||||
# Create "empty" namespace. We want to override just a few settings,
|
||||
# so it doesn't make sense to duplicate all the fields. We use a hack
|
||||
# to pre-populate the namespace like argparse would, all defaults.
|
||||
# We need to specify the two mandatory arguments to not get an error.
|
||||
opts2 = ukify['create_parser']().parse_args(('A','B'))
|
||||
|
||||
opts2.config = config_file_location()
|
||||
opts2.uname = opts.kernel_version
|
||||
opts2.linux = opts.kernel_image
|
||||
opts2.initrd = opts.initrd
|
||||
# Note that 'uki.efi' is the name required by 90-uki-copy.install.
|
||||
opts2.output = opts.staging_area / 'uki.efi'
|
||||
|
||||
opts2.cmdline = kernel_cmdline(opts)
|
||||
if BOOT_STUB:
|
||||
opts2.stub = BOOT_STUB
|
||||
|
||||
# opts2.summary = True
|
||||
|
||||
ukify['apply_config'](opts2)
|
||||
ukify['finalize_options'](opts2)
|
||||
ukify['check_inputs'](opts2)
|
||||
ukify['make_uki'](opts2)
|
||||
|
||||
log(f'{opts2.output} has been created')
|
||||
|
||||
|
||||
def main():
|
||||
opts = parse_args()
|
||||
if opts.command != 'add':
|
||||
return
|
||||
if not we_are_wanted():
|
||||
return
|
||||
|
||||
call_ukify(opts)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -28,9 +28,9 @@ INITRD_OPTIONS_SHIFT=4
|
||||
|
||||
[ "$KERNEL_INSTALL_LAYOUT" = "bls" ] || exit 0
|
||||
|
||||
MACHINE_ID="$KERNEL_INSTALL_MACHINE_ID"
|
||||
ENTRY_TOKEN="$KERNEL_INSTALL_ENTRY_TOKEN"
|
||||
BOOT_ROOT="$KERNEL_INSTALL_BOOT_ROOT"
|
||||
MACHINE_ID="${KERNEL_INSTALL_MACHINE_ID:?}"
|
||||
ENTRY_TOKEN="${KERNEL_INSTALL_ENTRY_TOKEN:?}"
|
||||
BOOT_ROOT="${KERNEL_INSTALL_BOOT_ROOT:?}"
|
||||
|
||||
[ -n "$BOOT_MNT" ] || BOOT_MNT="$(stat -c %m "$BOOT_ROOT")"
|
||||
if [ "$BOOT_MNT" = '/' ]; then
|
||||
|
@ -1,21 +1,31 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
kernel_install_in = files('kernel-install.in')
|
||||
loaderentry_install_in = files('90-loaderentry.install.in')
|
||||
|
||||
ukify_install = custom_target(
|
||||
'60-ukify.install',
|
||||
input : '60-ukify.install.in',
|
||||
output : '60-ukify.install',
|
||||
command : [jinja2_cmdline, '@INPUT@', '@OUTPUT@'],
|
||||
install : want_kernel_install and want_ukify,
|
||||
install_mode : 'rwxr-xr-x',
|
||||
install_dir : kernelinstalldir)
|
||||
|
||||
loaderentry_install = custom_target(
|
||||
'90-loaderentry.install',
|
||||
input : loaderentry_install_in,
|
||||
input : '90-loaderentry.install.in',
|
||||
output : '90-loaderentry.install',
|
||||
command : [jinja2_cmdline, '@INPUT@', '@OUTPUT@'],
|
||||
install : want_kernel_install,
|
||||
install_mode : 'rwxr-xr-x',
|
||||
install_dir : kernelinstalldir)
|
||||
|
||||
kernel_install_files = files(
|
||||
'50-depmod.install',
|
||||
'90-uki-copy.install',
|
||||
)
|
||||
uki_copy_install = files('90-uki-copy.install')
|
||||
|
||||
kernel_install_files = [
|
||||
files('50-depmod.install'),
|
||||
uki_copy_install,
|
||||
]
|
||||
|
||||
if want_kernel_install
|
||||
install_data(kernel_install_files,
|
||||
|
@ -7,7 +7,11 @@ set -o pipefail
|
||||
export SYSTEMD_LOG_LEVEL=debug
|
||||
|
||||
kernel_install="${1:?}"
|
||||
plugin="${2:?}"
|
||||
loaderentry_install="${2:?}"
|
||||
uki_copy_install="${3:?}"
|
||||
ukify="${4:-}"
|
||||
ukify_install="${5:-}"
|
||||
boot_stub="${6:-}"
|
||||
if [[ -d "${PROJECT_BUILD_ROOT:-}" ]]; then
|
||||
bootctl="${PROJECT_BUILD_ROOT}/bootctl"
|
||||
else
|
||||
@ -36,11 +40,15 @@ MACHINE_ID=badbadbadbadbadbad6abadbadbadbad
|
||||
EOF
|
||||
|
||||
export KERNEL_INSTALL_CONF_ROOT="$D/sources"
|
||||
export KERNEL_INSTALL_PLUGINS="$plugin"
|
||||
# We "install" multiple plugins, but control which ones will be active via install.conf.
|
||||
export KERNEL_INSTALL_PLUGINS="${ukify_install} ${loaderentry_install} ${uki_copy_install}"
|
||||
export BOOT_ROOT="$D/boot"
|
||||
export BOOT_MNT="$D/boot"
|
||||
export MACHINE_ID='3e0484f3634a418b8e6a39e8828b03e3'
|
||||
export KERNEL_INSTALL_UKIFY="$ukify"
|
||||
export KERNEL_INSTALL_BOOT_STUB="$boot_stub"
|
||||
|
||||
# Test type#1 installation
|
||||
"$kernel_install" -v add 1.1.1 "$D/sources/linux" "$D/sources/initrd"
|
||||
|
||||
entry="$BOOT_ROOT/loader/entries/the-token-1.1.1.conf"
|
||||
@ -91,7 +99,25 @@ grep -qE '^initrd .*/the-token/1.1.1/initrd' "$entry"
|
||||
grep -qE 'image' "$BOOT_ROOT/the-token/1.1.1/linux"
|
||||
grep -qE 'initrd' "$BOOT_ROOT/the-token/1.1.1/initrd"
|
||||
|
||||
if test -x "$bootctl"; then
|
||||
# Install UKI
|
||||
if [ -f "$ukify" ]; then
|
||||
cat >>"$D/sources/install.conf" <<EOF
|
||||
layout=uki
|
||||
uki_generator=ukify
|
||||
EOF
|
||||
"$kernel_install" -v add 1.1.3 "$D/sources/linux" "$D/sources/initrd"
|
||||
uki="${BOOT_ROOT}/EFI/Linux/the-token-1.1.3+56.efi"
|
||||
test -f "$uki"
|
||||
|
||||
if [ -x "$bootctl" ]; then
|
||||
"$bootctl" kernel-inspect "$uki" | grep -qE 'Kernel Type: +uki$'
|
||||
"$bootctl" kernel-inspect "$uki" | grep -qE 'Version: +1\.1\.3$'
|
||||
"$bootctl" kernel-inspect "$uki" | grep -qE 'Cmdline: +opt1 opt2$'
|
||||
fi
|
||||
fi
|
||||
|
||||
# Test bootctl
|
||||
if [ -x "$bootctl" ]; then
|
||||
echo "Testing bootctl"
|
||||
e2="${entry%+*}_2.conf"
|
||||
cp "$entry" "$e2"
|
||||
|
@ -1,7 +1,19 @@
|
||||
# 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)
|
||||
have_pytest_flakes = pymod.find_installation(
|
||||
'python3',
|
||||
required : false,
|
||||
modules : ['pytest_flakes'],
|
||||
).found()
|
||||
|
||||
args = ['-v']
|
||||
if have_pytest_flakes
|
||||
args += ['--flakes']
|
||||
endif
|
||||
|
||||
test('test-ukify',
|
||||
files('test_ukify.py'),
|
||||
args: args,
|
||||
env : test_env)
|
||||
endif
|
||||
|
@ -1,2 +0,0 @@
|
||||
[tool:pytest]
|
||||
addopts = --flakes
|
@ -14,6 +14,7 @@ import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import textwrap
|
||||
|
||||
try:
|
||||
import pytest
|
||||
@ -46,6 +47,93 @@ def test_round_up():
|
||||
assert ukify.round_up(4096) == 4096
|
||||
assert ukify.round_up(4097) == 8192
|
||||
|
||||
def test_namespace_creation():
|
||||
ns = ukify.create_parser().parse_args(('A','B'))
|
||||
assert ns.linux == pathlib.Path('A')
|
||||
assert ns.initrd == [pathlib.Path('B')]
|
||||
|
||||
def test_config_example():
|
||||
ex = ukify.config_example()
|
||||
assert '[UKI]' in ex
|
||||
assert 'Splash = BMP' in ex
|
||||
|
||||
def test_apply_config(tmp_path):
|
||||
config = tmp_path / 'config1.conf'
|
||||
config.write_text(textwrap.dedent(
|
||||
f'''
|
||||
[UKI]
|
||||
Linux = LINUX
|
||||
Initrd = initrd1 initrd2
|
||||
initrd3
|
||||
Cmdline = 1 2 3 4 5
|
||||
6 7 8
|
||||
OSRelease = @some/path1
|
||||
DeviceTree = some/path2
|
||||
Splash = some/path3
|
||||
Uname = 1.2.3
|
||||
EFIArch=arm
|
||||
Stub = some/path4
|
||||
PCRBanks = sha512,sha1
|
||||
SigningEngine = engine1
|
||||
SecureBootPrivateKey = some/path5
|
||||
SecureBootCertificate = some/path6
|
||||
SignKernel = no
|
||||
|
||||
[PCRSignature:NAME]
|
||||
PCRPrivateKey = some/path7
|
||||
PCRPublicKey = some/path8
|
||||
Phases = {':'.join(ukify.KNOWN_PHASES)}
|
||||
'''))
|
||||
|
||||
ns = ukify.create_parser().parse_args(('A','B'))
|
||||
ns.linux = None
|
||||
ns.initrd = []
|
||||
ukify.apply_config(ns, config)
|
||||
|
||||
assert ns.linux == pathlib.Path('LINUX')
|
||||
assert ns.initrd == [pathlib.Path('initrd1'),
|
||||
pathlib.Path('initrd2'),
|
||||
pathlib.Path('initrd3')]
|
||||
assert ns.cmdline == '1 2 3 4 5\n6 7 8'
|
||||
assert ns.os_release == '@some/path1'
|
||||
assert ns.devicetree == pathlib.Path('some/path2')
|
||||
assert ns.splash == pathlib.Path('some/path3')
|
||||
assert ns.efi_arch == 'arm'
|
||||
assert ns.stub == pathlib.Path('some/path4')
|
||||
assert ns.pcr_banks == ['sha512', 'sha1']
|
||||
assert ns.signing_engine == 'engine1'
|
||||
assert ns.sb_key == 'some/path5'
|
||||
assert ns.sb_cert == 'some/path6'
|
||||
assert ns.sign_kernel == False
|
||||
|
||||
assert ns._groups == ['NAME']
|
||||
assert ns.pcr_private_keys == [pathlib.Path('some/path7')]
|
||||
assert ns.pcr_public_keys == [pathlib.Path('some/path8')]
|
||||
assert ns.phase_path_groups == [['enter-initrd:leave-initrd:sysinit:ready:shutdown:final']]
|
||||
|
||||
ukify.finalize_options(ns)
|
||||
|
||||
assert ns.linux == pathlib.Path('LINUX')
|
||||
assert ns.initrd == [pathlib.Path('initrd1'),
|
||||
pathlib.Path('initrd2'),
|
||||
pathlib.Path('initrd3')]
|
||||
assert ns.cmdline == '1 2 3 4 5 6 7 8'
|
||||
assert ns.os_release == pathlib.Path('some/path1')
|
||||
assert ns.devicetree == pathlib.Path('some/path2')
|
||||
assert ns.splash == pathlib.Path('some/path3')
|
||||
assert ns.efi_arch == 'arm'
|
||||
assert ns.stub == pathlib.Path('some/path4')
|
||||
assert ns.pcr_banks == ['sha512', 'sha1']
|
||||
assert ns.signing_engine == 'engine1'
|
||||
assert ns.sb_key == 'some/path5'
|
||||
assert ns.sb_cert == 'some/path6'
|
||||
assert ns.sign_kernel == False
|
||||
|
||||
assert ns._groups == ['NAME']
|
||||
assert ns.pcr_private_keys == [pathlib.Path('some/path7')]
|
||||
assert ns.pcr_public_keys == [pathlib.Path('some/path8')]
|
||||
assert ns.phase_path_groups == [['enter-initrd:leave-initrd:sysinit:ready:shutdown:final']]
|
||||
|
||||
def test_parse_args_minimal():
|
||||
opts = ukify.parse_args('arg1 arg2'.split())
|
||||
assert opts.linux == pathlib.Path('arg1')
|
||||
@ -78,6 +166,7 @@ def test_parse_args_many():
|
||||
])
|
||||
assert opts.linux == pathlib.Path('/ARG1')
|
||||
assert opts.initrd == [pathlib.Path('/ARG2'), pathlib.Path('/ARG3 WITH SPACE')]
|
||||
assert opts.cmdline == 'a b c'
|
||||
assert opts.os_release == 'K1=V1\nK2=V2'
|
||||
assert opts.devicetree == pathlib.Path('DDDDTTTT')
|
||||
assert opts.splash == pathlib.Path('splash')
|
||||
@ -91,7 +180,7 @@ def test_parse_args_many():
|
||||
assert opts.sb_key == 'SBKEY'
|
||||
assert opts.sb_cert == 'SBCERT'
|
||||
assert opts.sign_kernel is False
|
||||
assert opts.tools == pathlib.Path('TOOLZ/')
|
||||
assert opts.tools == [pathlib.Path('TOOLZ/')]
|
||||
assert opts.output == pathlib.Path('OUTPUT')
|
||||
assert opts.measure is False
|
||||
|
||||
@ -109,15 +198,92 @@ def test_parse_sections():
|
||||
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_config_priority(tmp_path):
|
||||
config = tmp_path / 'config1.conf'
|
||||
config.write_text(textwrap.dedent(
|
||||
f'''
|
||||
[UKI]
|
||||
Linux = LINUX
|
||||
Initrd = initrd1 initrd2
|
||||
initrd3
|
||||
Cmdline = 1 2 3 4 5
|
||||
6 7 8
|
||||
OSRelease = @some/path1
|
||||
DeviceTree = some/path2
|
||||
Splash = some/path3
|
||||
Uname = 1.2.3
|
||||
EFIArch=arm
|
||||
Stub = some/path4
|
||||
PCRBanks = sha512,sha1
|
||||
SigningEngine = engine1
|
||||
SecureBootPrivateKey = some/path5
|
||||
SecureBootCertificate = some/path6
|
||||
SignKernel = no
|
||||
|
||||
[PCRSignature:NAME]
|
||||
PCRPrivateKey = some/path7
|
||||
PCRPublicKey = some/path8
|
||||
Phases = {':'.join(ukify.KNOWN_PHASES)}
|
||||
'''))
|
||||
|
||||
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',
|
||||
])
|
||||
|
||||
ukify.apply_config(opts, config)
|
||||
ukify.finalize_options(opts)
|
||||
|
||||
assert opts.linux == pathlib.Path('/ARG1')
|
||||
assert opts.initrd == [pathlib.Path('initrd1'),
|
||||
pathlib.Path('initrd2'),
|
||||
pathlib.Path('initrd3'),
|
||||
pathlib.Path('/ARG2'),
|
||||
pathlib.Path('/ARG3 WITH SPACE')]
|
||||
assert opts.cmdline == 'a b c'
|
||||
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'),
|
||||
pathlib.Path('some/path7')]
|
||||
assert opts.pcr_public_keys == [pathlib.Path('PKEY2'),
|
||||
pathlib.Path('some/path8')]
|
||||
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 True
|
||||
|
||||
def test_help(capsys):
|
||||
with pytest.raises(SystemExit):
|
||||
ukify.parse_args(['--help'])
|
||||
@ -148,7 +314,7 @@ def kernel_initrd():
|
||||
linux = f"{item['root']}{item['linux']}"
|
||||
initrd = f"{item['root']}{item['initrd'][0]}"
|
||||
except (KeyError, IndexError):
|
||||
pass
|
||||
continue
|
||||
return [linux, initrd]
|
||||
else:
|
||||
return None
|
||||
@ -242,7 +408,6 @@ def test_uname_scraping(kernel_initrd):
|
||||
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')
|
||||
@ -410,4 +575,4 @@ def test_pcr_signing2(kernel_initrd, tmpdir):
|
||||
assert len(sig['sha1']) == 6 # six items for six phases paths
|
||||
|
||||
if __name__ == '__main__':
|
||||
pytest.main([__file__, '-v'])
|
||||
sys.exit(pytest.main(sys.argv))
|
||||
|
@ -1,12 +1,28 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
#
|
||||
# This file is part of systemd.
|
||||
#
|
||||
# systemd is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# systemd is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with systemd; If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# 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
|
||||
# pylint: disable=too-many-branches,fixme
|
||||
|
||||
import argparse
|
||||
import configparser
|
||||
import collections
|
||||
import dataclasses
|
||||
import fnmatch
|
||||
@ -14,27 +30,33 @@ import itertools
|
||||
import json
|
||||
import os
|
||||
import pathlib
|
||||
import pprint
|
||||
import re
|
||||
import shlex
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import typing
|
||||
from typing import (Any,
|
||||
Callable,
|
||||
IO,
|
||||
Optional,
|
||||
Union)
|
||||
|
||||
import pefile
|
||||
import pefile # type: ignore
|
||||
|
||||
__version__ = '{{PROJECT_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'],
|
||||
'loongarch32' : ['loongarch32'],
|
||||
'loongarch64' : ['loongarch64'],
|
||||
'riscv32' : ['riscv32'],
|
||||
'riscv64' : ['riscv64'],
|
||||
# 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'],
|
||||
'loongarch32' : ['loongarch32'],
|
||||
'loongarch64' : ['loongarch64'],
|
||||
'riscv32' : ['riscv32'],
|
||||
'riscv64' : ['riscv64'],
|
||||
}
|
||||
EFI_ARCHES: list[str] = sum(EFI_ARCH_MAP.values(), [])
|
||||
|
||||
@ -69,18 +91,6 @@ def shell_join(cmd):
|
||||
return ' '.join(shlex.quote(str(x)) for x in cmd)
|
||||
|
||||
|
||||
def path_is_readable(s: typing.Optional[str]) -> typing.Optional[pathlib.Path]:
|
||||
"""Convert a filename string to a Path and verify access."""
|
||||
if s is None:
|
||||
return None
|
||||
p = pathlib.Path(s)
|
||||
try:
|
||||
p.open().close()
|
||||
except IsADirectoryError:
|
||||
pass
|
||||
return p
|
||||
|
||||
|
||||
def round_up(x, blocksize=4096):
|
||||
return (x + blocksize - 1) // blocksize * blocksize
|
||||
|
||||
@ -222,7 +232,7 @@ class Uname:
|
||||
class Section:
|
||||
name: str
|
||||
content: pathlib.Path
|
||||
tmpfile: typing.Optional[typing.IO] = None
|
||||
tmpfile: Optional[IO] = None
|
||||
measure: bool = False
|
||||
|
||||
@classmethod
|
||||
@ -266,7 +276,7 @@ class Section:
|
||||
|
||||
@dataclasses.dataclass
|
||||
class UKI:
|
||||
executable: list[typing.Union[pathlib.Path, str]]
|
||||
executable: list[Union[pathlib.Path, str]]
|
||||
sections: list[Section] = dataclasses.field(default_factory=list, init=False)
|
||||
|
||||
def add_section(self, section):
|
||||
@ -322,11 +332,13 @@ def check_inputs(opts):
|
||||
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()
|
||||
if isinstance(value, pathlib.Path):
|
||||
# Open file to check that we can read it, or generate an exception
|
||||
value.open().close()
|
||||
elif isinstance(value, list):
|
||||
for item in value:
|
||||
if isinstance(item, pathlib.Path):
|
||||
item.open().close()
|
||||
|
||||
check_splash(opts.splash)
|
||||
|
||||
@ -443,7 +455,7 @@ def pairwise(iterable):
|
||||
return zip(a, b)
|
||||
|
||||
|
||||
class PeError(Exception):
|
||||
class PEError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
@ -485,12 +497,12 @@ def pe_add_sections(uki: UKI, output: str):
|
||||
|
||||
warnings = pe.get_warnings()
|
||||
if warnings:
|
||||
raise PeError(f'pefile warnings treated as errors: {warnings}')
|
||||
raise PEError(f'pefile warnings treated as errors: {warnings}')
|
||||
|
||||
security = pe.OPTIONAL_HEADER.DATA_DIRECTORY[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_SECURITY']]
|
||||
if security.VirtualAddress != 0:
|
||||
# We could strip the signatures, but why would anyone sign the stub?
|
||||
raise PeError(f'Stub image is signed, refusing.')
|
||||
raise PEError('Stub image is signed, refusing.')
|
||||
|
||||
for section in uki.sections:
|
||||
new_section = pefile.SectionStructure(pe.__IMAGE_SECTION_HEADER_format__, pe=pe)
|
||||
@ -498,7 +510,7 @@ def pe_add_sections(uki: UKI, output: str):
|
||||
|
||||
offset = pe.sections[-1].get_file_offset() + new_section.sizeof()
|
||||
if offset + new_section.sizeof() > pe.OPTIONAL_HEADER.SizeOfHeaders:
|
||||
raise PeError(f'Not enough header space to add section {section.name}.')
|
||||
raise PEError(f'Not enough header space to add section {section.name}.')
|
||||
|
||||
data = section.content.read_bytes()
|
||||
|
||||
@ -653,155 +665,429 @@ def make_uki(opts):
|
||||
print(f"Wrote {'signed' if opts.sb_key else 'unsigned'} {opts.output}")
|
||||
|
||||
|
||||
def parse_args(args=None):
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class ConfigItem:
|
||||
@staticmethod
|
||||
def config_list_prepend(
|
||||
namespace: argparse.Namespace,
|
||||
group: Optional[str],
|
||||
dest: str,
|
||||
value: Any,
|
||||
) -> None:
|
||||
"Prepend value to namespace.<dest>"
|
||||
|
||||
assert not group
|
||||
|
||||
old = getattr(namespace, dest, [])
|
||||
setattr(namespace, dest, value + old)
|
||||
|
||||
@staticmethod
|
||||
def config_set_if_unset(
|
||||
namespace: argparse.Namespace,
|
||||
group: Optional[str],
|
||||
dest: str,
|
||||
value: Any,
|
||||
) -> None:
|
||||
"Set namespace.<dest> to value only if it was None"
|
||||
|
||||
assert not group
|
||||
|
||||
if getattr(namespace, dest) is None:
|
||||
setattr(namespace, dest, value)
|
||||
|
||||
@staticmethod
|
||||
def config_set_group(
|
||||
namespace: argparse.Namespace,
|
||||
group: Optional[str],
|
||||
dest: str,
|
||||
value: Any,
|
||||
) -> None:
|
||||
"Set namespace.<dest>[idx] to value, with idx derived from group"
|
||||
|
||||
if group not in namespace._groups:
|
||||
namespace._groups += [group]
|
||||
idx = namespace._groups.index(group)
|
||||
|
||||
old = getattr(namespace, dest, None)
|
||||
if old is None:
|
||||
old = []
|
||||
setattr(namespace, dest,
|
||||
old + ([None] * (idx - len(old))) + [value])
|
||||
|
||||
@staticmethod
|
||||
def parse_boolean(s: str) -> bool:
|
||||
"Parse 1/true/yes/y/t/on as true and 0/false/no/n/f/off/None as false"
|
||||
s_l = s.lower()
|
||||
if s_l in {'1', 'true', 'yes', 'y', 't', 'on'}:
|
||||
return True
|
||||
if s_l in {'0', 'false', 'no', 'n', 'f', 'off'}:
|
||||
return False
|
||||
raise ValueError('f"Invalid boolean literal: {s!r}')
|
||||
|
||||
# arguments for argparse.ArgumentParser.add_argument()
|
||||
name: Union[str, tuple[str, str]]
|
||||
dest: Optional[str] = None
|
||||
metavar: Optional[str] = None
|
||||
type: Optional[Callable] = None
|
||||
nargs: Optional[str] = None
|
||||
action: Optional[Union[str, Callable]] = None
|
||||
default: Any = None
|
||||
version: Optional[str] = None
|
||||
choices: Optional[tuple[str, ...]] = None
|
||||
help: Optional[str] = None
|
||||
|
||||
# metadata for config file parsing
|
||||
config_key: Optional[str] = None
|
||||
config_push: Callable[[argparse.Namespace, Optional[str], str, Any], None] = \
|
||||
config_set_if_unset
|
||||
|
||||
def _names(self) -> tuple[str, ...]:
|
||||
return self.name if isinstance(self.name, tuple) else (self.name,)
|
||||
|
||||
def argparse_dest(self) -> str:
|
||||
# It'd be nice if argparse exported this, but I don't see that in the API
|
||||
if self.dest:
|
||||
return self.dest
|
||||
return self._names()[0].lstrip('-').replace('-', '_')
|
||||
|
||||
def add_to(self, parser: argparse.ArgumentParser):
|
||||
kwargs = { key:val
|
||||
for key in dataclasses.asdict(self)
|
||||
if (key not in ('name', 'config_key', 'config_push') and
|
||||
(val := getattr(self, key)) is not None) }
|
||||
args = self._names()
|
||||
parser.add_argument(*args, **kwargs)
|
||||
|
||||
def apply_config(self, namespace, section, group, key, value) -> None:
|
||||
assert f'{section}/{key}' == self.config_key
|
||||
dest = self.argparse_dest()
|
||||
|
||||
conv: Callable[[str], Any]
|
||||
if self.action == argparse.BooleanOptionalAction:
|
||||
# We need to handle this case separately: the options are called
|
||||
# --foo and --no-foo, and no argument is parsed. But in the config
|
||||
# file, we have Foo=yes or Foo=no.
|
||||
conv = self.parse_boolean
|
||||
elif self.type:
|
||||
conv = self.type
|
||||
else:
|
||||
conv = lambda s:s
|
||||
|
||||
if self.nargs == '*':
|
||||
value = [conv(v) for v in value.split()]
|
||||
else:
|
||||
value = conv(value)
|
||||
|
||||
self.config_push(namespace, group, dest, value)
|
||||
|
||||
def config_example(self) -> tuple[Optional[str], Optional[str], Optional[str]]:
|
||||
if not self.config_key:
|
||||
return None, None, None
|
||||
section_name, key = self.config_key.split('/', 1)
|
||||
if section_name.endswith(':'):
|
||||
section_name += 'NAME'
|
||||
if self.choices:
|
||||
value = '|'.join(self.choices)
|
||||
else:
|
||||
value = self.metavar or self.argparse_dest().upper()
|
||||
return (section_name, key, value)
|
||||
|
||||
|
||||
CONFIG_ITEMS = [
|
||||
ConfigItem(
|
||||
'--version',
|
||||
action = 'version',
|
||||
version = f'ukify {__version__}',
|
||||
),
|
||||
|
||||
ConfigItem(
|
||||
'--summary',
|
||||
help = 'print parsed config and exit',
|
||||
action = 'store_true',
|
||||
),
|
||||
|
||||
ConfigItem(
|
||||
'linux',
|
||||
metavar = 'LINUX',
|
||||
type = pathlib.Path,
|
||||
nargs = '?',
|
||||
help = 'vmlinuz file [.linux section]',
|
||||
config_key = 'UKI/Linux',
|
||||
),
|
||||
|
||||
ConfigItem(
|
||||
'initrd',
|
||||
metavar = 'INITRD…',
|
||||
type = pathlib.Path,
|
||||
nargs = '*',
|
||||
help = 'initrd files [.initrd section]',
|
||||
config_key = 'UKI/Initrd',
|
||||
config_push = ConfigItem.config_list_prepend,
|
||||
),
|
||||
|
||||
ConfigItem(
|
||||
('--config', '-c'),
|
||||
metavar = 'PATH',
|
||||
help = 'configuration file',
|
||||
),
|
||||
|
||||
ConfigItem(
|
||||
'--cmdline',
|
||||
metavar = 'TEXT|@PATH',
|
||||
help = 'kernel command line [.cmdline section]',
|
||||
config_key = 'UKI/Cmdline',
|
||||
),
|
||||
|
||||
ConfigItem(
|
||||
'--os-release',
|
||||
metavar = 'TEXT|@PATH',
|
||||
help = 'path to os-release file [.osrel section]',
|
||||
config_key = 'UKI/OSRelease',
|
||||
),
|
||||
|
||||
ConfigItem(
|
||||
'--devicetree',
|
||||
metavar = 'PATH',
|
||||
type = pathlib.Path,
|
||||
help = 'Device Tree file [.dtb section]',
|
||||
config_key = 'UKI/DeviceTree',
|
||||
),
|
||||
ConfigItem(
|
||||
'--splash',
|
||||
metavar = 'BMP',
|
||||
type = pathlib.Path,
|
||||
help = 'splash image bitmap file [.splash section]',
|
||||
config_key = 'UKI/Splash',
|
||||
),
|
||||
ConfigItem(
|
||||
'--pcrpkey',
|
||||
metavar = 'KEY',
|
||||
type = pathlib.Path,
|
||||
help = 'embedded public key to seal secrets to [.pcrpkey section]',
|
||||
config_key = 'UKI/PCRPKey',
|
||||
),
|
||||
ConfigItem(
|
||||
'--uname',
|
||||
metavar='VERSION',
|
||||
help='"uname -r" information [.uname section]',
|
||||
config_key = 'UKI/Uname',
|
||||
),
|
||||
|
||||
ConfigItem(
|
||||
'--efi-arch',
|
||||
metavar = 'ARCH',
|
||||
choices = ('ia32', 'x64', 'arm', 'aa64', 'riscv64'),
|
||||
help = 'target EFI architecture',
|
||||
config_key = 'UKI/EFIArch',
|
||||
),
|
||||
|
||||
ConfigItem(
|
||||
'--stub',
|
||||
type = pathlib.Path,
|
||||
help = 'path to the sd-stub file [.text,.data,… sections]',
|
||||
config_key = 'UKI/Stub',
|
||||
),
|
||||
|
||||
ConfigItem(
|
||||
'--section',
|
||||
dest = 'sections',
|
||||
metavar = 'NAME:TEXT|@PATH',
|
||||
type = Section.parse_arg,
|
||||
action = 'append',
|
||||
default = [],
|
||||
help = 'additional section as name and contents [NAME section]',
|
||||
),
|
||||
|
||||
ConfigItem(
|
||||
'--pcr-banks',
|
||||
metavar = 'BANK…',
|
||||
type = parse_banks,
|
||||
config_key = 'UKI/PCRBanks',
|
||||
),
|
||||
|
||||
ConfigItem(
|
||||
'--signing-engine',
|
||||
metavar = 'ENGINE',
|
||||
help = 'OpenSSL engine to use for signing',
|
||||
config_key = 'UKI/SigningEngine',
|
||||
),
|
||||
ConfigItem(
|
||||
'--secureboot-private-key',
|
||||
dest = 'sb_key',
|
||||
help = 'path to key file or engine-specific designation for SB signing',
|
||||
config_key = 'UKI/SecureBootPrivateKey',
|
||||
),
|
||||
ConfigItem(
|
||||
'--secureboot-certificate',
|
||||
dest = 'sb_cert',
|
||||
help = 'path to certificate file or engine-specific designation for SB signing',
|
||||
config_key = 'UKI/SecureBootCertificate',
|
||||
),
|
||||
|
||||
ConfigItem(
|
||||
'--sign-kernel',
|
||||
action = argparse.BooleanOptionalAction,
|
||||
help = 'Sign the embedded kernel',
|
||||
config_key = 'UKI/SignKernel',
|
||||
),
|
||||
|
||||
ConfigItem(
|
||||
'--pcr-private-key',
|
||||
dest = 'pcr_private_keys',
|
||||
metavar = 'PATH',
|
||||
type = pathlib.Path,
|
||||
action = 'append',
|
||||
help = 'private part of the keypair for signing PCR signatures',
|
||||
config_key = 'PCRSignature:/PCRPrivateKey',
|
||||
config_push = ConfigItem.config_set_group,
|
||||
),
|
||||
ConfigItem(
|
||||
'--pcr-public-key',
|
||||
dest = 'pcr_public_keys',
|
||||
metavar = 'PATH',
|
||||
type = pathlib.Path,
|
||||
action = 'append',
|
||||
help = 'public part of the keypair for signing PCR signatures',
|
||||
config_key = 'PCRSignature:/PCRPublicKey',
|
||||
config_push = ConfigItem.config_set_group,
|
||||
),
|
||||
ConfigItem(
|
||||
'--phases',
|
||||
dest = 'phase_path_groups',
|
||||
metavar = 'PHASE-PATH…',
|
||||
type = parse_phase_paths,
|
||||
action = 'append',
|
||||
help = 'phase-paths to create signatures for',
|
||||
config_key = 'PCRSignature:/Phases',
|
||||
config_push = ConfigItem.config_set_group,
|
||||
),
|
||||
|
||||
ConfigItem(
|
||||
'--tools',
|
||||
type = pathlib.Path,
|
||||
action = 'append',
|
||||
help = 'Directories to search for tools (systemd-measure, …)',
|
||||
),
|
||||
|
||||
ConfigItem(
|
||||
('--output', '-o'),
|
||||
type = pathlib.Path,
|
||||
help = 'output file path',
|
||||
),
|
||||
|
||||
ConfigItem(
|
||||
'--measure',
|
||||
action = argparse.BooleanOptionalAction,
|
||||
help = 'print systemd-measure output for the UKI',
|
||||
),
|
||||
]
|
||||
|
||||
CONFIGFILE_ITEMS = { item.config_key:item
|
||||
for item in CONFIG_ITEMS
|
||||
if item.config_key }
|
||||
|
||||
|
||||
def apply_config(namespace, filename=None):
|
||||
if filename is None:
|
||||
filename = namespace.config
|
||||
if filename is None:
|
||||
return
|
||||
|
||||
# Fill in ._groups based on --pcr-public-key=, --pcr-private-key=, and --phases=.
|
||||
assert '_groups' not in namespace
|
||||
n_pcr_priv = len(namespace.pcr_private_keys or ())
|
||||
namespace._groups = list(range(n_pcr_priv))
|
||||
|
||||
cp = configparser.ConfigParser(
|
||||
comment_prefixes='#',
|
||||
inline_comment_prefixes='#',
|
||||
delimiters='=',
|
||||
empty_lines_in_values=False,
|
||||
interpolation=None,
|
||||
strict=False)
|
||||
# Do not make keys lowercase
|
||||
cp.optionxform = lambda option: option
|
||||
|
||||
cp.read(filename)
|
||||
|
||||
for section_name, section in cp.items():
|
||||
idx = section_name.find(':')
|
||||
if idx >= 0:
|
||||
section_name, group = section_name[:idx+1], section_name[idx+1:]
|
||||
if not section_name or not group:
|
||||
raise ValueError('Section name components cannot be empty')
|
||||
if ':' in group:
|
||||
raise ValueError('Section name cannot contain more than one ":"')
|
||||
else:
|
||||
group = None
|
||||
for key, value in section.items():
|
||||
if item := CONFIGFILE_ITEMS.get(f'{section_name}/{key}'):
|
||||
item.apply_config(namespace, section_name, group, key, value)
|
||||
else:
|
||||
print(f'Unknown config setting [{section_name}] {key}=')
|
||||
|
||||
|
||||
def config_example():
|
||||
prev_section = None
|
||||
for item in CONFIG_ITEMS:
|
||||
section, key, value = item.config_example()
|
||||
if section:
|
||||
if prev_section != section:
|
||||
if prev_section:
|
||||
yield ''
|
||||
yield f'[{section}]'
|
||||
prev_section = section
|
||||
yield f'{key} = {value}'
|
||||
|
||||
|
||||
def create_parser():
|
||||
p = argparse.ArgumentParser(
|
||||
description='Build and sign Unified Kernel Images',
|
||||
allow_abbrev=False,
|
||||
usage='''\
|
||||
usage: ukify [options…] [linux [initrd…]]
|
||||
ukify -h | --help
|
||||
''')
|
||||
ukify [options…] [LINUX INITRD…]
|
||||
''',
|
||||
epilog='\n '.join(('config file:', *config_example())),
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
|
||||
for item in CONFIG_ITEMS:
|
||||
item.add_to(p)
|
||||
|
||||
# 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,
|
||||
nargs="?",
|
||||
help='vmlinuz file [.linux section]')
|
||||
p.add_argument('initrd',
|
||||
type=pathlib.Path,
|
||||
nargs='*',
|
||||
help='initrd files [.initrd section]')
|
||||
return p
|
||||
|
||||
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 to 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,
|
||||
action='append',
|
||||
help='Directories to search for tools (systemd-measure, ...)')
|
||||
|
||||
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.linux is not None:
|
||||
path_is_readable(opts.linux)
|
||||
for initrd in opts.initrd or ():
|
||||
path_is_readable(initrd)
|
||||
path_is_readable(opts.devicetree)
|
||||
path_is_readable(opts.pcrpkey)
|
||||
for key in opts.pcr_private_keys or ():
|
||||
path_is_readable(key)
|
||||
for key in opts.pcr_public_keys or ():
|
||||
path_is_readable(key)
|
||||
|
||||
def finalize_options(opts):
|
||||
if opts.cmdline and opts.cmdline.startswith('@'):
|
||||
opts.cmdline = path_is_readable(opts.cmdline[1:])
|
||||
opts.cmdline = pathlib.Path(opts.cmdline[1:])
|
||||
elif opts.cmdline:
|
||||
# Drop whitespace from the commandline. If we're reading from a file,
|
||||
# we copy the contents verbatim. But configuration specified on the commandline
|
||||
# or in the config file may contain additional whitespace that has no meaning.
|
||||
opts.cmdline = ' '.join(opts.cmdline.split())
|
||||
|
||||
if opts.os_release is not None and opts.os_release.startswith('@'):
|
||||
opts.os_release = path_is_readable(opts.os_release[1:])
|
||||
elif opts.os_release is None and opts.linux is not None:
|
||||
if opts.os_release and opts.os_release.startswith('@'):
|
||||
opts.os_release = pathlib.Path(opts.os_release[1:])
|
||||
elif not opts.os_release and opts.linux:
|
||||
p = pathlib.Path('/etc/os-release')
|
||||
if not p.exists():
|
||||
p = path_is_readable('/usr/lib/os-release')
|
||||
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 = path_is_readable(f'/usr/lib/systemd/boot/efi/linux{opts.efi_arch}.efi.stub')
|
||||
opts.stub = pathlib.Path(f'/usr/lib/systemd/boot/efi/linux{opts.efi_arch}.efi.stub')
|
||||
|
||||
if opts.signing_engine is None:
|
||||
opts.sb_key = path_is_readable(opts.sb_key) if opts.sb_key else None
|
||||
opts.sb_cert = path_is_readable(opts.sb_cert) if opts.sb_cert else None
|
||||
if opts.sb_key:
|
||||
opts.sb_key = pathlib.Path(opts.sb_key)
|
||||
if opts.sb_cert:
|
||||
opts.sb_cert = pathlib.Path(opts.sb_cert)
|
||||
|
||||
if bool(opts.sb_key) ^ bool(opts.sb_cert):
|
||||
raise ValueError('--secureboot-private-key= and --secureboot-certificate= must be specified together')
|
||||
@ -809,14 +1095,6 @@ usage: ukify [options…] [linux [initrd…]]
|
||||
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:
|
||||
if opts.linux is None:
|
||||
raise ValueError('--output= must be specified when building a PE addon')
|
||||
@ -826,6 +1104,30 @@ usage: ukify [options…] [linux [initrd…]]
|
||||
for section in opts.sections:
|
||||
section.check_name()
|
||||
|
||||
if opts.summary:
|
||||
# TODO: replace pprint() with some fancy formatting.
|
||||
pprint.pprint(vars(opts))
|
||||
sys.exit()
|
||||
|
||||
|
||||
def parse_args(args=None):
|
||||
p = create_parser()
|
||||
opts = p.parse_args(args)
|
||||
|
||||
# Check that --pcr-public-key=, --pcr-private-key=, and --phases=
|
||||
# have either the same number of arguments are are not specified at all.
|
||||
n_pcr_pub = None if opts.pcr_public_keys is None else len(opts.pcr_public_keys)
|
||||
n_pcr_priv = None if opts.pcr_private_keys is None else len(opts.pcr_private_keys)
|
||||
n_phase_path_groups = None if opts.phase_path_groups is None else len(opts.phase_path_groups)
|
||||
if n_pcr_pub is not None and n_pcr_pub != n_pcr_priv:
|
||||
raise ValueError('--pcr-public-key= specifications must match --pcr-private-key=')
|
||||
if n_phase_path_groups is not None and n_phase_path_groups != n_pcr_priv:
|
||||
raise ValueError('--phases= specifications must match --pcr-private-key=')
|
||||
|
||||
apply_config(opts)
|
||||
|
||||
finalize_options(opts)
|
||||
|
||||
return opts
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user