mirror of
https://github.com/systemd/systemd
synced 2026-03-07 13:44:46 +01:00
Compare commits
16 Commits
450e0dce02
...
cad60201fc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cad60201fc | ||
|
|
10eaca4159 | ||
|
|
d8da141348 | ||
|
|
02cab70acf | ||
|
|
a3af68b84d | ||
|
|
3821e4d8fb | ||
|
|
521a523ce0 | ||
|
|
a450fab9c6 | ||
|
|
85d7fb2247 | ||
|
|
32f405074a | ||
|
|
71ca7532de | ||
|
|
3f31c8ff46 | ||
|
|
8485bba53d | ||
|
|
0a8f393c64 | ||
|
|
be36f69c90 | ||
|
|
d84d17853e |
2
README
2
README
@ -204,6 +204,8 @@ REQUIREMENTS:
|
||||
CONFIG_MEMCG
|
||||
|
||||
glibc >= 2.34
|
||||
musl >= 1.2.5 with fde29c04adbab9d5b081bf6717b5458188647f1c
|
||||
(required when building systemd with -Dlibc=musl)
|
||||
libxcrypt >= 4.4.0 (optional)
|
||||
libmount >= 2.30 (from util-linux)
|
||||
(util-linux *must* be built without --enable-libmount-support-mtab)
|
||||
|
||||
22
TODO
22
TODO
@ -720,10 +720,6 @@ Features:
|
||||
deleting entries for rotation, place an event that declares how many items
|
||||
have been dropped, and what the hash before and after that.
|
||||
|
||||
* measure information about all DDIs as we activate them to an NvPCR. We
|
||||
probably should measure the dm-verity root hash from the kernel side, but
|
||||
DDI meta info from userspace.
|
||||
|
||||
* use name_to_handle_at() with AT_HANDLE_FID instead of .st_ino (inode
|
||||
number) for identifying inodes, for example in copy.c when finding hard
|
||||
links, or loop-util.c for tracking backing files, and other places.
|
||||
@ -1299,9 +1295,9 @@ Features:
|
||||
- If run on every boot, should it use the sysupdate config from the host on
|
||||
subsequent boots?
|
||||
|
||||
* To mimic the new tpm2-measure-pcr= crypttab option add the same to veritytab
|
||||
(measuring the root hash) and integritytab (measuring the HMAC key if one is
|
||||
used)
|
||||
* To mimic the new tpm2-measure-pcr= crypttab option and tpm2-measure-nvpcr=
|
||||
veritytab option, add the same to integritytab (measuring the HMAC key if one
|
||||
is used)
|
||||
|
||||
* We should start measuring all services, containers, and system extensions we
|
||||
activate. probably into PCR 13. i.e. add --tpm2-measure-pcr= or so to
|
||||
@ -1720,18 +1716,6 @@ Features:
|
||||
keys of /etc/crypttab. That way people can store/provide the roothash
|
||||
externally and provide to us on demand only.
|
||||
|
||||
* we probably should extend the root verity hash of the root fs into some PCR
|
||||
on boot. (i.e. maybe add a veritytab option tpm2-measure=12 or so to measure
|
||||
it into PCR 12); Similar: we probably should extend the LUKS volume key of
|
||||
the root fs into some PCR on boot. (i.e. maybe add a crypttab option
|
||||
tpm2-measure=15 or so to measure it into PCR 15); once both are in place
|
||||
update gpt-auto-discovery to generate these by default for the partitions it
|
||||
discovers. Static vendor stuff should probably end up in PCR 12 (i.e. the
|
||||
verity hash), with local keys in PCR 15 (i.e. the encryption volume
|
||||
key). That way, we nicely distinguish resources supplied by the OS vendor
|
||||
(i.e. sysext, root verity) from those inherently local (i.e. encryption key),
|
||||
which is useful if they shall be signed separately.
|
||||
|
||||
* rework recursive read-only remount to use new mount API
|
||||
|
||||
* when mounting disk images: if IMAGE_ID/IMAGE_VERSION is set in os-release
|
||||
|
||||
@ -945,12 +945,12 @@
|
||||
<term><option>tpm2-measure-keyslot-nvpcr=</option></term>
|
||||
|
||||
<listitem><para>Controls whether to measure information about the used LUKS unlock keyslot to a TPM2
|
||||
non-volatile index (nvindex in PCR mode). If set to to an empty string (which is the default) no TPM2
|
||||
nvindex extension is done, otherwise keyslot information is measured to an nvindex of the specified
|
||||
name, which is allocated if needed. It is recommended to set this to <literal>cryptsetup</literal> to
|
||||
enable this logic. The slot index and the used unlock mechanism (i.e. <literal>tpm2</literal>,
|
||||
<literal>fido2</literal>, <literal>pkcs11</literal>) is measured along with the activated volume name
|
||||
and its UUID.</para>
|
||||
non-volatile index (nvindex in PCR mode). Takes a boolean argument, or an NvPCR name. If set to false
|
||||
or an empty string (which is the default) no TPM2 nvindex extension is done, otherwise keyslot
|
||||
information is measured to an nvindex of the specified name, which is allocated if needed. If set to
|
||||
true the recommended default of <literal>cryptsetup</literal> is selected as NvPCR. The slot index
|
||||
and the used unlock mechanism (i.e. <literal>tpm2</literal>, <literal>fido2</literal>,
|
||||
<literal>pkcs11</literal>) are measured along with the activated volume name and its UUID.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
@ -272,9 +272,13 @@
|
||||
<para>If the system was booted via
|
||||
<citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry> and the
|
||||
stub reported to userspace that the kernel image was measured to a TPM2 PCR, then any discovered root and
|
||||
<filename>/var/</filename> volume identifiers (and volume encryption key in case it is encrypted) will be
|
||||
automatically measured into PCR 15 on activation, via
|
||||
<citerefentry><refentrytitle>systemd-pcrfs@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
|
||||
<filename>/var/</filename> volume identifiers (and volume encryption keys, in case they are encrypted)
|
||||
will be automatically measured into PCR 15 on activation, via
|
||||
<citerefentry><refentrytitle>systemd-pcrfs@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>. Moreover,
|
||||
information about the LUKS key slot used to unlock the volume is measured into NvPCR
|
||||
<literal>cryptsetup</literal>. Finally, if the root or <filename>/usr/</filename> partition is protected
|
||||
via Verity its root hash and the serial/issuer of the key used for the provided root hash signature (if
|
||||
any) are measured into the NvPCR <literal>verity</literal>.</para>
|
||||
|
||||
<para>Mount constraint metadata contained in the file systems is validated by pulling in
|
||||
<citerefentry><refentrytitle>systemd-validatefs@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
|
||||
|
||||
@ -298,6 +298,18 @@ This is based on crypttab(5).
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>tpm2-measure-nvpcr=</option></term>
|
||||
|
||||
<listitem><para>Takes a boolean argument, or an NvPCR name as argument. May be used to enable
|
||||
automatic measurement of the volume name and Verity root hash, as well as the serials and issuers of
|
||||
the certificates used to generate the provided signatures (if any) will be measured. Passing false
|
||||
disables the mechanism. Passing true enables measurement into the <literal>verity</literal> NvPCR. If
|
||||
any other string is specified this selects the NvPCR to measure into by name.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v260"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
</variablelist>
|
||||
|
||||
<para>At early boot and when the system manager configuration is
|
||||
|
||||
@ -6,7 +6,11 @@ KERNEL!="event*", GOTO="joystick_end"
|
||||
|
||||
# joystick:<bustype>:v<vid>p<pid>:name:<name>:*
|
||||
KERNELS=="input*", ENV{ID_BUS}!="", \
|
||||
IMPORT{builtin}="hwdb 'joystick:$env{ID_BUS}:v$attr{id/vendor}p$attr{id/product}:name:$attr{name}:'", \
|
||||
GOTO="joystick_end"
|
||||
IMPORT{builtin}="hwdb 'joystick:$env{ID_BUS}:v$attr{id/vendor}p$attr{id/product}:name:$attr{name}:'"
|
||||
|
||||
# Spread the hwdb override to ID_INTEGRATION, in the future we could remove the
|
||||
# joystick hwdb entirely or retain it using the generic ID_INTEGRATION instead
|
||||
# specific ID_INPUT_JOYSTICK_INTEGRATION.
|
||||
ENV{ID_INPUT_JOYSTICK_INTEGRATION}!="", ENV{ID_INTEGRATION}="$env{ID_INPUT_JOYSTICK_INTEGRATION}"
|
||||
|
||||
LABEL="joystick_end"
|
||||
|
||||
@ -1,13 +1,16 @@
|
||||
# do not edit this file, it will be overwritten on update
|
||||
|
||||
ACTION=="remove", GOTO="touchpad_end"
|
||||
ENV{ID_INPUT}=="", GOTO="touchpad_end"
|
||||
ENV{ID_INPUT_TOUCHPAD}=="", GOTO="touchpad_end"
|
||||
KERNEL!="event*", GOTO="touchpad_end"
|
||||
|
||||
# touchpad:<subsystem>:v<vid>p<pid>:name:<name>:*
|
||||
KERNELS=="input*", ENV{ID_BUS}!="", \
|
||||
IMPORT{builtin}="hwdb 'touchpad:$env{ID_BUS}:v$attr{id/vendor}p$attr{id/product}:name:$attr{name}:'", \
|
||||
GOTO="touchpad_end"
|
||||
IMPORT{builtin}="hwdb 'touchpad:$env{ID_BUS}:v$attr{id/vendor}p$attr{id/product}:name:$attr{name}:'"
|
||||
|
||||
# Spread the hwdb override to ID_INTEGRATION, in the future we could remove the
|
||||
# touchpad hwdb entirely or retain it using the generic ID_INTEGRATION instead
|
||||
# specific ID_INPUT_TOUCHPAD_INTEGRATION.
|
||||
ENV{ID_INPUT_TOUCHPAD_INTEGRATION}!="", ENV{ID_INTEGRATION}="$env{ID_INPUT_TOUCHPAD_INTEGRATION}"
|
||||
|
||||
LABEL="touchpad_end"
|
||||
|
||||
@ -557,12 +557,14 @@ static int parse_one_option(const char *option) {
|
||||
|
||||
} else if ((val = startswith(option, "tpm2-measure-keyslot-nvpcr="))) {
|
||||
|
||||
if (isempty(val)) {
|
||||
r = isempty(val) ? false : parse_boolean(val);
|
||||
if (r == 0) {
|
||||
arg_tpm2_measure_keyslot_nvpcr = mfree(arg_tpm2_measure_keyslot_nvpcr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!tpm2_nvpcr_name_is_valid(val)) {
|
||||
if (r > 0)
|
||||
val = "cryptsetup";
|
||||
else if (!tpm2_nvpcr_name_is_valid(val)) {
|
||||
log_warning("Invalid NvPCR name, ignoring: %s", option);
|
||||
return 0;
|
||||
}
|
||||
@ -1095,8 +1097,9 @@ static int measure_keyslot(
|
||||
const char *mechanism,
|
||||
int keyslot) {
|
||||
|
||||
#if HAVE_TPM2
|
||||
int r;
|
||||
|
||||
#endif
|
||||
assert(cd);
|
||||
assert(name);
|
||||
|
||||
@ -1105,6 +1108,7 @@ static int measure_keyslot(
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if HAVE_TPM2
|
||||
r = efi_measured_uki(LOG_WARNING);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@ -1113,7 +1117,6 @@ static int measure_keyslot(
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if HAVE_TPM2
|
||||
_cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL;
|
||||
r = tpm2_context_new_or_warn(arg_tpm2_device, &c);
|
||||
if (r < 0)
|
||||
|
||||
@ -127,7 +127,7 @@ static int add_cryptsetup(
|
||||
* assignment, under the assumption that people who are fine to use sd-stub with its PCR
|
||||
* assignments are also OK with our PCR 15 use here. */
|
||||
if (r > 0)
|
||||
if (!strextend_with_separator(&options, ",", "tpm2-measure-pcr=yes,tpm2-measure-keyslot-nvpcr=cryptsetup"))
|
||||
if (!strextend_with_separator(&options, ",", "tpm2-measure-pcr=yes,tpm2-measure-keyslot-nvpcr=yes"))
|
||||
return log_oom();
|
||||
if (r == 0)
|
||||
log_debug("Will not measure volume key of volume '%s', not booted via systemd-stub with measurements enabled.", id);
|
||||
@ -190,7 +190,8 @@ static int add_veritysetup(
|
||||
const char *id,
|
||||
const char *data_what,
|
||||
const char *hash_what,
|
||||
const char *mount_opts) {
|
||||
const char *mount_opts,
|
||||
MountPointFlags flags) {
|
||||
|
||||
#if HAVE_LIBCRYPTSETUP
|
||||
int r;
|
||||
@ -233,13 +234,26 @@ static int add_veritysetup(
|
||||
"After=%1$s %2$s\n",
|
||||
dd, dh);
|
||||
|
||||
_cleanup_free_ char *options =
|
||||
strdup("root-hash-signature=auto"); /* auto means: derive signature from udev property ID_DISSECT_PART_ROOTHASH_SIG */
|
||||
if (!options)
|
||||
return log_oom();
|
||||
|
||||
if (FLAGS_SET(flags, MOUNT_MEASURE)) {
|
||||
r = efi_measured_uki(LOG_WARNING);
|
||||
if (r > 0 && !strextend_with_separator(&options, ",", "tpm2-measure-nvpcr=yes"))
|
||||
return log_oom();
|
||||
if (r == 0)
|
||||
log_debug("Will not measure root hash/signature of volume '%s', not booted via systemd-stub with measurements enabled.", id);
|
||||
}
|
||||
|
||||
r = generator_write_veritysetup_service_section(
|
||||
f,
|
||||
id,
|
||||
data_what,
|
||||
hash_what,
|
||||
/* roothash= */ NULL, /* NULL means: derive root hash from udev property ID_DISSECT_PART_ROOTHASH */
|
||||
"root-hash-signature=auto"); /* auto means: derive signature from udev property ID_DISSECT_PART_ROOTHASH_SIG */
|
||||
options);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@ -871,7 +885,8 @@ static int add_root_mount(void) {
|
||||
"root",
|
||||
"/dev/disk/by-designator/root-verity-data",
|
||||
"/dev/disk/by-designator/root-verity",
|
||||
arg_root_options);
|
||||
arg_root_options,
|
||||
MOUNT_MEASURE);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
@ -952,7 +967,8 @@ static int add_usr_mount(void) {
|
||||
"usr",
|
||||
"/dev/disk/by-designator/usr-verity-data",
|
||||
"/dev/disk/by-designator/usr-verity",
|
||||
arg_usr_options);
|
||||
arg_usr_options,
|
||||
MOUNT_MEASURE);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
@ -369,6 +369,7 @@ typedef struct MethodExtendParameters {
|
||||
const char *nvpcr;
|
||||
const char *text;
|
||||
struct iovec data;
|
||||
Tpm2UserspaceEventType event_type;
|
||||
} MethodExtendParameters;
|
||||
|
||||
static void method_extend_parameters_done(MethodExtendParameters *p) {
|
||||
@ -377,17 +378,21 @@ static void method_extend_parameters_done(MethodExtendParameters *p) {
|
||||
iovec_done(&p->data);
|
||||
}
|
||||
|
||||
static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_tpm2_userspace_event_type, Tpm2UserspaceEventType, tpm2_userspace_event_type_from_string);
|
||||
|
||||
static int vl_method_extend(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
|
||||
|
||||
static const sd_json_dispatch_field dispatch_table[] = {
|
||||
{ "pcr", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, offsetof(MethodExtendParameters, pcr), 0 },
|
||||
{ "nvpcr", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MethodExtendParameters, nvpcr), 0 },
|
||||
{ "text", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MethodExtendParameters, text), 0 },
|
||||
{ "data", SD_JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(MethodExtendParameters, data), 0 },
|
||||
{ "pcr", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, offsetof(MethodExtendParameters, pcr), 0 },
|
||||
{ "nvpcr", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MethodExtendParameters, nvpcr), 0 },
|
||||
{ "text", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MethodExtendParameters, text), 0 },
|
||||
{ "data", SD_JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(MethodExtendParameters, data), 0 },
|
||||
{ "eventType", SD_JSON_VARIANT_STRING, json_dispatch_tpm2_userspace_event_type, offsetof(MethodExtendParameters, event_type), 0 },
|
||||
{}
|
||||
};
|
||||
_cleanup_(method_extend_parameters_done) MethodExtendParameters p = {
|
||||
.pcr = UINT_MAX,
|
||||
.event_type = _TPM2_USERSPACE_EVENT_TYPE_INVALID,
|
||||
};
|
||||
int r;
|
||||
|
||||
@ -424,11 +429,11 @@ static int vl_method_extend(sd_varlink *link, sd_json_variant *parameters, sd_va
|
||||
return sd_varlink_error_invalid_parameter_name(link, "text");
|
||||
|
||||
if (p.nvpcr) {
|
||||
r = extend_nvpcr_now(p.nvpcr, extend_iovec->iov_base, extend_iovec->iov_len, _TPM2_USERSPACE_EVENT_TYPE_INVALID);
|
||||
r = extend_nvpcr_now(p.nvpcr, extend_iovec->iov_base, extend_iovec->iov_len, p.event_type);
|
||||
if (r == -ENOENT)
|
||||
return sd_varlink_error(link, "io.systemd.PCRExtend.NoSuchNvPCR", NULL);
|
||||
} else
|
||||
r = extend_pcr_now(p.pcr, extend_iovec->iov_base, extend_iovec->iov_len, _TPM2_USERSPACE_EVENT_TYPE_INVALID);
|
||||
r = extend_pcr_now(p.pcr, extend_iovec->iov_base, extend_iovec->iov_len, p.event_type);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
||||
@ -60,6 +60,7 @@
|
||||
#include "openssl-util.h"
|
||||
#include "os-util.h"
|
||||
#include "path-util.h"
|
||||
#include "pcrextend-util.h"
|
||||
#include "pidref.h"
|
||||
#include "proc-cmdline.h"
|
||||
#include "process-util.h"
|
||||
@ -3219,13 +3220,13 @@ static int do_crypt_activate_verity(
|
||||
DissectImageFlags flags,
|
||||
PartitionPolicyFlags policy_flags) {
|
||||
|
||||
bool check_signature;
|
||||
int r, k;
|
||||
int r;
|
||||
|
||||
assert(cd);
|
||||
assert(name);
|
||||
assert(verity);
|
||||
|
||||
bool check_signature;
|
||||
if (iovec_is_set(&verity->root_hash_sig) && FLAGS_SET(policy_flags, PARTITION_POLICY_SIGNED)) {
|
||||
r = secure_getenv_bool("SYSTEMD_DISSECT_VERITY_SIGNATURE");
|
||||
if (r < 0 && r != -ENXIO)
|
||||
@ -3235,7 +3236,10 @@ static int do_crypt_activate_verity(
|
||||
} else
|
||||
check_signature = false;
|
||||
|
||||
bool measure_signature;
|
||||
if (check_signature) {
|
||||
int k;
|
||||
|
||||
/* First, if we have support for signed keys in the kernel, then try that first. */
|
||||
r = sym_crypt_activate_by_signed_key(
|
||||
cd,
|
||||
@ -3247,7 +3251,8 @@ static int do_crypt_activate_verity(
|
||||
CRYPT_ACTIVATE_READONLY);
|
||||
if (r >= 0) {
|
||||
log_debug("Verity activation via kernel signature logic worked.");
|
||||
return 0;
|
||||
measure_signature = true;
|
||||
goto done;
|
||||
}
|
||||
|
||||
log_debug_errno(r, "Validation of dm-verity signature failed via the kernel, trying userspace validation instead: %m");
|
||||
@ -3280,9 +3285,13 @@ static int do_crypt_activate_verity(
|
||||
|
||||
/* Otherwise let's see what signature-less activation results in. */
|
||||
|
||||
measure_signature = true;
|
||||
|
||||
} else if (!FLAGS_SET(policy_flags, PARTITION_POLICY_VERITY))
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ERFKILL),
|
||||
"No-signature activation of Verity volume not allowed by policy, refusing.");
|
||||
else
|
||||
measure_signature = false;
|
||||
|
||||
r = sym_crypt_activate_by_volume_key(
|
||||
cd,
|
||||
@ -3294,6 +3303,12 @@ static int do_crypt_activate_verity(
|
||||
return log_debug_errno(r, "Activation of Verity via root hash failed: %m");
|
||||
|
||||
log_debug("Activation of Verity via root hash succeeded.");
|
||||
|
||||
done:
|
||||
(void) pcrextend_verity_now(
|
||||
name,
|
||||
&verity->root_hash,
|
||||
measure_signature ? &verity->root_hash_sig : NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@ -152,6 +152,7 @@ shared_sources = files(
|
||||
'pcre2-util.c',
|
||||
'pcrextend-util.c',
|
||||
'pe-binary.c',
|
||||
'pkcs7-util.c',
|
||||
'pkcs11-util.c',
|
||||
'plymouth-util.c',
|
||||
'polkit-agent.c',
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
#include "sd-device.h"
|
||||
#include "sd-id128.h"
|
||||
#include "sd-varlink.h"
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "blkid-util.h"
|
||||
@ -10,10 +11,13 @@
|
||||
#include "errno-util.h"
|
||||
#include "escape.h"
|
||||
#include "fd-util.h"
|
||||
#include "hexdecoct.h"
|
||||
#include "id128-util.h"
|
||||
#include "iovec-util.h"
|
||||
#include "log.h"
|
||||
#include "mountpoint-util.h"
|
||||
#include "pcrextend-util.h"
|
||||
#include "pkcs7-util.h"
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
|
||||
@ -180,3 +184,110 @@ int pcrextend_product_id_word(char **ret) {
|
||||
*ret = TAKE_PTR(word);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pcrextend_verity_word(
|
||||
const char *name,
|
||||
const struct iovec *root_hash,
|
||||
const struct iovec *root_hash_sig,
|
||||
char **ret) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(name);
|
||||
assert(iovec_is_set(root_hash));
|
||||
|
||||
_cleanup_free_ char *name_escaped = xescape(name, ":"); /* Avoid ambiguity around ":" */
|
||||
if (!name_escaped)
|
||||
return log_oom();
|
||||
|
||||
_cleanup_free_ char *h = hexmem(root_hash->iov_base, root_hash->iov_len);
|
||||
if (!h)
|
||||
return log_oom();
|
||||
|
||||
_cleanup_free_ char *sigs = NULL;
|
||||
if (iovec_is_set(root_hash_sig)) {
|
||||
size_t n_signers = 0;
|
||||
Signer *signers = NULL;
|
||||
|
||||
/* Let's extract the X.509 issuer + serial number from the PKCS#7 signature and include that
|
||||
* in the measurement record. This is useful since it allows us to have different signing
|
||||
* keys for confext + sysext + other types of DDIs, and by means of this information we can
|
||||
* discern which kind it was. Ideally, we'd measure the fingerprint of the X.509 certificate,
|
||||
* but typically that's not available in a PKCS#7 signature. */
|
||||
|
||||
CLEANUP_ARRAY(signers, n_signers, signer_free_many);
|
||||
|
||||
r = pkcs7_extract_signers(root_hash_sig, &signers, &n_signers);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
FOREACH_ARRAY(i, signers, n_signers) {
|
||||
_cleanup_free_ char *serial = hexmem(i->serial.iov_base, i->serial.iov_len);
|
||||
if (!serial)
|
||||
return log_oom();
|
||||
|
||||
_cleanup_free_ char *issuer = NULL;
|
||||
if (base64mem(i->issuer.iov_base, i->issuer.iov_len, &issuer) < 0)
|
||||
return log_oom();
|
||||
|
||||
if (strextendf_with_separator(&sigs, ",", "%s/%s", serial, issuer) < 0)
|
||||
return log_oom();
|
||||
}
|
||||
}
|
||||
|
||||
_cleanup_free_ char *word = strjoin("verity:", name_escaped, ":", h, ":", strempty(sigs));
|
||||
if (!word)
|
||||
return log_oom();
|
||||
|
||||
*ret = TAKE_PTR(word);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pcrextend_verity_now(
|
||||
const char *name,
|
||||
const struct iovec *root_hash,
|
||||
const struct iovec *root_hash_sig) {
|
||||
|
||||
#if HAVE_TPM2
|
||||
int r;
|
||||
|
||||
_cleanup_free_ char *word = NULL;
|
||||
r = pcrextend_verity_word(
|
||||
name,
|
||||
root_hash,
|
||||
root_hash_sig,
|
||||
&word);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_free_ sd_varlink *vl = NULL;
|
||||
r = sd_varlink_connect_address(&vl, "/run/systemd/io.systemd.PCRExtend");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *reply = NULL;
|
||||
const char *error_id = NULL;
|
||||
r = sd_varlink_callbo(
|
||||
vl,
|
||||
"io.systemd.PCRExtend.Extend",
|
||||
/* ret_reply= */ NULL,
|
||||
&error_id,
|
||||
SD_JSON_BUILD_PAIR_STRING("nvpcr", "verity"),
|
||||
SD_JSON_BUILD_PAIR_STRING("text", word),
|
||||
SD_JSON_BUILD_PAIR_STRING("eventType", "dm_verity"));
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to issue io.systemd.PCRExtend.Extend() varlink call: %m");
|
||||
if (error_id) {
|
||||
r = sd_varlink_error_to_errno(error_id, reply);
|
||||
if (r != -EBADR)
|
||||
return log_debug_errno(r, "Failed to issue io.systemd.PCRExtend.Extend() varlink call: %m");
|
||||
|
||||
return log_debug_errno(r, "Failed to issue io.systemd.PCRExtend.Extend() varlink call: %s", error_id);
|
||||
}
|
||||
|
||||
log_debug("Measurement of '%s' into 'images' NvPCR completed.", word);
|
||||
return 1;
|
||||
#else
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support disabled, not measuring Verity root hashes and signatures.");
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -4,3 +4,6 @@
|
||||
int pcrextend_file_system_word(const char *path, char **ret, char **ret_normalized_path);
|
||||
int pcrextend_machine_id_word(char **ret);
|
||||
int pcrextend_product_id_word(char **ret);
|
||||
int pcrextend_verity_word(const char *name, const struct iovec *root_hash, const struct iovec *root_hash_sig, char **ret);
|
||||
|
||||
int pcrextend_verity_now(const char *name, const struct iovec *root_hash,const struct iovec *root_hash_sig);
|
||||
|
||||
@ -243,7 +243,7 @@ int pe_read_section_data_by_name(
|
||||
|
||||
assert(fd >= 0);
|
||||
assert(pe_header);
|
||||
assert(sections || pe_header->pe.NumberOfSections == 0);
|
||||
assert(sections || le16toh(pe_header->pe.NumberOfSections) == 0);
|
||||
assert(name);
|
||||
|
||||
section = pe_header_find_section(pe_header, sections, name);
|
||||
@ -408,9 +408,9 @@ int pe_hash(int fd,
|
||||
return r;
|
||||
|
||||
/* Sort by location in file */
|
||||
typesafe_qsort(sections, pe_header->pe.NumberOfSections, section_offset_cmp);
|
||||
typesafe_qsort(sections, le16toh(pe_header->pe.NumberOfSections), section_offset_cmp);
|
||||
|
||||
FOREACH_ARRAY(section, sections, pe_header->pe.NumberOfSections) {
|
||||
FOREACH_ARRAY(section, sections, le16toh(pe_header->pe.NumberOfSections)) {
|
||||
r = hash_file(fd, mdctx, section->PointerToRawData, section->SizeOfRawData);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@ -537,7 +537,7 @@ int uki_hash(int fd,
|
||||
if (hsz < 0)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to get hash size.");
|
||||
|
||||
FOREACH_ARRAY(section, sections, pe_header->pe.NumberOfSections) {
|
||||
FOREACH_ARRAY(section, sections, le16toh(pe_header->pe.NumberOfSections)) {
|
||||
_cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *mdctx = NULL;
|
||||
_cleanup_free_ char *n = NULL;
|
||||
ssize_t i;
|
||||
|
||||
84
src/shared/pkcs7-util.c
Normal file
84
src/shared/pkcs7-util.c
Normal file
@ -0,0 +1,84 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "openssl-util.h"
|
||||
#include "pkcs7-util.h"
|
||||
#include "log.h"
|
||||
|
||||
#define SIGNERS_MAX 32
|
||||
|
||||
static void signer_done(Signer *signer) {
|
||||
assert(signer);
|
||||
|
||||
iovec_done(&signer->issuer);
|
||||
iovec_done(&signer->serial);
|
||||
}
|
||||
|
||||
void signer_free_many(Signer *signers, size_t n) {
|
||||
assert(signers || n == 0);
|
||||
|
||||
FOREACH_ARRAY(i, signers, n)
|
||||
signer_done(i);
|
||||
|
||||
free(signers);
|
||||
}
|
||||
|
||||
int pkcs7_extract_signers(
|
||||
const struct iovec *sig,
|
||||
Signer **ret_signers,
|
||||
size_t *ret_n_signers) {
|
||||
|
||||
assert(ret_signers);
|
||||
assert(ret_n_signers);
|
||||
|
||||
if (!iovec_is_set(sig))
|
||||
return -EBADMSG;
|
||||
|
||||
#if HAVE_OPENSSL
|
||||
const unsigned char *d = sig->iov_base;
|
||||
_cleanup_(PKCS7_freep) PKCS7 *p7 = NULL;
|
||||
p7 = d2i_PKCS7(/* a= */ NULL, &d, (long) sig->iov_len);
|
||||
if (!p7)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to parse PKCS7 DER signature data.");
|
||||
|
||||
STACK_OF(PKCS7_SIGNER_INFO) *sinfos = PKCS7_get_signer_info(p7);
|
||||
if (!sinfos)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENODATA), "No signature information in PKCS7 signature?");
|
||||
int n = sk_PKCS7_SIGNER_INFO_num(sinfos);
|
||||
if (n == 0)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENODATA), "No signatures in PKCS7 signature, refusing.");
|
||||
if (n > SIGNERS_MAX) /* safety net, in case people send us weirdly complex signatures */
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Too many signatures, refusing.");
|
||||
assert(n > 0);
|
||||
|
||||
size_t n_signers = 0;
|
||||
Signer *signers = new(Signer, n);
|
||||
if (!signers)
|
||||
return log_oom_debug();
|
||||
|
||||
CLEANUP_ARRAY(signers, n_signers, signer_free_many);
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
PKCS7_SIGNER_INFO *si = sk_PKCS7_SIGNER_INFO_value(PKCS7_get_signer_info(p7), i);
|
||||
if (!si)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to get signer information.");
|
||||
|
||||
_cleanup_(signer_done) Signer signer = {};
|
||||
|
||||
_cleanup_free_ unsigned char *p = NULL;
|
||||
int len = i2d_X509_NAME(si->issuer_and_serial->issuer, &p);
|
||||
signer.issuer = IOVEC_MAKE(TAKE_PTR(p), len);
|
||||
|
||||
len = i2d_ASN1_INTEGER(si->issuer_and_serial->serial, &p);
|
||||
signer.serial = IOVEC_MAKE(TAKE_PTR(p), len);
|
||||
|
||||
signers[n_signers++] = TAKE_STRUCT(signer);
|
||||
}
|
||||
|
||||
*ret_signers = TAKE_PTR(signers);
|
||||
*ret_n_signers = n_signers;
|
||||
return n;
|
||||
#else
|
||||
return -EOPNOTSUPP;
|
||||
#endif
|
||||
}
|
||||
16
src/shared/pkcs7-util.h
Normal file
16
src/shared/pkcs7-util.h
Normal file
@ -0,0 +1,16 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include <sys/uio.h>
|
||||
|
||||
typedef struct Signer {
|
||||
struct iovec issuer;
|
||||
struct iovec serial;
|
||||
} Signer;
|
||||
|
||||
void signer_free_many(Signer *signers, size_t n);
|
||||
|
||||
int pkcs7_extract_signers(
|
||||
const struct iovec *sig,
|
||||
Signer **ret_signers,
|
||||
size_t *ret_n_signers);
|
||||
@ -6478,6 +6478,7 @@ static const char* tpm2_userspace_event_type_table[_TPM2_USERSPACE_EVENT_TYPE_MA
|
||||
[TPM2_EVENT_KEYSLOT] = "keyslot",
|
||||
[TPM2_EVENT_NVPCR_INIT] = "nvpcr-init",
|
||||
[TPM2_EVENT_NVPCR_SEPARATOR] = "nvpcr-separator",
|
||||
[TPM2_EVENT_DM_VERITY] = "dm-verity",
|
||||
};
|
||||
|
||||
DEFINE_STRING_TABLE_LOOKUP(tpm2_userspace_event_type, Tpm2UserspaceEventType);
|
||||
|
||||
@ -145,6 +145,7 @@ typedef enum Tpm2UserspaceEventType {
|
||||
TPM2_EVENT_KEYSLOT,
|
||||
TPM2_EVENT_NVPCR_INIT,
|
||||
TPM2_EVENT_NVPCR_SEPARATOR,
|
||||
TPM2_EVENT_DM_VERITY,
|
||||
_TPM2_USERSPACE_EVENT_TYPE_MAX,
|
||||
_TPM2_USERSPACE_EVENT_TYPE_INVALID = -EINVAL,
|
||||
} Tpm2UserspaceEventType;
|
||||
|
||||
@ -2,6 +2,18 @@
|
||||
|
||||
#include "varlink-io.systemd.PCRExtend.h"
|
||||
|
||||
static SD_VARLINK_DEFINE_ENUM_TYPE(
|
||||
EventType,
|
||||
SD_VARLINK_DEFINE_ENUM_VALUE(phase),
|
||||
SD_VARLINK_DEFINE_ENUM_VALUE(filesystem),
|
||||
SD_VARLINK_DEFINE_ENUM_VALUE(volume_key),
|
||||
SD_VARLINK_DEFINE_ENUM_VALUE(machine_id),
|
||||
SD_VARLINK_DEFINE_ENUM_VALUE(product_id),
|
||||
SD_VARLINK_DEFINE_ENUM_VALUE(keyslot),
|
||||
SD_VARLINK_DEFINE_ENUM_VALUE(nvpcr_init),
|
||||
SD_VARLINK_DEFINE_ENUM_VALUE(nvpcr_separator),
|
||||
SD_VARLINK_DEFINE_ENUM_VALUE(dm_verity));
|
||||
|
||||
static SD_VARLINK_DEFINE_METHOD(
|
||||
Extend,
|
||||
SD_VARLINK_FIELD_COMMENT("PCR number to extend, in range of 0…23. Either this or 'nvpcr' must be specified, not both, not neither."),
|
||||
@ -11,7 +23,9 @@ static SD_VARLINK_DEFINE_METHOD(
|
||||
SD_VARLINK_FIELD_COMMENT("Text string to measure. (Specify either this, or the 'data' field below, not both)"),
|
||||
SD_VARLINK_DEFINE_INPUT(text, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("Binary data to measure, encoded in Base64. (Specify either this, or the 'text' field above, not both)"),
|
||||
SD_VARLINK_DEFINE_INPUT(data, SD_VARLINK_STRING, SD_VARLINK_NULLABLE));
|
||||
SD_VARLINK_DEFINE_INPUT(data, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
|
||||
SD_VARLINK_FIELD_COMMENT("Event type to include in the (userspace) event log). This is optional, and mostly for debugging."),
|
||||
SD_VARLINK_DEFINE_INPUT_BY_TYPE(eventType, EventType, SD_VARLINK_NULLABLE));
|
||||
|
||||
static SD_VARLINK_DEFINE_ERROR(NoSuchNvPCR);
|
||||
|
||||
@ -21,4 +35,6 @@ SD_VARLINK_DEFINE_INTERFACE(
|
||||
SD_VARLINK_INTERFACE_COMMENT("TPM PCR Extension APIs"),
|
||||
SD_VARLINK_SYMBOL_COMMENT("Measure some text or binary data into a PCR"),
|
||||
&vl_method_Extend,
|
||||
SD_VARLINK_SYMBOL_COMMENT("Event type to store in event log"),
|
||||
&vl_type_EventType,
|
||||
&vl_error_NoSuchNvPCR);
|
||||
|
||||
@ -9,6 +9,20 @@
|
||||
#include "string-util.h"
|
||||
#include "sysupdate-partition.h"
|
||||
|
||||
/* App-specific IDs used as HMAC keys to derive "partial" and "pending" partition type UUIDs from the
|
||||
* original partition type UUID. This way we can indicate sysupdate transfer state via a separate GPT
|
||||
* partition type UUID instead of using a label prefix, saving precious label space. */
|
||||
#define GPT_SYSUPDATE_PARTIAL_APP_ID SD_ID128_MAKE(ac,cf,a0,c2,da,24,46,0a,9f,c9,0b,b8,fc,78,52,19)
|
||||
#define GPT_SYSUPDATE_PENDING_APP_ID SD_ID128_MAKE(80,f3,d6,1e,23,83,43,b9,81,f5,ce,37,93,f4,7d,4c)
|
||||
|
||||
int gpt_partition_type_uuid_for_sysupdate_partial(sd_id128_t type, sd_id128_t *ret) {
|
||||
return sd_id128_get_app_specific(type, GPT_SYSUPDATE_PARTIAL_APP_ID, ret);
|
||||
}
|
||||
|
||||
int gpt_partition_type_uuid_for_sysupdate_pending(sd_id128_t type, sd_id128_t *ret) {
|
||||
return sd_id128_get_app_specific(type, GPT_SYSUPDATE_PENDING_APP_ID, ret);
|
||||
}
|
||||
|
||||
void partition_info_destroy(PartitionInfo *p) {
|
||||
assert(p);
|
||||
|
||||
@ -242,6 +256,22 @@ int patch_partition(
|
||||
return log_error_errno(r, "Failed to update partition UUID: %m");
|
||||
}
|
||||
|
||||
if (change & PARTITION_TYPE) {
|
||||
_cleanup_(fdisk_unref_parttypep) struct fdisk_parttype *pt = NULL;
|
||||
|
||||
pt = fdisk_new_parttype();
|
||||
if (!pt)
|
||||
return log_oom();
|
||||
|
||||
r = fdisk_parttype_set_typestr(pt, SD_ID128_TO_UUID_STRING(info->type));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to initialize partition type: %m");
|
||||
|
||||
r = fdisk_partition_set_type(pa, pt);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to update partition type: %m");
|
||||
}
|
||||
|
||||
type = gpt_partition_type_from_uuid(info->type);
|
||||
|
||||
/* Tweak the read-only flag, but only if supported by the partition type */
|
||||
|
||||
@ -12,7 +12,8 @@ typedef enum PartitionChange {
|
||||
PARTITION_GROWFS = 1 << 3,
|
||||
PARTITION_UUID = 1 << 4,
|
||||
PARTITION_LABEL = 1 << 5,
|
||||
_PARTITION_CHANGE_MAX = (1 << 6) - 1, /* all of the above */
|
||||
PARTITION_TYPE = 1 << 6,
|
||||
_PARTITION_CHANGE_MAX = (1 << 7) - 1, /* all of the above */
|
||||
_PARTITION_CHANGE_INVALID = -EINVAL,
|
||||
} PartitionChange;
|
||||
|
||||
@ -40,5 +41,8 @@ int partition_info_copy(PartitionInfo *dest, const PartitionInfo *src);
|
||||
|
||||
int read_partition_info(struct fdisk_context *c, struct fdisk_table *t, size_t i, PartitionInfo *ret);
|
||||
|
||||
int gpt_partition_type_uuid_for_sysupdate_partial(sd_id128_t type, sd_id128_t *ret);
|
||||
int gpt_partition_type_uuid_for_sysupdate_pending(sd_id128_t type, sd_id128_t *ret);
|
||||
|
||||
int find_suitable_partition(const char *device, uint64_t space, sd_id128_t *partition_type, PartitionInfo *ret);
|
||||
int patch_partition(const char *device, const PartitionInfo *info, PartitionChange change);
|
||||
|
||||
@ -31,6 +31,7 @@
|
||||
#include "strv.h"
|
||||
#include "sysupdate-cache.h"
|
||||
#include "sysupdate-instance.h"
|
||||
#include "sysupdate-partition.h"
|
||||
#include "sysupdate-pattern.h"
|
||||
#include "sysupdate-resource.h"
|
||||
#include "time-util.h"
|
||||
@ -244,9 +245,7 @@ static int resource_load_from_blockdev(Resource *rr) {
|
||||
_cleanup_(instance_metadata_destroy) InstanceMetadata extracted_fields = INSTANCE_METADATA_NULL;
|
||||
_cleanup_(partition_info_destroy) PartitionInfo pinfo = PARTITION_INFO_NULL;
|
||||
Instance *instance;
|
||||
const char *pinfo_label_stripped;
|
||||
bool is_partial = false, is_pending = false;
|
||||
const char *stripped;
|
||||
|
||||
r = read_partition_info(c, t, i, &pinfo);
|
||||
if (r < 0)
|
||||
@ -254,9 +253,28 @@ static int resource_load_from_blockdev(Resource *rr) {
|
||||
if (r == 0) /* not assigned */
|
||||
continue;
|
||||
|
||||
/* Check if partition type matches */
|
||||
if (rr->partition_type_set && !sd_id128_equal(pinfo.type, rr->partition_type.uuid))
|
||||
continue;
|
||||
/* Check if partition type matches, either directly or via derived partial/pending type
|
||||
* UUIDs. The derived UUIDs are computed from the configured partition type by hashing it
|
||||
* with a fixed app-specific ID, so we can detect the state without relying on label
|
||||
* prefixes. */
|
||||
if (rr->partition_type_set) {
|
||||
sd_id128_t partial_type, pending_type;
|
||||
|
||||
r = gpt_partition_type_uuid_for_sysupdate_partial(rr->partition_type.uuid, &partial_type);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to derive partial partition type UUID: %m");
|
||||
|
||||
r = gpt_partition_type_uuid_for_sysupdate_pending(rr->partition_type.uuid, &pending_type);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to derive pending partition type UUID: %m");
|
||||
|
||||
if (sd_id128_equal(pinfo.type, partial_type))
|
||||
is_partial = true;
|
||||
else if (sd_id128_equal(pinfo.type, pending_type))
|
||||
is_pending = true;
|
||||
else if (!sd_id128_equal(pinfo.type, rr->partition_type.uuid))
|
||||
continue;
|
||||
}
|
||||
|
||||
/* A label of "_empty" means "not used so far" for us */
|
||||
if (streq_ptr(pinfo.label, "_empty")) {
|
||||
@ -264,18 +282,7 @@ static int resource_load_from_blockdev(Resource *rr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Match the label with any partial/pending prefix removed so the user’s existing patterns
|
||||
* match regardless of the instance’s state. */
|
||||
if ((stripped = startswith(pinfo.label, "PRT#"))) {
|
||||
pinfo_label_stripped = stripped;
|
||||
is_partial = true;
|
||||
} else if ((stripped = startswith(pinfo.label, "PND#"))) {
|
||||
pinfo_label_stripped = stripped;
|
||||
is_pending = true;
|
||||
} else
|
||||
pinfo_label_stripped = pinfo.label;
|
||||
|
||||
r = pattern_match_many(rr->patterns, pinfo_label_stripped, &extracted_fields);
|
||||
r = pattern_match_many(rr->patterns, pinfo.label, &extracted_fields);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to match pattern: %m");
|
||||
if (IN_SET(r, PATTERN_MATCH_NO, PATTERN_MATCH_RETRY))
|
||||
|
||||
@ -67,8 +67,6 @@ Transfer* transfer_free(Transfer *t) {
|
||||
strv_free(t->appstream);
|
||||
|
||||
partition_info_destroy(&t->partition_info);
|
||||
free(t->temporary_partial_partition_label);
|
||||
free(t->temporary_pending_partition_label);
|
||||
free(t->final_partition_label);
|
||||
|
||||
resource_destroy(&t->source);
|
||||
@ -766,12 +764,21 @@ static int transfer_instance_vacuum(
|
||||
|
||||
case RESOURCE_PARTITION: {
|
||||
PartitionInfo pinfo = instance->partition_info;
|
||||
PartitionChange change = PARTITION_LABEL;
|
||||
|
||||
/* label "_empty" means "no contents" for our purposes */
|
||||
pinfo.label = (char*) "_empty";
|
||||
|
||||
log_debug("Relabelling partition '%s' to '%s'.", pinfo.device, pinfo.label);
|
||||
r = patch_partition(t->target.path, &pinfo, PARTITION_LABEL);
|
||||
/* If the partition had a derived partial/pending type UUID, restore the original
|
||||
* partition type so that the slot is properly recognized as empty in subsequent
|
||||
* scans. */
|
||||
if ((instance->is_partial || instance->is_pending) && t->target.partition_type_set) {
|
||||
pinfo.type = t->target.partition_type.uuid;
|
||||
change |= PARTITION_TYPE;
|
||||
}
|
||||
|
||||
log_debug("Resetting partition '%s' to empty.", pinfo.device);
|
||||
r = patch_partition(t->target.path, &pinfo, change);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@ -1172,7 +1179,7 @@ static int run_callout(
|
||||
* and pending instances which are about to be installed (in which case, transfer_acquire_instance() is
|
||||
* skipped). */
|
||||
int transfer_compute_temporary_paths(Transfer *t, Instance *i, InstanceMetadata *f) {
|
||||
_cleanup_free_ char *formatted_pattern = NULL, *formatted_partial_pattern = NULL, *formatted_pending_pattern = NULL;
|
||||
_cleanup_free_ char *formatted_pattern = NULL;
|
||||
int r;
|
||||
|
||||
assert(t);
|
||||
@ -1182,8 +1189,6 @@ int transfer_compute_temporary_paths(Transfer *t, Instance *i, InstanceMetadata
|
||||
assert(!t->temporary_partial_path);
|
||||
assert(!t->temporary_pending_path);
|
||||
assert(!t->final_partition_label);
|
||||
assert(!t->temporary_partial_partition_label);
|
||||
assert(!t->temporary_pending_partition_label);
|
||||
assert(!strv_isempty(t->target.patterns));
|
||||
|
||||
/* Format the target name using the first pattern specified */
|
||||
@ -1234,25 +1239,18 @@ int transfer_compute_temporary_paths(Transfer *t, Instance *i, InstanceMetadata
|
||||
if (!r)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as GPT partition label, refusing: %s", formatted_pattern);
|
||||
|
||||
if (!strprepend(&formatted_partial_pattern, "PRT#", formatted_pattern))
|
||||
return log_oom();
|
||||
r = gpt_partition_label_valid(formatted_partial_pattern);
|
||||
if (!t->target.partition_type_set)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Partition type must be set for partition targets.");
|
||||
|
||||
/* Derive temporary partition type UUIDs for partial/pending states from the configured
|
||||
* partition type. This avoids the need for label prefixes. */
|
||||
r = gpt_partition_type_uuid_for_sysupdate_partial(t->target.partition_type.uuid, &t->partition_type_partial);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to determine if formatted pattern is suitable as GPT partition label: %s", formatted_partial_pattern);
|
||||
if (!r)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as GPT partition label, refusing: %s", formatted_partial_pattern);
|
||||
return log_error_errno(r, "Failed to derive partial partition type UUID: %m");
|
||||
|
||||
free_and_replace(t->temporary_partial_partition_label, formatted_partial_pattern);
|
||||
|
||||
if (!strprepend(&formatted_pending_pattern, "PND#", formatted_pattern))
|
||||
return log_oom();
|
||||
r = gpt_partition_label_valid(formatted_pending_pattern);
|
||||
r = gpt_partition_type_uuid_for_sysupdate_pending(t->target.partition_type.uuid, &t->partition_type_pending);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to determine if formatted pattern is suitable as GPT partition label: %s", formatted_pending_pattern);
|
||||
if (!r)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as GPT partition label, refusing: %s", formatted_pending_pattern);
|
||||
|
||||
free_and_replace(t->temporary_pending_partition_label, formatted_pending_pattern);
|
||||
return log_error_errno(r, "Failed to derive pending partition type UUID: %m");
|
||||
|
||||
t->final_partition_label = TAKE_PTR(formatted_pattern);
|
||||
}
|
||||
@ -1312,13 +1310,18 @@ int transfer_acquire_instance(Transfer *t, Instance *i, InstanceMetadata *f, Tra
|
||||
|
||||
where = t->partition_info.device;
|
||||
|
||||
/* Rename the partition to `PRT#<VERSION>` to indicate that a transfer to it is in progress. */
|
||||
r = free_and_strdup_warn(&t->partition_info.label, t->temporary_partial_partition_label);
|
||||
/* Set the partition label and change the partition type to the derived "partial" type UUID
|
||||
* to indicate that a transfer to it is in progress. */
|
||||
r = free_and_strdup_warn(&t->partition_info.label, t->final_partition_label);
|
||||
if (r < 0)
|
||||
return r;
|
||||
t->partition_change = PARTITION_LABEL;
|
||||
t->partition_info.type = t->partition_type_partial;
|
||||
t->partition_change = PARTITION_LABEL | PARTITION_TYPE;
|
||||
|
||||
log_debug("Relabelling partition '%s' to '%s'.", t->partition_info.device, t->partition_info.label);
|
||||
log_debug("Marking partition '%s' as partial (label='%s', type=%s).",
|
||||
t->partition_info.device,
|
||||
t->partition_info.label,
|
||||
SD_ID128_TO_UUID_STRING(t->partition_info.type));
|
||||
r = patch_partition(
|
||||
t->target.path,
|
||||
&t->partition_info,
|
||||
@ -1545,12 +1548,10 @@ int transfer_acquire_instance(Transfer *t, Instance *i, InstanceMetadata *f, Tra
|
||||
}
|
||||
|
||||
if (t->target.type == RESOURCE_PARTITION) {
|
||||
/* Now rename the partition again to `PND#<VERSION>` to indicate that the acquire is complete
|
||||
* and the partition is ready for install. */
|
||||
r = free_and_strdup_warn(&t->partition_info.label, t->temporary_pending_partition_label);
|
||||
if (r < 0)
|
||||
return r;
|
||||
t->partition_change = PARTITION_LABEL;
|
||||
/* Now change the partition type to the derived "pending" type UUID to indicate that the
|
||||
* acquire is complete and the partition is ready for install. */
|
||||
t->partition_info.type = t->partition_type_pending;
|
||||
t->partition_change = PARTITION_TYPE;
|
||||
|
||||
if (f->partition_uuid_set) {
|
||||
t->partition_info.uuid = f->partition_uuid;
|
||||
@ -1577,7 +1578,9 @@ int transfer_acquire_instance(Transfer *t, Instance *i, InstanceMetadata *f, Tra
|
||||
t->partition_change |= PARTITION_GROWFS;
|
||||
}
|
||||
|
||||
log_debug("Relabelling partition '%s' to '%s'.", t->partition_info.device, t->partition_info.label);
|
||||
log_debug("Marking partition '%s' as pending (type=%s).",
|
||||
t->partition_info.device,
|
||||
SD_ID128_TO_UUID_STRING(t->partition_info.type));
|
||||
r = patch_partition(
|
||||
t->target.path,
|
||||
&t->partition_info,
|
||||
@ -1617,7 +1620,7 @@ int transfer_process_partial_and_pending_instance(Transfer *t, Instance *i) {
|
||||
|
||||
/* This is the analogue of find_suitable_partition(), but since finding the suitable partition has
|
||||
* already happened in the acquire phase, the target should already have that information and it
|
||||
* should already have been claimed as `PND#`. */
|
||||
* should already have been claimed with the pending partition type UUID. */
|
||||
if (t->target.type == RESOURCE_PARTITION) {
|
||||
assert(i->resource == &t->target);
|
||||
assert(i->is_pending);
|
||||
@ -1665,14 +1668,17 @@ int transfer_install_instance(
|
||||
t->temporary_pending_path = mfree(t->temporary_pending_path);
|
||||
}
|
||||
|
||||
if (t->temporary_pending_partition_label) {
|
||||
if (t->final_partition_label) {
|
||||
assert(t->target.type == RESOURCE_PARTITION);
|
||||
assert(t->final_partition_label);
|
||||
assert(t->target.partition_type_set);
|
||||
|
||||
r = free_and_strdup_warn(&t->partition_info.label, t->final_partition_label);
|
||||
if (r < 0)
|
||||
return r;
|
||||
t->partition_change = PARTITION_LABEL;
|
||||
|
||||
/* Restore the original partition type UUID now that the partition is fully installed. */
|
||||
t->partition_info.type = t->target.partition_type.uuid;
|
||||
t->partition_change = PARTITION_LABEL | PARTITION_TYPE;
|
||||
|
||||
r = patch_partition(
|
||||
t->target.path,
|
||||
|
||||
@ -48,8 +48,11 @@ typedef struct Transfer {
|
||||
PartitionInfo partition_info;
|
||||
PartitionChange partition_change;
|
||||
char *final_partition_label;
|
||||
char *temporary_partial_partition_label;
|
||||
char *temporary_pending_partition_label;
|
||||
|
||||
/* Derived partition type UUIDs used to indicate partial/pending state on the partition type level,
|
||||
* instead of polluting the partition label with prefixes */
|
||||
sd_id128_t partition_type_partial;
|
||||
sd_id128_t partition_type_pending;
|
||||
|
||||
Context *context;
|
||||
} Transfer;
|
||||
|
||||
@ -405,6 +405,11 @@ executables += [
|
||||
'dependencies' : libopenssl,
|
||||
'conditions' : ['HAVE_OPENSSL'],
|
||||
},
|
||||
test_template + {
|
||||
'sources' : files('test-pkcs7-util.c'),
|
||||
'dependencies' : libopenssl,
|
||||
'conditions' : ['HAVE_OPENSSL'],
|
||||
},
|
||||
test_template + {
|
||||
'sources' : files('test-parse-util.c'),
|
||||
'dependencies' : libm,
|
||||
|
||||
55
src/test/test-pkcs7-util.c
Normal file
55
src/test/test-pkcs7-util.c
Normal file
@ -0,0 +1,55 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "hexdecoct.h"
|
||||
#include "iovec-util.h"
|
||||
#include "pkcs7-util.h"
|
||||
#include "tests.h"
|
||||
|
||||
TEST(pkcs7_extract_signers) {
|
||||
|
||||
const char tsig[] =
|
||||
"MIIEgwYJKoZIhvcNAQcCoIIEdDCCBHACAQExDzANBglghkgBZQMEAgEFADALBgkqhkiG9w0BBwGgggLp"
|
||||
"MIIC5TCCAc2gAwIBAgIURlvlj5ak0ZhvNS8hENNKwVv60x0wDQYJKoZIhvcNAQELBQAwGzEZMBcGA1UE"
|
||||
"AwwQbWtvc2kgb2YgbGVubmFydDAeFw0yNTAyMDMxMTAwMjNaFw0yNzAyMDMxMTAwMjNaMBsxGTAXBgNV"
|
||||
"BAMMEG1rb3NpIG9mIGxlbm5hcnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCldQdmHVgU"
|
||||
"m4saaSqEpF1EVRf9pcIxpVShROBGAbxpxs4BQ7vV7zCg5bXwEVaqaENlIyzqPAj+YQifQS3Dj6LQfH3i"
|
||||
"War+ciKAv4PYcESG+pcdBb2kJOS8cVD6abZlO9rInvdhXK6PhYF7VohMwMPj/yJYdO50skwA5OQsCHO6"
|
||||
"amowh0tVzNzpbaJZg6wWIyTf3+ZzZdnOl5EvBCaUqeUhbaxRV9SrAw51rzAOnndUhW1we/vVKEGAPk3c"
|
||||
"SCb0LLPBG20XX2C00UXgnCWBkU5rkq6BnWSuInhFKCa48avstIet1ZFBA5T5J83ncU9UOVFksEYBXFoc"
|
||||
"lzcmcbx/2/oFAgMBAAGjITAfMB0GA1UdDgQWBBQewILFCixwq2rejOvJqmZSug2BBDANBgkqhkiG9w0B"
|
||||
"AQsFAAOCAQEAIvaeNPaJUoIUN5lQC/kcCiKeys96WNRGL2wbTp5PqdnRw14sbWY5iC2z13ih3dmTI9NF"
|
||||
"TBa7C/ji+5BaAfiJF17LOV00Y8eP5V94fHz4isb0sv5RzLsE4h8X7QFk4JBdV5GiCDzPXjxQAx9kM2so"
|
||||
"9RGtL8EhHpNygYDgyZ18YeiwcUPkCXT+xG2rM6s/Xlsji0s/18ycI4G8AC8dj5HycyS9BiZHgKrkgqTb"
|
||||
"VPo4zHYzhZdh0Qrd0J4YpoaotzQ35bkH9PtIkF6C7mE1Z7uMSGFkGQASgJ0BDTpM8QPAf2HIR2xxEtJR"
|
||||
"ZXkwxxdC+W9AJAzqJldmCHYGSrSR54J0rDGCAV4wggFaAgEBMDMwGzEZMBcGA1UEAwwQbWtvc2kgb2Yg"
|
||||
"bGVubmFydAIURlvlj5ak0ZhvNS8hENNKwVv60x0wDQYJYIZIAWUDBAIBBQAwDQYJKoZIhvcNAQEBBQAE"
|
||||
"ggEAXccqvpiEWsz/xvuLhINVZKIOznVdqjkERbZSqCBK94BYESSd+cijaB4XbYaFUZ45Bb3uUDQ56Ojq"
|
||||
"WoY1elEfqPyCb4vc887QoHmxI0BtdIaHhIDfCGBxhX8fwMknxqjgFa9YvONmDtv4QG4syTw+U3SEqBaa"
|
||||
"Avftqaa4v4eLk4uZ0nMIgMkx4qOlaxknpP404/nyZPANkOIwDxviNtRBCN9zSiPSqo1zre1vqzaM57Ww"
|
||||
"8zJASsPEzNR7OsPoLaIZv2OHXpowsRB78TuXGkQnm74T6xdG6DNs24jTYJuCPfGuYLHbrytdhXpFBS6m"
|
||||
"Orz9715jK2NU5VvGhNVXX4chcw==";
|
||||
|
||||
_cleanup_free_ void *sig = NULL;
|
||||
size_t siglen;
|
||||
ASSERT_OK(unbase64mem(tsig, &sig, &siglen));
|
||||
|
||||
size_t n_signers = 0;
|
||||
Signer *signers = NULL;
|
||||
CLEANUP_ARRAY(signers, n_signers, signer_free_many);
|
||||
|
||||
ASSERT_OK_EQ(pkcs7_extract_signers(&IOVEC_MAKE(sig, siglen), &signers, &n_signers), 1);
|
||||
ASSERT_EQ(n_signers, 1U);
|
||||
ASSERT_EQ(signers[0].issuer.iov_len, 29U);
|
||||
ASSERT_EQ(signers[0].serial.iov_len, 22U);
|
||||
|
||||
_cleanup_free_ char *issuer = NULL;
|
||||
ASSERT_OK(base64mem(signers[0].issuer.iov_base, signers[0].issuer.iov_len, &issuer));
|
||||
|
||||
_cleanup_free_ char *serial = NULL;
|
||||
ASSERT_OK(base64mem(signers[0].serial.iov_base, signers[0].serial.iov_len, &serial));
|
||||
|
||||
ASSERT_STREQ(issuer, "MBsxGTAXBgNVBAMMEG1rb3NpIG9mIGxlbm5hcnQ=");
|
||||
ASSERT_STREQ(serial, "AhRGW+WPlqTRmG81LyEQ00rBW/rTHQ==");
|
||||
}
|
||||
|
||||
DEFINE_TEST_MAIN(LOG_INFO);
|
||||
@ -31,12 +31,12 @@ executables += [
|
||||
'HAVE_TPM2',
|
||||
],
|
||||
},
|
||||
|
||||
]
|
||||
|
||||
if conf.get('ENABLE_BOOTLOADER') == 1 and conf.get('HAVE_OPENSSL') == 1 and conf.get('HAVE_TPM2') == 1
|
||||
nvpcrs = [ 'cryptsetup',
|
||||
'hardware' ]
|
||||
'hardware',
|
||||
'verity']
|
||||
foreach n : nvpcrs
|
||||
custom_target(
|
||||
input : 'nvpcr/' + n + '.nvpcr.in',
|
||||
|
||||
5
src/tpm2-setup/nvpcr/verity.nvpcr.in
Normal file
5
src/tpm2-setup/nvpcr/verity.nvpcr.in
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"name" : "verity",
|
||||
"algorithm" : "sha256",
|
||||
"nvIndex" : {{TPM2_NVPCR_BASE + 2}}
|
||||
}
|
||||
@ -16,9 +16,11 @@
|
||||
#include "main-func.h"
|
||||
#include "parse-util.h"
|
||||
#include "path-util.h"
|
||||
#include "pcrextend-util.h"
|
||||
#include "pretty-print.h"
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
#include "tpm2-util.h"
|
||||
#include "verbs.h"
|
||||
|
||||
static char *arg_hash = NULL; /* the hash algorithm */
|
||||
@ -38,12 +40,14 @@ static uint64_t arg_fec_roots = 2;
|
||||
static void *arg_root_hash_signature = NULL;
|
||||
static size_t arg_root_hash_signature_size = 0;
|
||||
static bool arg_root_hash_signature_auto = false;
|
||||
static char *arg_tpm2_measure_nvpcr = NULL;
|
||||
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_hash, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_salt, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_uuid, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_fec_what, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_root_hash_signature, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_measure_nvpcr, freep);
|
||||
|
||||
static int help(void) {
|
||||
_cleanup_free_ char *link = NULL;
|
||||
@ -142,7 +146,7 @@ static int parse_options(const char *options) {
|
||||
|
||||
for (;;) {
|
||||
_cleanup_free_ char *word = NULL;
|
||||
char *val;
|
||||
const char *val;
|
||||
|
||||
r = extract_first_word(&options, &word, ",", EXTRACT_DONT_COALESCE_SEPARATORS | EXTRACT_UNESCAPE_SEPARATORS);
|
||||
if (r < 0)
|
||||
@ -290,6 +294,21 @@ static int parse_options(const char *options) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
} else if ((val = startswith(word, "tpm2-measure-nvpcr="))) {
|
||||
r = isempty(val) ? 0 : parse_boolean(val);
|
||||
if (r == 0) {
|
||||
arg_tpm2_measure_nvpcr = mfree(arg_tpm2_measure_nvpcr);
|
||||
return 0;
|
||||
}
|
||||
if (r > 0)
|
||||
val = "verity";
|
||||
else if (!tpm2_nvpcr_name_is_valid(val)) {
|
||||
log_warning("Invalid NvPCR name, ignoring: %s", word);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (free_and_strdup(&arg_tpm2_measure_nvpcr, val) < 0)
|
||||
return log_oom();
|
||||
} else
|
||||
log_warning("Encountered unknown option '%s', ignoring.", word);
|
||||
}
|
||||
@ -423,6 +442,11 @@ static int verb_attach(int argc, char *argv[], void *userdata) {
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set up verity device '%s': %m", volume);
|
||||
|
||||
(void) pcrextend_verity_now(
|
||||
volume,
|
||||
&IOVEC_MAKE(rh, rh_size),
|
||||
&IOVEC_MAKE(arg_root_hash_signature, arg_root_hash_signature_size));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@ -20,7 +20,8 @@ at_exit() {
|
||||
jq --seq --slurp </run/log/systemd/tpm2-measure.log
|
||||
fi
|
||||
|
||||
rm -rf /run/nvpcr
|
||||
rm -rf /run/nvpcr /tmp/nvpcr
|
||||
rm -f /var/tmp/nvpcr.raw /run/verity.d/test-79-nvpcr.crt
|
||||
}
|
||||
|
||||
trap at_exit EXIT
|
||||
@ -52,3 +53,46 @@ test "$DIGEST_ACTUAL2" != "$DIGEST_EXPECTED"
|
||||
DIGEST_MEASURED2="$(echo -n "schnurz" | openssl dgst -sha256 -hex -r | cut -d' ' -f1)"
|
||||
DIGEST_EXPECTED2="$(echo "$DIGEST_EXPECTED$DIGEST_MEASURED2" | tr '[:lower:]' '[:upper:]' | basenc --base16 -d | openssl dgst -sha256 -hex -r | cut -d' ' -f1)"
|
||||
test "$DIGEST_ACTUAL2" = "$DIGEST_EXPECTED2"
|
||||
|
||||
mkdir /tmp/nvpcr
|
||||
|
||||
OPENSSL_CONFIG="/tmp/nvpcr/opensslconfig"
|
||||
# Unfortunately OpenSSL insists on reading some config file, hence provide one with mostly placeholder contents
|
||||
cat >"${OPENSSL_CONFIG:?}" <<EOF
|
||||
[ req ]
|
||||
prompt = no
|
||||
distinguished_name = req_distinguished_name
|
||||
|
||||
[ req_distinguished_name ]
|
||||
C = DE
|
||||
ST = Test State
|
||||
L = Test Locality
|
||||
O = Org Name
|
||||
OU = Org Unit Name
|
||||
CN = Common Name
|
||||
emailAddress = test@email.com
|
||||
EOF
|
||||
|
||||
openssl req -config "$OPENSSL_CONFIG" -subj="/CN=waldo" \
|
||||
-x509 -sha256 -nodes -days 365 -newkey rsa:4096 \
|
||||
-keyout /tmp/nvpcr/test-70-nvpcr.key -out /tmp/nvpcr/test-70-nvpcr.crt
|
||||
|
||||
mkdir /tmp/nvpcr/tree
|
||||
touch /tmp/nvpcr/tree/file
|
||||
|
||||
SYSTEMD_REPART_OVERRIDE_FSTYPE=squashfs \
|
||||
systemd-repart -P \
|
||||
-s /tmp/nvpcr/tree \
|
||||
--certificate=/tmp/nvpcr/test-70-nvpcr.crt \
|
||||
--private-key=/tmp/nvpcr/test-70-nvpcr.key \
|
||||
/var/tmp/nvpcr.raw
|
||||
|
||||
mkdir -p /run/verity.d
|
||||
cp /tmp/nvpcr/test-70-nvpcr.crt /run/verity.d/
|
||||
|
||||
cp /run/log/systemd/tpm2-measure.log /tmp/nvpcr/log-before
|
||||
|
||||
systemd-dissect --image-policy='root=signed:=absent+unused' --mtree /var/tmp/nvpcr.raw
|
||||
|
||||
set +o pipefail
|
||||
diff /tmp/nvpcr/log-before /run/log/systemd/tpm2-measure.log | grep -F '"content":{"nvIndexName":"verity","string":"verity:'
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user