mirror of
https://github.com/systemd/systemd.git
synced 2026-11-06 05:17:24 +00:00
keyring-setup: add systemd-keyring-setup (#44114)
Lennart asked in #44114 to split the preparatory stuff out so it can be merged in smaller PRs. So this is a stack of five, each based on the one before it and each showing only its own commits: 1. #44323, the C helpers (merged) 2. #44324, support for programs written in Rust (merged) 3. #44420, bindings.h lists only the headers Rust code uses (merged) 4. #44325, the systemd_shared wrappers systemd-keyring-setup needs (merged) 5. #44114, systemd-keyring-setup itself This one is 5. This adds systemd-keyring-setup which enrolls X.509 certificates from the VOA hierarchy into the kernel's .dm-verity, .fs-verity and .bpf keyrings at boot and seals them afterwards. It's the first program in systemd written in Rust. The C prototype in #43549 was never merged, so nothing is ported here. The kernel has various keyrings that early userspace may choose to set up. Right now it has the .fs-verity and .dm-verity keyrings and in the next kernel it will also have a .bpf keyring. All three keyrings accept X.509 certificates in DER form. The .dm-verity and .bpf keyrings are inactive and restricted by default unless the respective kernel command line options are passed that leave them unrestricted. Both keyrings are ignored until they are restricted. IOW, restricting them causes the kernel to use them for signature verification. The .fs-verity keyring is always active and is taken into account as soon as it contains a key. The UAPI group published the File Hierarchy for the Verification of OS Artifacts (VOA) specification (cf. [1]). systemd-keyring-setup reads the certificates for each keyring from the kernel-keyring role with the keyring as the context and the trust anchors from the trust-anchor-kernel-keyring counterpart: ``` <load-path>/<os>/kernel-keyring/<context>/x509/<name>-certificate.pem <load-path>/<os>/trust-anchor-kernel-keyring/<context>/x509/<name>-certificate.pem ``` The load paths in descending priority are /etc/voa, /run/voa, /usr/local/share/voa and /usr/share/voa. `<os>` is the exact identifier `ID[:VERSION_ID[:VARIANT_ID[:IMAGE_ID[:IMAGE_VERSION]]]]` and the bare `ID`. `<context>` is dm-verity, fs-verity or bpf. Examples for a vendor shipping verifiers on Debian: ``` /usr/share/voa/debian/kernel-keyring/dm-verity/x509/vendor-certificate.pem /usr/share/voa/debian/kernel-keyring/fs-verity/x509/vendor-certificate.pem /usr/share/voa/debian/kernel-keyring/bpf/x509/vendor-certificate.pem ``` A CA certificate as a trust anchor and an admin override in /etc for a specific release: ``` /usr/share/voa/debian/trust-anchor-kernel-keyring/dm-verity/x509/vendor-ca-certificate.pem /etc/voa/debian:13/kernel-keyring/dm-verity/x509/corp-certificate.pem ``` Masking works as everywhere in VOA via a /dev/null symlink under the same file name in any load path: ``` /etc/voa/debian/kernel-keyring/dm-verity/x509/vendor-certificate.pem -> /dev/null ``` The lookup is anchored at a pinned root directory and drops verifiers that somebody else could have tampered with. That's what CHASE_MAX_MODE and the CONF_FILES_CHASE_* flags are for. The VOA lookup, the keyring helpers and the X.509 helpers are all C in libsystemd-shared and the Rust program calls into them. After enrolling, the keyring is restricted to certificates signed by an enrolled key and SetAttr is dropped from its permission mask. So neither the restriction nor the mask can be changed anymore. Any keyring that's still open is sealed at the end of a run, even when it's empty. The binary may run in the initrd and on the host. A second run detects the sealed keyring, skips certificates that are enrolled already and enrolls the ones signed by an enrolled key. The service isn't enabled by default. Certificates can also be shipped as `keyring-setup.<keyring>.<name>` system credentials (SMBIOS, ESP credential drop-ins, --set-credential for the boot loader). This lets users avoid having to ship keys as part of the initrd. We don't enroll them directly but follow the VOA spec instead. They are placed into /run/voa first so masking, merging and deduplication apply to them like to any other file: ``` /run/voa/debian/kernel-keyring/dm-verity/x509/corp-certificate.pem ``` A keyring-setup.os credential overrides the OS identifiers derived from os-release. This is useful for generic initrds whose identity differs from the host's. The specification requires the extended key usage of a verifier to be aligned with its purpose. The kernel has no concept of this. So systemd-keyring-setup checks it. TEST-98-KEYRING-SETUP boots with and without dm_verity.keyring_unsealed=1 and the kernel verifies the signed test image against the keyring that systemd-keyring-setup provisioned. TEST-90 copes with a .dm-verity keyring that was sealed at boot. [1]: https://uapi-group.org/specifications/specs/file_hierarchy_for_the_verification_of_os_artifacts/ Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
This commit is contained in:
27 files changed
+2273
-11
No files matched your search
@@ -209,6 +209,32 @@ CHANGES WITH 263 in spe:
|
||||
"# [Experimental]" comment line in the interface description, which
|
||||
sd_varlink_idl_parse() turns back into the flag.
|
||||
|
||||
Changes in systemd-keyring-setup:
|
||||
|
||||
* A new service systemd-keyring-setup.service has been added that
|
||||
enrolls X.509 certificates into the kernel's .dm-verity, .fs-verity
|
||||
and .bpf keyrings early during boot, and seals the keyrings
|
||||
afterwards. The certificates are read from the File Hierarchy for
|
||||
the Verification of OS Artifacts (VOA), i.e. from /etc/voa/,
|
||||
/run/voa/, /usr/local/share/voa/ and /usr/share/voa/, or may be
|
||||
passed in as keyring-setup.* system credentials. This allows the
|
||||
kernel to verify signed dm-verity volumes, fs-verity files and BPF
|
||||
programs with certificates provided by the OS vendor or the local
|
||||
administrator, without rebuilding the kernel or enrolling keys into
|
||||
the firmware. Note that the kernel seals the .dm-verity and .bpf
|
||||
keyrings while they are still empty, unless
|
||||
dm_verity.keyring_unsealed=1 or bpf.keyring_unsealed=1 is specified
|
||||
on the kernel command line, respectively. The service is enabled
|
||||
statically whenever it is built, and is considered experimental for
|
||||
now. See
|
||||
systemd-keyring-setup.service(8) and
|
||||
https://uapi-group.org/specifications/specs/file_hierarchy_for_the_verification_of_os_artifacts/
|
||||
for details.
|
||||
|
||||
systemd-keyring-setup is the first systemd component written in
|
||||
Rust, and is hence only built if Rust support is enabled with the
|
||||
new -Drust= build option.
|
||||
|
||||
CHANGES WITH 262:
|
||||
|
||||
Announcements of Future Feature Removals and Incompatible Changes:
|
||||
|
||||
@@ -87,6 +87,7 @@ REQUIREMENTS:
|
||||
PIDFD_INFO_COREDUMP by ioctl(PIDFD_GET_INFO)
|
||||
≥ 6.17 for coredump socket pattern ('@@' prefix)
|
||||
≥ 6.19 for PIDFD_INFO_SUPPORTED_MASK and PIDFD_INFO_COREDUMP_SIGNAL
|
||||
≥ 7.0 for the .dm-verity keyring provisioned by systemd-keyring-setup
|
||||
≥ 7.1 for PIDFD_INFO_COREDUMP_CODE
|
||||
|
||||
✅ systemd utilizes several new kernel APIs, but will fall back gracefully
|
||||
@@ -182,6 +183,10 @@ REQUIREMENTS:
|
||||
CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG_PLATFORM_KEYRING
|
||||
CONFIG_IMA_ARCH_POLICY
|
||||
CONFIG_INTEGRITY_MACHINE_KEYRING
|
||||
Required to verify signed Verity images and fs-verity files with
|
||||
certificates enrolled by systemd-keyring-setup:
|
||||
CONFIG_KEYS
|
||||
CONFIG_FS_VERITY_BUILTIN_SIGNATURES
|
||||
|
||||
Required for reading credentials from SMBIOS:
|
||||
CONFIG_DMI
|
||||
|
||||
@@ -1143,6 +1143,10 @@ manpages = [
|
||||
'systemd-journald@.service',
|
||||
'systemd-journald@.socket'],
|
||||
''],
|
||||
['systemd-keyring-setup.service',
|
||||
'8',
|
||||
['systemd-keyring-setup'],
|
||||
'HAVE_OPENSSL HAVE_RUST'],
|
||||
['systemd-keyutil', '1', [], ''],
|
||||
['systemd-localed.service', '8', ['systemd-localed'], 'ENABLE_LOCALED'],
|
||||
['systemd-logind.service',
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--*-nxml-*-->
|
||||
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
|
||||
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
|
||||
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
|
||||
<refentry id="systemd-keyring-setup.service" conditional='HAVE_OPENSSL HAVE_RUST'
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
|
||||
<refentryinfo>
|
||||
<title>systemd-keyring-setup.service</title>
|
||||
<productname>systemd</productname>
|
||||
</refentryinfo>
|
||||
|
||||
<refmeta>
|
||||
<refentrytitle>systemd-keyring-setup.service</refentrytitle>
|
||||
<manvolnum>8</manvolnum>
|
||||
</refmeta>
|
||||
|
||||
<refnamediv>
|
||||
<refname>systemd-keyring-setup.service</refname>
|
||||
<refname>systemd-keyring-setup</refname>
|
||||
<refpurpose>Enroll certificates into kernel keyrings</refpurpose>
|
||||
</refnamediv>
|
||||
|
||||
<refsynopsisdiv>
|
||||
<para><filename>systemd-keyring-setup.service</filename></para>
|
||||
<cmdsynopsis>
|
||||
<command>/usr/lib/systemd/systemd-keyring-setup</command>
|
||||
<arg choice="opt" rep="repeat">OPTIONS</arg>
|
||||
<arg choice="opt" rep="repeat">KEYRING</arg>
|
||||
</cmdsynopsis>
|
||||
</refsynopsisdiv>
|
||||
|
||||
<xi:include href="experimental.xml" xpointer="component" />
|
||||
|
||||
<refsect1>
|
||||
<title>Description</title>
|
||||
|
||||
<para><filename>systemd-keyring-setup.service</filename> is an early boot service that enrolls X.509
|
||||
certificates into the kernel keyrings used to verify signatures on dm-verity volumes, fs-verity files and
|
||||
BPF programs, and then seals these keyrings. The certificates are read from the <ulink
|
||||
url="https://uapi-group.org/specifications/specs/file_hierarchy_for_the_verification_of_os_artifacts/">File
|
||||
Hierarchy for the Verification of OS Artifacts</ulink> (VOA). This allows the kernel to verify signatures
|
||||
with keys provided by the OS vendor or the local administrator, without rebuilding the kernel and without
|
||||
enrolling the keys into the firmware.</para>
|
||||
|
||||
<para>The following keyrings are provisioned, each from a VOA <emphasis>context</emphasis> of the
|
||||
<literal>kernel-keyring</literal> purpose:</para>
|
||||
|
||||
<table>
|
||||
<title>Keyrings and VOA contexts</title>
|
||||
<tgroup cols="3">
|
||||
<colspec colname="keyring"/>
|
||||
<colspec colname="context"/>
|
||||
<colspec colname="use"/>
|
||||
<thead>
|
||||
<row>
|
||||
<entry>Keyring</entry>
|
||||
<entry>VOA context</entry>
|
||||
<entry>Used to verify</entry>
|
||||
</row>
|
||||
</thead>
|
||||
<tbody>
|
||||
<row>
|
||||
<entry><literal>.dm-verity</literal></entry>
|
||||
<entry><literal>dm-verity</literal></entry>
|
||||
<entry>Signatures of the root hashes of dm-verity volumes, such as those of Discoverable Disk Images set up by <citerefentry><refentrytitle>systemd-veritysetup@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>, <citerefentry><refentrytitle>systemd-dissect</refentrytitle><manvolnum>1</manvolnum></citerefentry> or the <varname>RootImage=</varname> setting of services</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry><literal>.fs-verity</literal></entry>
|
||||
<entry><literal>fs-verity</literal></entry>
|
||||
<entry>Built-in signatures of fs-verity protected files</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry><literal>.bpf</literal></entry>
|
||||
<entry><literal>bpf</literal></entry>
|
||||
<entry>Signatures of BPF programs</entry>
|
||||
</row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</table>
|
||||
|
||||
<para>The kernel only uses the <literal>.dm-verity</literal> and <literal>.bpf</literal> keyrings once they
|
||||
have been sealed, and seals them itself while they are still empty, unless
|
||||
<varname>dm_verity.keyring_unsealed=1</varname> or <varname>bpf.keyring_unsealed=1</varname> is specified on
|
||||
the kernel command line, respectively. Hence, these options are required to provision the two keyrings, see
|
||||
below. The <literal>.fs-verity</literal> keyring is not sealed by the kernel, and is used as soon as it
|
||||
contains a key.</para>
|
||||
|
||||
<para>For each keyring that is not sealed yet, the service enrolls the certificates it finds and then seals
|
||||
the keyring: it installs a restriction so that only certificates signed by a key in the keyring can be added
|
||||
afterwards, and removes the permission to change this restriction or the permissions of the keyring. The
|
||||
keyring is sealed even if no certificates were found, or if some of them could not be enrolled. Keys that
|
||||
were already in the keyring before the service ran are sealed in as well, and a warning is logged about
|
||||
them.</para>
|
||||
|
||||
<para>Note that kexec will unseal and empty the keyrings.</para>
|
||||
|
||||
<para>The service runs both in the initrd and on the host. Usually, the keyrings are provisioned in the
|
||||
initrd, since the root file system might itself be a dm-verity volume that is verified with one of the
|
||||
enrolled keys. When the service runs again on the host, the keyrings have been sealed already. In this case,
|
||||
only certificates signed by a key that was enrolled earlier can be added, and all other certificates are
|
||||
rejected. Certificates that have been enrolled already are skipped.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Certificate Directories and Precedence</title>
|
||||
|
||||
<para>Certificates are read from the VOA load paths <filename>/etc/voa/</filename>,
|
||||
<filename>/run/voa/</filename>, <filename>/usr/local/share/voa/</filename> and
|
||||
<filename>/usr/share/voa/</filename>, in order of decreasing priority. For each keyring, artifact verifiers
|
||||
are read from
|
||||
<filename><replaceable>OS</replaceable>/kernel-keyring/<replaceable>CONTEXT</replaceable>/x509/<replaceable>NAME</replaceable>-certificate.pem</filename>,
|
||||
and trust anchors from
|
||||
<filename><replaceable>OS</replaceable>/trust-anchor-kernel-keyring/<replaceable>CONTEXT</replaceable>/x509/<replaceable>NAME</replaceable>-certificate.pem</filename>
|
||||
below each load path. Both are enrolled into the same keyring, and only differ in how their usage is
|
||||
checked, see below. <replaceable>NAME</replaceable> may consist of lowercase letters, digits,
|
||||
<literal>.</literal>, <literal>_</literal> and <literal>-</literal>, other files are ignored. Each file must
|
||||
contain a single PEM-encoded certificate. A file that contains more than one certificate is ignored as a
|
||||
whole. CA certificates should be placed in separate files among the trust anchors instead.</para>
|
||||
|
||||
<para>Certificates are looked up for two OS identifiers derived from
|
||||
<citerefentry><refentrytitle>os-release</refentrytitle><manvolnum>5</manvolnum></citerefentry>: the exact
|
||||
identifier, and the value of <varname>ID=</varname> alone. The exact identifier consists of the values of
|
||||
<varname>ID=</varname>, <varname>VERSION_ID=</varname>, <varname>VARIANT_ID=</varname>,
|
||||
<varname>IMAGE_ID=</varname> and <varname>IMAGE_VERSION=</varname>, separated by <literal>:</literal>. A
|
||||
field that is not set is left empty, and unset fields at the end are omitted. For example,
|
||||
<literal>ID=debian</literal> and <literal>VERSION_ID=13</literal> result in the identifiers
|
||||
<literal>debian:13</literal> and <literal>debian</literal>. If <varname>ID=</varname> is not set,
|
||||
<literal>linux</literal> is used. If one of the other fields contains characters that are not permitted by
|
||||
the VOA specification, only the value of <varname>ID=</varname> is used. If <varname>ID=</varname> itself
|
||||
contains such characters, no certificates are looked up. The <varname>keyring-setup.os</varname> credential
|
||||
may be used to specify the identifiers explicitly, see below.</para>
|
||||
|
||||
<para>Note that the VOA specification requires different semantics than the usual logic for configuration
|
||||
files, where a file in a directory with higher priority replaces a file with the same name in a directory
|
||||
with lower priority. Instead, X.509 certificates are merged: all files in all load paths are used. A
|
||||
certificate that is found in more than one place is only enrolled once. If two different certificates have
|
||||
the same subject and key identifier, only one of them can be added to a keyring. In this case, certificates
|
||||
found for the exact OS identifier take precedence over the ones found for the value of <varname>ID=</varname>
|
||||
alone, and among those, the certificate from the load path with the highest priority is enrolled.</para>
|
||||
|
||||
<para>A certificate may be masked by placing a symlink to <filename>/dev/null</filename> with the same file
|
||||
name in any of the load paths, usually in <filename>/etc/voa/</filename> or <filename>/run/voa/</filename>.
|
||||
As required by the VOA specification, a masked certificate is ignored in all load paths, including the
|
||||
ones with a higher priority than the one containing the symlink. Masking is meant as a substitute for
|
||||
revocation: if a certificate is considered compromised, a single symlink ensures that none of its copies is
|
||||
enrolled. Other symlinks are followed as long as they point to a location within the VOA hierarchy, and
|
||||
symlinks to certificate files only if the target has the same file name.</para>
|
||||
|
||||
<para>To make sure that certificates cannot be placed by unprivileged users, files that are executable or
|
||||
world-writable, and directories that are world-writable, are ignored. Once a directory owned by an
|
||||
unprivileged user has been entered, the lookup does not continue into files or directories owned by anyone
|
||||
else.</para>
|
||||
|
||||
<para>As required by the VOA specification, the usage of each certificate is checked against its purpose.
|
||||
An artifact verifier is ignored if it has an extended key usage extension that permits neither code signing
|
||||
nor any extended key usage, or a key usage extension that does not permit digital signatures. A trust anchor
|
||||
that is not a CA certificate is enrolled nonetheless, since self-signed certificates that act as their own
|
||||
trust anchor are common, but a message is logged about it. Certificates with invalid extensions are
|
||||
ignored.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Credentials</title>
|
||||
|
||||
<para><command>systemd-keyring-setup</command> supports the service credentials logic as implemented by
|
||||
<varname>ImportCredential=</varname>/<varname>LoadCredential=</varname>/<varname>SetCredential=</varname>
|
||||
(see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
|
||||
details). The following credentials are used when passed in:</para>
|
||||
|
||||
<variablelist class='system-credentials'>
|
||||
<varlistentry>
|
||||
<term><varname>keyring-setup.dm-verity.<replaceable>NAME</replaceable></varname></term>
|
||||
<term><varname>keyring-setup.fs-verity.<replaceable>NAME</replaceable></varname></term>
|
||||
<term><varname>keyring-setup.bpf.<replaceable>NAME</replaceable></varname></term>
|
||||
|
||||
<listitem><para>A PEM-encoded X.509 certificate to enroll into the <literal>.dm-verity</literal>,
|
||||
<literal>.fs-verity</literal> or <literal>.bpf</literal> keyring, respectively.
|
||||
<replaceable>NAME</replaceable> may consist of lowercase letters, digits, <literal>.</literal>,
|
||||
<literal>_</literal> and <literal>-</literal>. As suggested by the VOA specification for certificates
|
||||
obtained from elsewhere, the certificate is not enrolled directly, but is first written to
|
||||
<filename>/run/voa/<replaceable>OS</replaceable>/kernel-keyring/<replaceable>CONTEXT</replaceable>/x509/<replaceable>NAME</replaceable>-certificate.pem</filename>,
|
||||
using the first OS identifier. Hence, masking and merging apply to it like to any other file in the
|
||||
hierarchy. Only the certificate itself is written there, so that a private key included in the
|
||||
credential is not made readable to other users. A credential that does not contain exactly one
|
||||
certificate is ignored.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v263"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>keyring-setup.os</varname></term>
|
||||
|
||||
<listitem><para>A whitespace-separated list of up to 64 OS identifiers to use instead of the ones
|
||||
derived from <citerefentry><refentrytitle>os-release</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
|
||||
This is useful for generic initrds, whose <filename>os-release</filename> does not match the one of the
|
||||
host. If the list is empty, too long or contains an invalid identifier, the credential is ignored and
|
||||
the identifiers derived from <filename>os-release</filename> are used.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v263"/></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
|
||||
<para>See
|
||||
<citerefentry><refentrytitle>systemd.system-credentials</refentrytitle><manvolnum>7</manvolnum></citerefentry>
|
||||
for details about system credentials.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Options</title>
|
||||
|
||||
<para>If one or more <replaceable>KEYRING</replaceable> names, such as <literal>.dm-verity</literal>, are
|
||||
specified, only those keyrings are processed. Otherwise, all keyrings listed above are processed.</para>
|
||||
|
||||
<para>The following options are understood:</para>
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><option>--dry-run</option></term>
|
||||
|
||||
<listitem><para>Show which certificates would be enrolled into which keyring, and the current state
|
||||
of the keyrings, without changing anything.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v263"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<xi:include href="standard-options.xml" xpointer="no-pager"/>
|
||||
<xi:include href="standard-options.xml" xpointer="json"/>
|
||||
<xi:include href="standard-options.xml" xpointer="help"/>
|
||||
<xi:include href="standard-options.xml" xpointer="version"/>
|
||||
</variablelist>
|
||||
|
||||
<para><option>--json=</option> is only supported together with <option>--dry-run</option>.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Exit status</title>
|
||||
|
||||
<para>On success, 0 is returned. This includes the cases where a keyring does not exist, or has been
|
||||
provisioned by an earlier run already. If a certificate or a credential could not be used, for example
|
||||
because it is malformed, or because it is not signed by a key in a keyring that has been sealed already, 65
|
||||
(<constant>EX_DATAERR</constant>) is returned. <filename>systemd-keyring-setup.service</filename> treats this
|
||||
exit status as success. On other failures, a different non-zero exit status is returned.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Kernel Command Line</title>
|
||||
|
||||
<variablelist class='kernel-commandline-options'>
|
||||
<varlistentry>
|
||||
<term><varname>dm_verity.keyring_unsealed=</varname></term>
|
||||
<term><varname>bpf.keyring_unsealed=</varname></term>
|
||||
|
||||
<listitem><para>These options are interpreted by the kernel. If set to <literal>1</literal>, the kernel
|
||||
leaves the <literal>.dm-verity</literal> or <literal>.bpf</literal> keyring unsealed during boot, so
|
||||
that it can be provisioned by <filename>systemd-keyring-setup.service</filename>. Otherwise, the kernel
|
||||
seals the keyring while it is still empty, and no certificates can be enrolled into it.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v263"/></listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>Security Considerations</title>
|
||||
|
||||
<para>The enrolled certificates can only be trusted as much as the files they were read from. The intended
|
||||
deployment is to provision the keyrings from the initrd of a signed Unified Kernel Image, see
|
||||
<citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para>
|
||||
|
||||
<para>Until a keyring is sealed, any sufficiently privileged process may add arbitrary keys to it. After it
|
||||
has been sealed, privileged processes may still remove keys from it, and add certificates that are signed
|
||||
by a key in the keyring, but they cannot add other keys, or change the restriction or the permissions of
|
||||
the keyring. The keyrings stay sealed until the next reboot. A soft reboot, see
|
||||
<citerefentry><refentrytitle>systemd-soft-reboot.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
does not reset them.</para>
|
||||
|
||||
<para>If dm-verity is built as a kernel module, unloading and reloading the module discards the
|
||||
<literal>.dm-verity</literal> keyring, including its seal, and creates a new, empty one. If
|
||||
<varname>dm_verity.keyring_unsealed=1</varname> is set, the new keyring may then be provisioned with
|
||||
arbitrary keys again. <command>systemd-keyring-setup</command> logs a warning if a keyring belongs to a
|
||||
loadable kernel module. For high-security setups, dm-verity should be built into the kernel.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
<title>See Also</title>
|
||||
|
||||
<para><simplelist type="inline">
|
||||
<member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>systemd-veritysetup@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>systemd-dissect</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>systemd.system-credentials</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>os-release</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
|
||||
<member><citerefentry project='man-pages'><refentrytitle>keyctl</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
||||
</simplelist></para>
|
||||
</refsect1>
|
||||
|
||||
</refentry>
|
||||
@@ -119,6 +119,35 @@
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>keyring-setup.dm-verity.<replaceable>NAME</replaceable></varname></term>
|
||||
<term><varname>keyring-setup.fs-verity.<replaceable>NAME</replaceable></varname></term>
|
||||
<term><varname>keyring-setup.bpf.<replaceable>NAME</replaceable></varname></term>
|
||||
<listitem>
|
||||
<para>A PEM-encoded X.509 certificate to enroll into the <literal>.dm-verity</literal>,
|
||||
<literal>.fs-verity</literal> or <literal>.bpf</literal> kernel keyring, respectively (e.g.
|
||||
<varname>keyring-setup.dm-verity.vendor</varname>). Read by
|
||||
<citerefentry><refentrytitle>systemd-keyring-setup.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
which writes the certificate to <filename>/run/voa/</filename> before enrolling it, see there for
|
||||
details. <replaceable>NAME</replaceable> may consist of lowercase letters, digits, <literal>.</literal>,
|
||||
<literal>_</literal> and <literal>-</literal>.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v263"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>keyring-setup.os</varname></term>
|
||||
<listitem>
|
||||
<para>A whitespace-separated list of OS identifiers (e.g. <literal>debian:13 debian</literal>) under
|
||||
which certificates are looked up in the VOA hierarchy, instead of the ones derived from
|
||||
<citerefentry><refentrytitle>os-release</refentrytitle><manvolnum>5</manvolnum></citerefentry>. Read by
|
||||
<citerefentry><refentrytitle>systemd-keyring-setup.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v263"/>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>login.issue</varname></term>
|
||||
<listitem>
|
||||
|
||||
@@ -2419,6 +2419,7 @@ subdir('src/integritysetup')
|
||||
subdir('src/journal')
|
||||
subdir('src/journal-remote')
|
||||
subdir('src/kernel-install')
|
||||
subdir('src/keyring-setup')
|
||||
subdir('src/keyutil')
|
||||
subdir('src/locale')
|
||||
subdir('src/login')
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBnDCCAUOgAwIBAgIURIGL6fLWIcO+/Ako2/UTNpYX3LUwCgYIKoZIzj0EAwIw
|
||||
GzEZMBcGA1UEAwwQa2V5cmluZy1zZXR1cC1jYTAgFw0yNjA5MDExOTI0MzZaGA8y
|
||||
MTI2MDgwODE5MjQzNlowGzEZMBcGA1UEAwwQa2V5cmluZy1zZXR1cC1jYTBZMBMG
|
||||
ByqGSM49AgEGCCqGSM49AwEHA0IABIE9RbLZ1pLUETv15tD8Pu8M7VujIPsLyfbI
|
||||
gnnnMt8DuCWBcYpskR7UaXz3KGw8mrHSCXYHcjNr1VH1jKX1u9ijYzBhMB0GA1Ud
|
||||
DgQWBBSJJP2p6+SQKwQUKcskhuxFrHPYIDAfBgNVHSMEGDAWgBSJJP2p6+SQKwQU
|
||||
KcskhuxFrHPYIDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwICBDAKBggq
|
||||
hkjOPQQDAgNHADBEAiBbfas+St1VlhTqYskeDmT3ysCEP/Mbnmq9ailCP+7C1wIg
|
||||
dU2k8OMygpbLZBJaBWHvkKTXHrvGjYHslqtu7hO4W0A=
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,5 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg3BJkI9JHC9ZZF2dO
|
||||
C3y3uBEpZrLp6pPnYniJ1KoJx4ehRANCAASBPUWy2daS1BE79ebQ/D7vDO1boyD7
|
||||
C8n2yIJ55zLfA7glgXGKbJEe1Gl89yhsPJqx0gl2B3Iza9VR9Yyl9bvY
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
"""Mint the extension-less test certificates embedded in TEST-98-KEYRING-SETUP.unsealed.sh.
|
||||
|
||||
The subtests for the serial-number fallback of certificate_description_suffix() need certificates without
|
||||
a subjectKeyIdentifier extension. OpenSSL's x509 tool adds one to every certificate it signs, no
|
||||
matter what, so they cannot be produced with it. python-cryptography only adds what is asked for.
|
||||
|
||||
The issuing CA pair next to this script was created with
|
||||
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes \
|
||||
-subj /CN=keyring-setup-ca -days 36500 \
|
||||
-keyout keyring-setup-test-ca.key -out keyring-setup-test-ca.crt
|
||||
and is installed into the trust-anchor hierarchy of the test images by mkosi.finalize. After
|
||||
rotating the pair, rerun this script and paste the output over the embedded blobs.
|
||||
"""
|
||||
|
||||
import pathlib
|
||||
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.primitives import hashes, serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
from cryptography.x509.oid import NameOID
|
||||
|
||||
here = pathlib.Path(__file__).parent
|
||||
ca_cert = x509.load_pem_x509_certificate((here / 'keyring-setup-test-ca.crt').read_bytes())
|
||||
ca_key = serialization.load_pem_private_key((here / 'keyring-setup-test-ca.key').read_bytes(), password=None)
|
||||
|
||||
for cn, serial in (('noskid', 0x8112131415161718), ('noskid2', 0x11121314)):
|
||||
key = ec.generate_private_key(ec.SECP256R1())
|
||||
cert = (
|
||||
x509.CertificateBuilder()
|
||||
.subject_name(x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, cn)]))
|
||||
.issuer_name(ca_cert.subject)
|
||||
.public_key(key.public_key())
|
||||
.serial_number(serial)
|
||||
.not_valid_before(ca_cert.not_valid_before_utc)
|
||||
.not_valid_after(ca_cert.not_valid_after_utc)
|
||||
.sign(ca_key, hashes.SHA256())
|
||||
)
|
||||
print(f'# {cn}, serial {serial:#x}')
|
||||
print(cert.public_bytes(serialization.Encoding.PEM).decode(), end='')
|
||||
@@ -16,3 +16,14 @@ fi
|
||||
|
||||
# Used to sign artifacts verified by kernel platform keyring
|
||||
cp "$SRCDIR/mkosi/mkosi.crt" "$SRCDIR/mkosi/mkosi.key" "$BUILDROOT/usr/share/"
|
||||
|
||||
# The same certificate in the VOA hierarchy, for systemd-keyring-setup to enroll into the .dm-verity and
|
||||
# .fs-verity keyrings
|
||||
# shellcheck source=/dev/null
|
||||
id=$(. "$BUILDROOT/usr/lib/os-release" && echo "$ID")
|
||||
for context in dm-verity fs-verity; do
|
||||
install -D -m 0644 "$SRCDIR/mkosi/mkosi.crt" "$BUILDROOT/usr/share/voa/$id/kernel-keyring/$context/x509/mkosi-certificate.pem"
|
||||
done
|
||||
# A CA certificate among the trust anchors, so that those get enrolled as well
|
||||
install -D -m 0644 "$SRCDIR/mkosi/keyring-setup-test-ca.crt" \
|
||||
"$BUILDROOT/usr/share/voa/$id/trust-anchor-kernel-keyring/dm-verity/x509/ca-certificate.pem"
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
set -e
|
||||
|
||||
# The certificate mkosi installs for userspace verification, mirrored into the VOA hierarchy so that
|
||||
# systemd-keyring-setup enrolls it into the .dm-verity and .fs-verity keyrings while the initrd still can.
|
||||
if [ -f "$BUILDROOT/usr/lib/verity.d/mkosi.crt" ]; then
|
||||
# shellcheck source=/dev/null
|
||||
id=$(. "$BUILDROOT/usr/lib/os-release" && echo "$ID")
|
||||
for context in dm-verity fs-verity; do
|
||||
install -D -m 0644 "$BUILDROOT/usr/lib/verity.d/mkosi.crt" "$BUILDROOT/usr/share/voa/$id/kernel-keyring/$context/x509/mkosi-certificate.pem"
|
||||
done
|
||||
install -D -m 0644 "$SRCDIR/mkosi/keyring-setup-test-ca.crt" \
|
||||
"$BUILDROOT/usr/share/voa/$id/trust-anchor-kernel-keyring/dm-verity/x509/ca-certificate.pem"
|
||||
fi
|
||||
@@ -37,4 +37,5 @@ disable systemd-boot-check-no-failures.service
|
||||
disable systemd-journal-gatewayd.*
|
||||
disable systemd-journal-remote.*
|
||||
disable systemd-journal-upload.*
|
||||
disable systemd-keyring-setup.service
|
||||
disable systemd-time-wait-sync.service
|
||||
@@ -44,3 +44,8 @@ typedef int32_t key_serial_t;
|
||||
#else
|
||||
static_assert(KEY_OTH_ALL == 0x0000003f, "");
|
||||
#endif
|
||||
|
||||
/* TODO: drop once the headers we require carry it */
|
||||
#ifndef KEY_SPEC_BPF_KEYRING
|
||||
# define KEY_SPEC_BPF_KEYRING -9 /* - key ID for the .bpf keyring */
|
||||
#endif
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,14 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
if not have_rust
|
||||
subdir_done()
|
||||
endif
|
||||
|
||||
executables += [
|
||||
rust_executable_template + {
|
||||
'name' : 'systemd-keyring-setup',
|
||||
'sources' : files('keyring-setup.rs'),
|
||||
'install_dir' : libexecdir,
|
||||
'conditions' : ['HAVE_OPENSSL'],
|
||||
},
|
||||
]
|
||||
@@ -73,6 +73,7 @@
|
||||
#include "process-util.h"
|
||||
#include "recurse-dir.h"
|
||||
#include "rm-rf.h"
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
#include "terminal-util.h"
|
||||
#include "tests.h"
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBkzCCATmgAwIBAgIUB1iKsODqTZ1IYTA+/glv7DzSkPgwCgYIKoZIzj0EAwIw
|
||||
HjEcMBoGA1UEAwwTa2V5cmluZy1zZXR1cC1leHRyYTAgFw0yNjA4MjgxMjM2MDVa
|
||||
GA8yMTI2MDgwNDEyMzYwNVowHjEcMBoGA1UEAwwTa2V5cmluZy1zZXR1cC1leHRy
|
||||
YTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOOeWwYTrf2uIUeUBTSvorTIYhwI
|
||||
zfn12j9HHwaQ8fTC+ZezLp6LIQoTZRk6dPyzZ9mYOcNVdvMdWq+c2oEUq32jUzBR
|
||||
MB0GA1UdDgQWBBSY71cv1pViqLGu0C8Kn4+Uyg0KQDAfBgNVHSMEGDAWgBSY71cv
|
||||
1pViqLGu0C8Kn4+Uyg0KQDAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMCA0gA
|
||||
MEUCIQCNDKa0r7Omk8sUfVCtxhd1KADE0jluRr0N81//h/5a5AIgCCJRcwS72mkS
|
||||
nVEUTbIWQfDVNIJ52tSPbu/GlgENhb4=
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,36 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
# Without dm_verity.keyring_unsealed=1 the kernel seals .dm-verity empty and the
|
||||
# service must leave it alone. .fs-verity is provisioned and sealed by the service.
|
||||
# Both variants boot through an initrd, where the service runs first.
|
||||
integration_tests += [
|
||||
integration_test_template + {
|
||||
'name' : fs.name(meson.current_source_dir()),
|
||||
'vm' : true,
|
||||
'firmware' : 'linux',
|
||||
'configuration' : integration_test_template['configuration'] + {
|
||||
'env' : {'TEST_MATCH_SUBTEST' : '[.](sealed|fs-verity)[.]sh$'},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
# With the flag the service provisions .dm-verity from the VOA hierarchy at
|
||||
# boot, and the kernel verifies the signed test image against it.
|
||||
integration_tests += [
|
||||
integration_test_template + {
|
||||
'name' : fs.name(meson.current_source_dir()) + '-unsealed',
|
||||
'vm' : true,
|
||||
'firmware' : 'linux',
|
||||
'credentials' : integration_test_template['credentials'] + [
|
||||
files('keyring-setup.dm-verity.extra'),
|
||||
],
|
||||
'cmdline' : integration_test_template['cmdline'] + [
|
||||
'dm_verity.keyring_unsealed=1',
|
||||
'dm_verity.require_signatures=1',
|
||||
],
|
||||
'configuration' : integration_test_template['configuration'] + {
|
||||
'command' : '/usr/lib/systemd/tests/testdata/units/' + fs.name(meson.current_source_dir()) + '.sh',
|
||||
'env' : {'TEST_MATCH_SUBTEST' : 'unsealed'},
|
||||
},
|
||||
},
|
||||
]
|
||||
@@ -115,6 +115,7 @@ foreach dirname : [
|
||||
'TEST-95-DBUS-RECONNECT',
|
||||
'TEST-96-PTYBROKER',
|
||||
'TEST-97-VMSPAWN-MEMORY',
|
||||
'TEST-98-KEYRING-SETUP',
|
||||
]
|
||||
subdir(dirname)
|
||||
endforeach
|
||||
|
||||
@@ -57,20 +57,50 @@ if [[ "$val" != "Y" && "$val" != "1" ]]; then
|
||||
exit 77
|
||||
fi
|
||||
|
||||
# Provision the .dm-verity keyring. Empty description lets the kernel derive
|
||||
# one from the X.509 subject so machine_supports_verity_keyring finds the CN.
|
||||
keyid=$(openssl x509 -in /usr/share/mkosi.crt -outform DER |
|
||||
keyctl padd asymmetric '' %:.dm-verity 2>/dev/null) || keyid=""
|
||||
if [[ -z "$keyid" ]]; then
|
||||
echo ".dm-verity keyring not provisionable (kernel < v7.0?), skipping"
|
||||
if ! grep -E ' keyring +\.dm-verity: ' /proc/keys >/dev/null; then
|
||||
echo ".dm-verity keyring not available (kernel < v7.0?), skipping"
|
||||
exit 77
|
||||
fi
|
||||
if ! keyctl restrict_keyring %:.dm-verity; then
|
||||
keyctl unlink "$keyid" %:.dm-verity 2>/dev/null || true
|
||||
echo "ERROR: keyctl restrict_keyring failed" >&2
|
||||
|
||||
# systemd-keyring-setup.service provisions the keyring at boot and seals it
|
||||
# (permission mask 08070000), after which only certificates signed by an
|
||||
# enrolled key can be added and the certificate must already be in place.
|
||||
# Provision by hand only otherwise.
|
||||
if grep -E '^[0-9a-f]+ [^ ]+ +[0-9]+ +[^ ]+ +08070000 +[0-9]+ +[0-9]+ +keyring +\.dm-verity: [0-9]+$' /proc/keys >/dev/null; then
|
||||
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -subj /CN=stranger -days 1 \
|
||||
-keyout /dev/null -out /tmp/stranger.pem
|
||||
openssl x509 -in /tmp/stranger.pem -outform DER -out /tmp/stranger.der
|
||||
if keyctl padd asymmetric '' %:.dm-verity </tmp/stranger.der 2>/dev/null; then
|
||||
echo "ERROR: sealed .dm-verity keyring accepted a stranger" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo ".dm-verity keyring already provisioned and sealed at boot"
|
||||
elif grep -E ' +08070000 +[0-9]+ +[0-9]+ +keyring +\.dm-verity: empty$' /proc/keys >/dev/null; then
|
||||
echo "ERROR: .dm-verity keyring sealed empty, systemd-keyring-setup enrolled nothing" >&2
|
||||
exit 1
|
||||
else
|
||||
# With the keyring left open by the kernel and systemd-keyring-setup enabled,
|
||||
# getting here means the service did not provision it
|
||||
if systemctl -q is-enabled systemd-keyring-setup.service 2>/dev/null &&
|
||||
[[ "$(cat /sys/module/dm_verity/parameters/keyring_unsealed 2>/dev/null)" == "Y" ]]; then
|
||||
echo "ERROR: .dm-verity keyring left open, systemd-keyring-setup did not provision it" >&2
|
||||
exit 1
|
||||
fi
|
||||
# Empty description lets the kernel derive one from the X.509 subject so
|
||||
# machine_supports_verity_keyring finds the CN.
|
||||
openssl x509 -in /usr/share/mkosi.crt -outform DER -out /tmp/mkosi.der
|
||||
keyid=$(keyctl padd asymmetric '' %:.dm-verity </tmp/mkosi.der 2>/dev/null) || keyid=""
|
||||
if [[ -z "$keyid" ]]; then
|
||||
echo ".dm-verity keyring sealed by the kernel at boot (no dm_verity.keyring_unsealed=1), skipping"
|
||||
exit 77
|
||||
fi
|
||||
if ! keyctl restrict_keyring %:.dm-verity; then
|
||||
keyctl unlink "$keyid" %:.dm-verity 2>/dev/null || true
|
||||
echo "ERROR: keyctl restrict_keyring failed" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Provisioned .dm-verity keyring with mkosi.crt"
|
||||
fi
|
||||
echo "Provisioned .dm-verity keyring with mkosi.crt"
|
||||
|
||||
at_exit() {
|
||||
set +e
|
||||
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
#
|
||||
# Note that unlike the .dm-verity and .bpf keyrings the kernel never seals
|
||||
# .fs-verity. Only systemd-keyring-setup does. It enrolled the certificate from
|
||||
# the VOA hierarchy at boot and sealed the keyring like the others, so only
|
||||
# certificates chaining to it can be added later, also via a credential.
|
||||
set -eux
|
||||
set -o pipefail
|
||||
|
||||
KEYRING_SETUP=/usr/lib/systemd/systemd-keyring-setup
|
||||
|
||||
if [[ ! -x "$KEYRING_SETUP" ]]; then
|
||||
echo "systemd-keyring-setup is not built (no Rust support), skipping"
|
||||
exit 77
|
||||
fi
|
||||
|
||||
# Everything up to the keyring gate below needs no kernel keyring support
|
||||
[[ "$(systemctl show -P ActiveState systemd-keyring-setup.service)" == "active" ]]
|
||||
[[ "$(systemctl show -P Result systemd-keyring-setup.service)" == "success" ]]
|
||||
[[ "$(systemctl show -P ExecMainStatus systemd-keyring-setup.service)" == "0" ]]
|
||||
|
||||
# Rejected credential names are ignored and do not fail the run
|
||||
systemd-run --pipe --wait \
|
||||
--property LoadCredential=keyring-setup.fs-verityX.foo:/usr/share/mkosi.crt \
|
||||
--property LoadCredential=keyring-setup.fs-verity.Bad:/usr/share/mkosi.crt \
|
||||
--property LoadCredential=keyring-setup.bogus.foo:/usr/share/mkosi.crt \
|
||||
"$KEYRING_SETUP" .fs-verity 2>&1 | tee /tmp/keyring-setup-creds.log
|
||||
grep -F "Ignoring unrecognized credential 'keyring-setup.fs-verityX.foo'" /tmp/keyring-setup-creds.log >/dev/null
|
||||
grep -F "Ignoring unrecognized credential 'keyring-setup.bogus.foo'" /tmp/keyring-setup-creds.log >/dev/null
|
||||
grep -F "Ignoring credential 'keyring-setup.fs-verity.Bad'" /tmp/keyring-setup-creds.log >/dev/null
|
||||
(! find /run/voa \( -name 'foo-certificate.pem' -o -name 'Bad-certificate.pem' \) | grep . >/dev/null)
|
||||
|
||||
# keyring-setup.os overrides where credentials are placed, an invalid identifier is bad input
|
||||
systemd-run --pipe --wait \
|
||||
--property SetCredential=keyring-setup.os:testos \
|
||||
--property LoadCredential=keyring-setup.fs-verity.osname:/usr/share/mkosi.crt \
|
||||
"$KEYRING_SETUP" .fs-verity
|
||||
ls /run/voa/testos/kernel-keyring/fs-verity/x509/osname-certificate.pem
|
||||
rc=0
|
||||
systemd-run --pipe --wait --property SetCredential=keyring-setup.os:INVALID "$KEYRING_SETUP" .fs-verity || rc=$?
|
||||
[[ "$rc" -eq 65 ]]
|
||||
|
||||
# Only the certificate of a credential is placed, a private key that came along stays out of the
|
||||
# world-readable hierarchy. A credential without a certificate is bad input.
|
||||
cat /usr/share/mkosi.key /usr/share/mkosi.crt >/tmp/withkey.pem
|
||||
systemd-run --pipe --wait \
|
||||
--property SetCredential=keyring-setup.os:testos \
|
||||
--property LoadCredential=keyring-setup.fs-verity.withkey:/tmp/withkey.pem \
|
||||
"$KEYRING_SETUP" .fs-verity
|
||||
(! grep -F "PRIVATE KEY" /run/voa/testos/kernel-keyring/fs-verity/x509/withkey-certificate.pem >/dev/null)
|
||||
cmp <(openssl x509 -in /run/voa/testos/kernel-keyring/fs-verity/x509/withkey-certificate.pem -outform DER) \
|
||||
<(openssl x509 -in /usr/share/mkosi.crt -outform DER)
|
||||
rm -f /tmp/withkey.pem
|
||||
rc=0
|
||||
systemd-run --pipe --wait \
|
||||
--property SetCredential=keyring-setup.os:testos \
|
||||
--property LoadCredential=keyring-setup.fs-verity.nocert:/usr/share/mkosi.key \
|
||||
"$KEYRING_SETUP" .fs-verity || rc=$?
|
||||
[[ "$rc" -eq 65 ]]
|
||||
[[ ! -e /run/voa/testos/kernel-keyring/fs-verity/x509/nocert-certificate.pem ]]
|
||||
|
||||
# Multiple identifiers feed the lookup, not only the placement of credentials. A distinct
|
||||
# certificate, the byte-identical dedup would hide a copy of an already collected one.
|
||||
mkdir -p /usr/share/voa/zweitos/kernel-keyring/fs-verity/x509
|
||||
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -subj /CN=zwei -days 1 \
|
||||
-keyout /dev/null -out /usr/share/voa/zweitos/kernel-keyring/fs-verity/x509/zwei-certificate.pem
|
||||
systemd-run --pipe --wait \
|
||||
--property SetCredential="keyring-setup.os:zweitos testos" \
|
||||
"$KEYRING_SETUP" --dry-run --no-pager --json=short .fs-verity | tee /tmp/keyring-setup-multi.json
|
||||
jq -e '.[0].certificates | any(contains("/zweitos/")) and any(contains("/testos/"))' /tmp/keyring-setup-multi.json
|
||||
rm -rf /usr/share/voa/zweitos
|
||||
|
||||
# An empty identifier list and one longer than 64 entries are bad input
|
||||
rc=0
|
||||
systemd-run --pipe --wait --property SetCredential=keyring-setup.os: \
|
||||
"$KEYRING_SETUP" .fs-verity 2>&1 | tee /tmp/keyring-setup-os-empty.log || rc=$?
|
||||
[[ "$rc" -eq 65 ]]
|
||||
grep -F "Credential keyring-setup.os is empty, ignoring." /tmp/keyring-setup-os-empty.log >/dev/null
|
||||
rc=0
|
||||
systemd-run --pipe --wait --property SetCredential="keyring-setup.os:$(seq -f 'os%.0f' 65 | tr '\n' ' ')" \
|
||||
"$KEYRING_SETUP" .fs-verity 2>&1 | tee /tmp/keyring-setup-os-many.log || rc=$?
|
||||
[[ "$rc" -eq 65 ]]
|
||||
grep -F "lists more than 64 OS identifiers, ignoring it." /tmp/keyring-setup-os-many.log >/dev/null
|
||||
|
||||
# An os-release ID= outside of the VOA charset disables the lookup, reported once, the run still fails
|
||||
printf 'ID=Debian\n' >/tmp/bad-os-release
|
||||
rc=0
|
||||
systemd-run --pipe --wait --property BindReadOnlyPaths=/tmp/bad-os-release:/etc/os-release \
|
||||
"$KEYRING_SETUP" .fs-verity 2>&1 | tee /tmp/keyring-setup-badid.log || rc=$?
|
||||
[[ "$rc" -eq 1 ]]
|
||||
grep -F "Failed to determine the OS identifier from os-release, enrolling nothing" /tmp/keyring-setup-badid.log >/dev/null
|
||||
(! grep -F "Failed to enumerate certificates" /tmp/keyring-setup-badid.log >/dev/null)
|
||||
|
||||
if ! grep -E ' keyring +\.fs-verity: ' /proc/keys >/dev/null; then
|
||||
echo ".fs-verity keyring not available (CONFIG_FS_VERITY_BUILTIN_SIGNATURES?), skipping"
|
||||
exit 77
|
||||
fi
|
||||
|
||||
# Sealed, holding the mkosi certificate
|
||||
grep -E '^[0-9a-f]+ [A-Za-z-]{7} +[0-9]+ +perm +08070000 +0 +0 +keyring +\.fs-verity: 1$' /proc/keys
|
||||
CN="$(openssl x509 -noout -subject -in /usr/share/mkosi.crt | sed 's/^.*CN *= *//')"
|
||||
grep -E "^[0-9a-f]+ [A-Za-z-]{7} +[0-9]+ +perm +39010000 +0 +0 +asymmetri .*$CN" /proc/keys
|
||||
. /etc/os-release
|
||||
# Not -u: messages of the initrd run carry no _SYSTEMD_UNIT=, see the unsealed subtest
|
||||
journalctl --merge -o cat -t systemd-keyring-setup |
|
||||
grep -F "Enrolled '/usr/share/voa/$ID/kernel-keyring/fs-verity/x509/mkosi-certificate.pem' into keyring .fs-verity" >/dev/null
|
||||
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -subj /CN=stranger -days 1 \
|
||||
-keyout /dev/null -out /tmp/stranger.pem
|
||||
openssl x509 -in /tmp/stranger.pem -outform DER -out /tmp/stranger.der
|
||||
(! keyctl padd asymmetric '' %:.fs-verity </tmp/stranger.der)
|
||||
keyctl list %:.fs-verity | grep -F "$CN"
|
||||
|
||||
# A certificate handed over later is placed into the hierarchy, and one that is
|
||||
# enrolled already is not enrolled twice
|
||||
systemd-run --pipe --wait \
|
||||
--property LoadCredential=keyring-setup.fs-verity.late:/usr/share/mkosi.crt \
|
||||
"$KEYRING_SETUP" .fs-verity 2>&1 | tee /tmp/keyring-setup-late.log
|
||||
(! grep -F "Enrolled '" /tmp/keyring-setup-late.log >/dev/null)
|
||||
ls /run/voa/*/kernel-keyring/fs-verity/x509/late-certificate.pem
|
||||
grep -E ' +08070000 +0 +0 +keyring +\.fs-verity: 1$' /proc/keys
|
||||
Executable
+97
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
#
|
||||
# Booted without dm_verity.keyring_unsealed=1: the kernel sealed .dm-verity
|
||||
# empty, systemd-keyring-setup finds the certificates but can enroll nothing,
|
||||
# and must leave the keyring alone.
|
||||
set -eux
|
||||
set -o pipefail
|
||||
|
||||
KEYRING_SETUP=/usr/lib/systemd/systemd-keyring-setup
|
||||
|
||||
if [[ ! -x "$KEYRING_SETUP" ]]; then
|
||||
echo "systemd-keyring-setup is not built (no Rust support), skipping"
|
||||
exit 77
|
||||
fi
|
||||
|
||||
# Everything up to the keyring gate below needs no kernel keyring support
|
||||
[[ "$(systemctl show -P ActiveState systemd-keyring-setup.service)" == "active" ]]
|
||||
[[ "$(systemctl show -P Result systemd-keyring-setup.service)" == "success" ]]
|
||||
[[ "$(systemctl show -P ExecMainStatus systemd-keyring-setup.service)" == "0" ]]
|
||||
|
||||
# An unknown keyring, a name without its leading dot, and --json= without --dry-run are rejected
|
||||
(! "$KEYRING_SETUP" --dry-run .bogus)
|
||||
(! "$KEYRING_SETUP" --dry-run dm-verity)
|
||||
(! "$KEYRING_SETUP" --json=short .dm-verity)
|
||||
|
||||
# The usage check keeps a certificate that cannot sign code out and reports it as bad input, while
|
||||
# one permitting any usage and a CA certificate placed among the trust anchors are accepted
|
||||
. /etc/os-release
|
||||
mkdir -p "/etc/voa/$ID/kernel-keyring/dm-verity/x509" "/etc/voa/$ID/trust-anchor-kernel-keyring/dm-verity/x509"
|
||||
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -subj /CN=tls -days 1 \
|
||||
-addext extendedKeyUsage=serverAuth \
|
||||
-keyout /dev/null -out "/etc/voa/$ID/kernel-keyring/dm-verity/x509/tls-certificate.pem"
|
||||
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -subj /CN=anyeku -days 1 \
|
||||
-addext extendedKeyUsage=critical,anyExtendedKeyUsage \
|
||||
-keyout /dev/null -out "/etc/voa/$ID/kernel-keyring/dm-verity/x509/anyeku-certificate.pem"
|
||||
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -subj /CN=ca -days 1 \
|
||||
-addext basicConstraints=critical,CA:TRUE -addext keyUsage=critical,keyCertSign \
|
||||
-keyout /dev/null -out "/etc/voa/$ID/trust-anchor-kernel-keyring/dm-verity/x509/ca-certificate.pem"
|
||||
:>"/etc/voa/$ID/kernel-keyring/dm-verity/x509/empty-certificate.pem"
|
||||
truncate -s 2M "/etc/voa/$ID/kernel-keyring/dm-verity/x509/large-certificate.pem"
|
||||
rc=0
|
||||
"$KEYRING_SETUP" --dry-run --no-pager --json=short .dm-verity >/tmp/keyring-setup.json || rc=$?
|
||||
[[ "$rc" -eq 65 ]]
|
||||
jq -e '.[0].certificates | any(endswith("/mkosi-certificate.pem")) and any(endswith("/anyeku-certificate.pem"))
|
||||
and any(endswith("/trust-anchor-kernel-keyring/dm-verity/x509/ca-certificate.pem"))
|
||||
and (any(endswith("/tls-certificate.pem")) | not)
|
||||
and (any(endswith("/empty-certificate.pem")) | not)
|
||||
and (any(endswith("/large-certificate.pem")) | not)' /tmp/keyring-setup.json
|
||||
|
||||
# The .bpf spec looks up the bpf role
|
||||
mkdir -p "/etc/voa/$ID/kernel-keyring/bpf/x509"
|
||||
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -subj /CN=bpfrole -days 1 \
|
||||
-keyout /dev/null -out "/etc/voa/$ID/kernel-keyring/bpf/x509/bpfrole-certificate.pem"
|
||||
"$KEYRING_SETUP" --dry-run --no-pager --json=short .bpf >/tmp/keyring-setup-bpf.json
|
||||
jq -e '.[0].certificates | any(endswith("/kernel-keyring/bpf/x509/bpfrole-certificate.pem"))' /tmp/keyring-setup-bpf.json
|
||||
# .fs-verity has no keyring_unsealed parameter, its kernel_unsealed state is null
|
||||
"$KEYRING_SETUP" --dry-run --no-pager --json=short .fs-verity | jq -e '.[0].kernel_unsealed == null'
|
||||
rm -rf "/etc/voa/$ID" /tmp/keyring-setup.json /tmp/keyring-setup-bpf.json
|
||||
|
||||
if ! grep -E ' keyring +\.dm-verity: ' /proc/keys >/dev/null; then
|
||||
if ! modprobe dm_verity 2>/dev/null || ! grep -E ' keyring +\.dm-verity: ' /proc/keys >/dev/null; then
|
||||
echo ".dm-verity keyring not available (kernel < v7.0?), skipping"
|
||||
exit 77
|
||||
fi
|
||||
# The service loads dm_verity itself, the keyring must have existed when it ran
|
||||
echo "ERROR: dm_verity was not loaded when systemd-keyring-setup.service ran" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
[[ "$(cat /sys/module/dm_verity/parameters/keyring_unsealed)" == "N" ]]
|
||||
|
||||
# The initial permission mask is untouched and the keyring stays empty
|
||||
grep -E '^[0-9a-f]+ [A-Za-z-]{7} +[0-9]+ +perm +082f0000 +0 +0 +keyring +\.dm-verity: empty$' /proc/keys
|
||||
|
||||
# The certificates were found, mkosi.crt among them
|
||||
"$KEYRING_SETUP" --dry-run --no-pager --json=short .dm-verity |
|
||||
jq -e '.[0] | .exists == true and .kernel_unsealed == false and .sealed == false and (.certificates | any(endswith("/mkosi-certificate.pem")))'
|
||||
|
||||
# Running again changes nothing
|
||||
"$KEYRING_SETUP" .dm-verity
|
||||
grep -E ' +082f0000 +0 +0 +keyring +\.dm-verity: empty$' /proc/keys
|
||||
|
||||
# Without a key in the .dm-verity keyring the kernel cannot verify the signed
|
||||
# test image, so with systemd's userspace fallback masked the activation must
|
||||
# fail, and succeed once the fallback is available again.
|
||||
mkdir -p /etc/verity.d
|
||||
ln -sf /dev/null /etc/verity.d/mkosi.crt
|
||||
(! systemd-run --pipe --wait \
|
||||
--property RootImage=/usr/share/minimal_0.raw \
|
||||
--property RootImagePolicy=root=signed \
|
||||
bash --version >/dev/null)
|
||||
rm -f /etc/verity.d/mkosi.crt
|
||||
systemd-run --pipe --wait \
|
||||
--property RootImage=/usr/share/minimal_0.raw \
|
||||
--property RootImagePolicy=root=signed \
|
||||
bash --version >/dev/null
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
set -eux
|
||||
set -o pipefail
|
||||
|
||||
# shellcheck source=test/units/test-control.sh
|
||||
. "$(dirname "$0")"/test-control.sh
|
||||
|
||||
run_subtests_and_exit
|
||||
Executable
+207
@@ -0,0 +1,207 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
#
|
||||
# Booted with dm_verity.keyring_unsealed=1: systemd-keyring-setup enrolled the
|
||||
# certificates from the VOA hierarchy at boot, sealed the keyring, and the
|
||||
# kernel verifies the signed test image against it.
|
||||
set -eux
|
||||
set -o pipefail
|
||||
|
||||
KEYRING_SETUP=/usr/lib/systemd/systemd-keyring-setup
|
||||
|
||||
if [[ ! -x "$KEYRING_SETUP" ]]; then
|
||||
echo "systemd-keyring-setup is not built (no Rust support), skipping"
|
||||
exit 77
|
||||
fi
|
||||
|
||||
if ! grep -E ' keyring +\.dm-verity: ' /proc/keys >/dev/null; then
|
||||
echo ".dm-verity keyring not available (kernel < v7.0?), skipping"
|
||||
exit 77
|
||||
fi
|
||||
|
||||
[[ "$(systemctl show -P ActiveState systemd-keyring-setup.service)" == "active" ]]
|
||||
[[ "$(systemctl show -P Result systemd-keyring-setup.service)" == "success" ]]
|
||||
[[ "$(systemctl show -P ExecMainStatus systemd-keyring-setup.service)" == "0" ]]
|
||||
[[ "$(cat /sys/module/dm_verity/parameters/keyring_unsealed)" == "Y" ]]
|
||||
|
||||
# Sealed, holding the mkosi certificate under its kernel-derived description, the one handed
|
||||
# over as a credential and the trust anchor
|
||||
grep -E '^[0-9a-f]+ [A-Za-z-]{7} +[0-9]+ +perm +08070000 +0 +0 +keyring +\.dm-verity: 3$' /proc/keys
|
||||
CN="$(openssl x509 -noout -subject -in /usr/share/mkosi.crt | sed 's/^.*CN *= *//')"
|
||||
grep -E "^[0-9a-f]+ [A-Za-z-]{7} +[0-9]+ +perm +39010000 +0 +0 +asymmetri .*$CN" /proc/keys
|
||||
grep -E '^[0-9a-f]+ [A-Za-z-]{7} +[0-9]+ +perm +39010000 +0 +0 +asymmetri .*keyring-setup-extra' /proc/keys
|
||||
grep -E '^[0-9a-f]+ [A-Za-z-]{7} +[0-9]+ +perm +39010000 +0 +0 +asymmetri .*keyring-setup-ca' /proc/keys
|
||||
# /proc/keys does not say which keyring a key is linked into, check that they are members of .dm-verity
|
||||
keyctl list %:.dm-verity | grep -F "$CN"
|
||||
keyctl list %:.dm-verity | grep -F keyring-setup-extra
|
||||
keyctl list %:.dm-verity | grep -F keyring-setup-ca
|
||||
"$KEYRING_SETUP" --dry-run --no-pager --json=short .dm-verity | jq -e '.[0].kernel_unsealed == true'
|
||||
|
||||
# The service enrolled them from the VOA hierarchy in the initrd, before the switch to the host
|
||||
. /etc/os-release
|
||||
# Not -u: the initrd run has exited by the time journald reads its messages, so they carry no _SYSTEMD_UNIT=
|
||||
journalctl --merge -o json -t systemd-keyring-setup >/tmp/keyring-setup.journal.json
|
||||
enrolled="$(jq -rn --arg m "Enrolled '/usr/share/voa/$ID/kernel-keyring/dm-verity/x509/mkosi-certificate.pem' into keyring .dm-verity" \
|
||||
'first(inputs | select(.MESSAGE | startswith($m)) | .__MONOTONIC_TIMESTAMP)' /tmp/keyring-setup.journal.json)"
|
||||
journalctl --merge -o json _PID=1 >/tmp/pid1.journal.json
|
||||
switched="$(jq -rn 'first(inputs | select(.MESSAGE | startswith("Switching root")) | .__MONOTONIC_TIMESTAMP)' /tmp/pid1.journal.json)"
|
||||
rm -f /tmp/keyring-setup.journal.json /tmp/pid1.journal.json
|
||||
[[ -n "$enrolled" && -n "$switched" && "$enrolled" -lt "$switched" ]]
|
||||
journalctl --merge -o cat -t systemd-keyring-setup |
|
||||
grep -E "^Enrolled '/run/voa/[^/]+/kernel-keyring/dm-verity/x509/extra-certificate.pem' into keyring .dm-verity" >/dev/null
|
||||
journalctl --merge -o cat -t systemd-keyring-setup |
|
||||
grep -F "Enrolled '/usr/share/voa/$ID/trust-anchor-kernel-keyring/dm-verity/x509/ca-certificate.pem' into keyring .dm-verity" >/dev/null
|
||||
|
||||
# From now on, only certificates signed by an enrolled key can be added, and neither the mask nor the
|
||||
# restriction can be changed, not even by root
|
||||
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -subj /CN=stranger -days 1 \
|
||||
-keyout /dev/null -out /tmp/stranger.pem
|
||||
openssl x509 -in /tmp/stranger.pem -outform DER -out /tmp/stranger.der
|
||||
(! keyctl padd asymmetric '' %:.dm-verity </tmp/stranger.der)
|
||||
(! keyctl restrict_keyring %:.dm-verity)
|
||||
(! keyctl invalidate %:.dm-verity)
|
||||
(! keyctl setperm %:.dm-verity 0x3f3f0000)
|
||||
keyctl list %:.dm-verity
|
||||
openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -subj /CN=chained \
|
||||
-keyout /dev/null -out /tmp/chained.csr
|
||||
openssl x509 -req -in /tmp/chained.csr -CA /usr/share/mkosi.crt -CAkey /usr/share/mkosi.key \
|
||||
-set_serial 1 -days 1 -extfile <(echo authorityKeyIdentifier=keyid,issuer) -out /tmp/chained.pem
|
||||
openssl x509 -in /tmp/chained.pem -outform DER | keyctl padd asymmetric '' %:.dm-verity
|
||||
grep -E ' +08070000 +0 +0 +keyring +\.dm-verity: 4$' /proc/keys
|
||||
|
||||
# A later stage finds the keyring provisioned and exits successfully without enrolling anything twice
|
||||
"$KEYRING_SETUP" .dm-verity 2>&1 | tee /tmp/keyring-setup-again.log
|
||||
(! grep -F "Enrolled '" /tmp/keyring-setup-again.log >/dev/null)
|
||||
grep -E ' +08070000 +0 +0 +keyring +\.dm-verity: 4$' /proc/keys
|
||||
|
||||
# A certificate configured after the fact is refused and reported as bad input unless it is signed
|
||||
# by an enrolled key
|
||||
mkdir -p "/etc/voa/$ID/kernel-keyring/dm-verity/x509"
|
||||
cp /tmp/stranger.pem "/etc/voa/$ID/kernel-keyring/dm-verity/x509/late-certificate.pem"
|
||||
rc=0
|
||||
"$KEYRING_SETUP" .dm-verity || rc=$?
|
||||
[[ "$rc" -eq 65 ]]
|
||||
grep -E ' +08070000 +0 +0 +keyring +\.dm-verity: 4$' /proc/keys
|
||||
openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -subj /CN=late \
|
||||
-keyout /dev/null -out /tmp/late.csr
|
||||
openssl x509 -req -in /tmp/late.csr -CA /usr/share/mkosi.crt -CAkey /usr/share/mkosi.key \
|
||||
-set_serial 2 -days 1 -extfile <(echo authorityKeyIdentifier=keyid,issuer) \
|
||||
-out "/etc/voa/$ID/kernel-keyring/dm-verity/x509/late-certificate.pem"
|
||||
"$KEYRING_SETUP" .dm-verity
|
||||
grep -E ' +08070000 +0 +0 +keyring +\.dm-verity: 5$' /proc/keys
|
||||
grep -E "^[0-9a-f]+ [A-Za-z-]{7} +[0-9]+ +perm +39010000 +0 +0 +asymmetri +late: [0-9a-f]+: " /proc/keys
|
||||
|
||||
# On a colliding subject and key identifier the copy from the highest-priority load path wins
|
||||
openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -subj /CN=dup \
|
||||
-keyout /tmp/dup.key -out /tmp/dup.csr
|
||||
openssl x509 -req -in /tmp/dup.csr -CA /usr/share/mkosi.crt -CAkey /usr/share/mkosi.key \
|
||||
-set_serial 3 -days 1 \
|
||||
-extfile <(printf 'authorityKeyIdentifier=keyid,issuer\nsubjectKeyIdentifier=hash\n') \
|
||||
-out "/usr/share/voa/$ID/kernel-keyring/dm-verity/x509/dup-certificate.pem"
|
||||
openssl x509 -req -in /tmp/dup.csr -CA /usr/share/mkosi.crt -CAkey /usr/share/mkosi.key \
|
||||
-set_serial 4 -days 2 \
|
||||
-extfile <(printf 'authorityKeyIdentifier=keyid,issuer\nsubjectKeyIdentifier=hash\n') \
|
||||
-out "/etc/voa/$ID/kernel-keyring/dm-verity/x509/dup-certificate.pem"
|
||||
"$KEYRING_SETUP" .dm-verity 2>&1 | tee /tmp/keyring-setup-dup.log
|
||||
grep -F "Enrolled '/usr/share/voa/$ID/kernel-keyring/dm-verity/x509/dup-certificate.pem'" /tmp/keyring-setup-dup.log >/dev/null
|
||||
grep -F "Enrolled '/etc/voa/$ID/kernel-keyring/dm-verity/x509/dup-certificate.pem'" /tmp/keyring-setup-dup.log >/dev/null
|
||||
grep -F "'/usr/share/voa/$ID/kernel-keyring/dm-verity/x509/dup-certificate.pem' was superseded by a certificate of higher priority" /tmp/keyring-setup-dup.log >/dev/null
|
||||
grep -E ' +08070000 +0 +0 +keyring +\.dm-verity: 6$' /proc/keys
|
||||
rm -f "/usr/share/voa/$ID/kernel-keyring/dm-verity/x509/dup-certificate.pem"
|
||||
|
||||
# The same certificate in two load paths is enrolled once, neither copy supersedes the other
|
||||
cp "/usr/share/voa/$ID/kernel-keyring/dm-verity/x509/mkosi-certificate.pem" \
|
||||
"/etc/voa/$ID/kernel-keyring/dm-verity/x509/mkosi-certificate.pem"
|
||||
SYSTEMD_LOG_LEVEL=debug "$KEYRING_SETUP" .dm-verity 2>&1 | tee /tmp/keyring-setup-samecert.log
|
||||
grep -F "'/usr/share/voa/$ID/kernel-keyring/dm-verity/x509/mkosi-certificate.pem' is identical to '/etc/voa/$ID/kernel-keyring/dm-verity/x509/mkosi-certificate.pem', skipping." /tmp/keyring-setup-samecert.log >/dev/null
|
||||
(! grep -F "Enrolled '" /tmp/keyring-setup-samecert.log >/dev/null)
|
||||
(! grep -F "was superseded" /tmp/keyring-setup-samecert.log >/dev/null)
|
||||
grep -E ' +08070000 +0 +0 +keyring +\.dm-verity: 6$' /proc/keys
|
||||
rm -f "/etc/voa/$ID/kernel-keyring/dm-verity/x509/mkosi-certificate.pem"
|
||||
|
||||
# Without a subject key identifier the serial number, DER padding included, identifies the key:
|
||||
# CN=noskid, serial 0x8112131415161718, no extensions, signed by the committed keyring-setup-test-ca key,
|
||||
# minted by mkosi/keyring-setup-test-ca.py
|
||||
cat >"/etc/voa/$ID/kernel-keyring/dm-verity/x509/noskid-certificate.pem" <<'CERT'
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBIjCByaADAgECAgkAgRITFBUWFxgwCgYIKoZIzj0EAwIwGzEZMBcGA1UEAwwQ
|
||||
a2V5cmluZy1zZXR1cC1jYTAgFw0yNjA5MDExOTI0MzZaGA8yMTI2MDgwODE5MjQz
|
||||
NlowETEPMA0GA1UEAwwGbm9za2lkMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
|
||||
FRpKLUgmHK8vFB9/N5ZRV54Bwk8uOxU8tt1R1A3OMwgn/ct32PBBNj5HbiS/Wta2
|
||||
qs6KODUBlPVfCEXktGHMYzAKBggqhkjOPQQDAgNIADBFAiEAsd92t7WgIFHe+cp/
|
||||
1gjd7OXQw+wkW+Y33iixvkGLA/QCIBLVHP7dhYYkqkxZSkrcUQF805lE893LDnuF
|
||||
MQVSUlGj
|
||||
-----END CERTIFICATE-----
|
||||
CERT
|
||||
"$KEYRING_SETUP" .dm-verity 2>&1 | tee /tmp/keyring-setup-noskid.log
|
||||
grep -F "Enrolled '/etc/voa/$ID/kernel-keyring/dm-verity/x509/noskid-certificate.pem' into keyring .dm-verity as noskid: 008112131415161718" /tmp/keyring-setup-noskid.log >/dev/null
|
||||
keyctl list %:.dm-verity | grep -F 'noskid: 008112131415161718'
|
||||
grep -E ' +08070000 +0 +0 +keyring +\.dm-verity: 7$' /proc/keys
|
||||
|
||||
# And the second run recognizes it by that identifier and enrolls nothing
|
||||
"$KEYRING_SETUP" .dm-verity 2>&1 | tee /tmp/keyring-setup-noskid2.log
|
||||
(! grep -F "Enrolled '/etc/voa/$ID/kernel-keyring/dm-verity/x509/noskid-certificate.pem'" /tmp/keyring-setup-noskid2.log >/dev/null)
|
||||
grep -E ' +08070000 +0 +0 +keyring +\.dm-verity: 7$' /proc/keys
|
||||
|
||||
# A serial without the top bit set gets no zero pad: CN=noskid2, serial 0x11121314, same CA and script
|
||||
cat >"/etc/voa/$ID/kernel-keyring/dm-verity/x509/noskid2-certificate.pem" <<'CERT'
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBHjCBxaADAgECAgQREhMUMAoGCCqGSM49BAMCMBsxGTAXBgNVBAMMEGtleXJp
|
||||
bmctc2V0dXAtY2EwIBcNMjYwOTAxMTkyNDM2WhgPMjEyNjA4MDgxOTI0MzZaMBIx
|
||||
EDAOBgNVBAMMB25vc2tpZDIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQZeGsy
|
||||
lGeEJ5rQtap+PKYtjPtqc3j84NM++obHFcfMjt+jhTRUUtoZxP7cYvODHLZiJqne
|
||||
y5VXUwcozNcN7E3EMAoGCCqGSM49BAMCA0gAMEUCIQCjnDs9G6zA1fNGMhIRSnHV
|
||||
f0WXbXciDE8wxo/p7owp7wIgGMLuK/KOyRkgKZLNZx9HG38xmgZYb73tcGFeqg2X
|
||||
ihY=
|
||||
-----END CERTIFICATE-----
|
||||
CERT
|
||||
"$KEYRING_SETUP" .dm-verity 2>&1 | tee /tmp/keyring-setup-noskid3.log
|
||||
grep -F "Enrolled '/etc/voa/$ID/kernel-keyring/dm-verity/x509/noskid2-certificate.pem' into keyring .dm-verity as noskid2: 11121314" /tmp/keyring-setup-noskid3.log >/dev/null
|
||||
keyctl list %:.dm-verity | grep -F 'noskid2: 11121314'
|
||||
grep -E ' +08070000 +0 +0 +keyring +\.dm-verity: 8$' /proc/keys
|
||||
|
||||
# Key material writable by others is refused, once the mode is sane it is enrolled
|
||||
openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -subj /CN=loose \
|
||||
-keyout /dev/null -out /tmp/loose.csr
|
||||
openssl x509 -req -in /tmp/loose.csr -CA /usr/share/mkosi.crt -CAkey /usr/share/mkosi.key \
|
||||
-set_serial 5 -days 1 -extfile <(echo authorityKeyIdentifier=keyid,issuer) \
|
||||
-out "/etc/voa/$ID/kernel-keyring/dm-verity/x509/loose-certificate.pem"
|
||||
chmod 0666 "/etc/voa/$ID/kernel-keyring/dm-verity/x509/loose-certificate.pem"
|
||||
"$KEYRING_SETUP" .dm-verity 2>&1 | tee /tmp/keyring-setup-loose.log
|
||||
grep -F "Failed to chase '/etc/voa/$ID/kernel-keyring/dm-verity/x509/loose-certificate.pem': Link has been severed" /tmp/keyring-setup-loose.log >/dev/null
|
||||
(! keyctl list %:.dm-verity | grep -F 'loose' >/dev/null)
|
||||
grep -E ' +08070000 +0 +0 +keyring +\.dm-verity: 8$' /proc/keys
|
||||
chmod 0644 "/etc/voa/$ID/kernel-keyring/dm-verity/x509/loose-certificate.pem"
|
||||
"$KEYRING_SETUP" .dm-verity
|
||||
keyctl list %:.dm-verity | grep -F 'loose'
|
||||
grep -E ' +08070000 +0 +0 +keyring +\.dm-verity: 9$' /proc/keys
|
||||
|
||||
# A file holding more than one certificate is refused as a whole, intermediates cannot be smuggled in
|
||||
for cn in chainfirst chainsecond; do
|
||||
openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -subj "/CN=$cn" \
|
||||
-keyout /dev/null -out "/tmp/$cn.csr"
|
||||
openssl x509 -req -in "/tmp/$cn.csr" -CA /usr/share/mkosi.crt -CAkey /usr/share/mkosi.key \
|
||||
-set_serial "0x$(printf '%s' "$cn" | sha256sum | cut -c1-8)" -days 1 \
|
||||
-extfile <(echo authorityKeyIdentifier=keyid,issuer) -out "/tmp/$cn.pem"
|
||||
done
|
||||
cat /tmp/chainfirst.pem /tmp/chainsecond.pem >"/etc/voa/$ID/kernel-keyring/dm-verity/x509/chain-certificate.pem"
|
||||
rc=0
|
||||
"$KEYRING_SETUP" .dm-verity 2>&1 | tee /tmp/keyring-setup-chain.log || rc=$?
|
||||
[[ "$rc" -eq 65 ]]
|
||||
grep -F "'/etc/voa/$ID/kernel-keyring/dm-verity/x509/chain-certificate.pem' contains more than one certificate, ignoring." /tmp/keyring-setup-chain.log >/dev/null
|
||||
(! keyctl list %:.dm-verity | grep -F 'chainfirst' >/dev/null)
|
||||
(! keyctl list %:.dm-verity | grep -F 'chainsecond' >/dev/null)
|
||||
grep -E ' +08070000 +0 +0 +keyring +\.dm-verity: 9$' /proc/keys
|
||||
|
||||
rm -rf "/etc/voa/$ID"
|
||||
|
||||
# Check that the kernel verifies the image against the .dm-verity keyring: with the certificate masked
|
||||
# for systemd's userspace fallback and a signed image required, activation only succeeds if the
|
||||
# kernel accepted the signature.
|
||||
mkdir -p /etc/verity.d
|
||||
ln -sf /dev/null /etc/verity.d/mkosi.crt
|
||||
systemd-run --pipe --wait \
|
||||
--property RootImage=/usr/share/minimal_0.raw \
|
||||
--property RootImagePolicy=root=signed \
|
||||
bash --version >/dev/null
|
||||
rm -f /etc/verity.d/mkosi.crt
|
||||
+4
-1
@@ -306,7 +306,10 @@ kernel_supports_lsm() {
|
||||
machine_supports_verity_keyring() {
|
||||
# Requires kernel built with certain kconfigs, as listed in README:
|
||||
# https://oracle.github.io/kconfigs/?config=UTS_RELEASE&config=DM_VERITY_VERIFY_ROOTHASH_SIG&config=DM_VERITY_VERIFY_ROOTHASH_SIG_SECONDARY_KEYRING&config=DM_VERITY_VERIFY_ROOTHASH_SIG_PLATFORM_KEYRING&config=IMA_ARCH_POLICY&config=INTEGRITY_MACHINE_KEYRING
|
||||
if grep -q "$(openssl x509 -noout -subject -in /usr/share/mkosi.crt | sed 's/^.*CN=//')" /proc/keys && \
|
||||
# Only the keyrings dm-verity consults count, .fs-verity holds the same certificate without implying
|
||||
# verity signature support
|
||||
if { keyctl list %:.dm-verity; keyctl list %:.platform; keyctl list %:.machine; keyctl list %:.secondary_trusted_keys; :; } 2>/dev/null |
|
||||
grep -F "$(openssl x509 -noout -subject -in /usr/share/mkosi.crt | sed 's/^.*CN=//')" >/dev/null && \
|
||||
( . /etc/os-release; [ "$ID" != "centos" ] || systemd-analyze compare-versions "$VERSION_ID" ge 10 ) && \
|
||||
( . /etc/os-release; [ "$ID" != "debian" ] || [ -z "${VERSION_ID:-}" ] || systemd-analyze compare-versions "$VERSION_ID" ge 13 ) && \
|
||||
( . /etc/os-release; [ "$ID" != "ubuntu" ] || systemd-analyze compare-versions "$VERSION_ID" ge 24.04 ) && \
|
||||
|
||||
@@ -546,6 +546,11 @@ units = [
|
||||
{ 'file' : 'systemd-journald@.service.in' },
|
||||
{ 'file' : 'systemd-journald@.socket' },
|
||||
{ 'file' : 'systemd-kexec.service' },
|
||||
{
|
||||
'file' : 'systemd-keyring-setup.service.in',
|
||||
'conditions' : ['HAVE_OPENSSL', 'HAVE_RUST'],
|
||||
'symlinks' : ['sysinit.target.wants/'],
|
||||
},
|
||||
{
|
||||
'file' : 'systemd-localed.service.in',
|
||||
'conditions' : ['ENABLE_LOCALED'],
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
# 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.
|
||||
|
||||
[Unit]
|
||||
Description=Enroll Certificates into Kernel Keyrings
|
||||
Documentation=man:systemd-keyring-setup.service(8)
|
||||
|
||||
DefaultDependencies=no
|
||||
Conflicts=shutdown.target initrd-switch-root.target
|
||||
Before=sysinit.target shutdown.target initrd-switch-root.target
|
||||
# Verity devices are set up after veritysetup-pre.target, everything mounting images after local-fs-pre.target
|
||||
Before=local-fs-pre.target veritysetup-pre.target
|
||||
Wants=local-fs-pre.target veritysetup-pre.target
|
||||
Before=systemd-sysext-initrd.service systemd-confext-initrd.service systemd-mountfsd.service
|
||||
|
||||
# A privileged container sharing the initial user namespace could otherwise provision the host's keyrings
|
||||
ConditionVirtualization=!container
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
SuccessExitStatus=DATAERR
|
||||
ImportCredential=keyring-setup.*
|
||||
ExecStart={{LIBEXECDIR}}/systemd-keyring-setup
|
||||
|
||||
# Loading dm_verity is the only privileged operation, the keyrings belong to root anyway
|
||||
CapabilityBoundingSet=CAP_SYS_MODULE
|
||||
IPAddressDeny=any
|
||||
LockPersonality=yes
|
||||
MemoryDenyWriteExecute=yes
|
||||
NoNewPrivileges=yes
|
||||
PrivateDevices=yes
|
||||
ProtectClock=yes
|
||||
ProtectControlGroups=yes
|
||||
ProtectHome=yes
|
||||
ProtectHostname=yes
|
||||
ProtectKernelLogs=yes
|
||||
ProtectKernelTunables=yes
|
||||
ProtectSystem=strict
|
||||
RestrictAddressFamilies=AF_UNIX
|
||||
RestrictNamespaces=yes
|
||||
RestrictRealtime=yes
|
||||
RestrictSUIDSGID=yes
|
||||
# The ephemeral load path credentials are placed into, kept for the consumers of the hierarchy
|
||||
RuntimeDirectory=voa
|
||||
RuntimeDirectoryPreserve=yes
|
||||
SystemCallArchitectures=native
|
||||
SystemCallErrorNumber=EPERM
|
||||
SystemCallFilter=@system-service @module
|
||||
Reference in new issue
Block a user