mirror of
https://github.com/systemd/systemd.git
synced 2026-08-09 01:20:53 +00:00
cryptsetup: measure volume key and keyslot via the pcrextend Varlink service (#43109)
Motivated by https://github.com/systemd/systemd/pull/43041#discussion_r3595022610. Switch systemd-cryptsetup's volume-key and keyslot measurements from driving the TPM directly (tpm2-util) to the io.systemd.PCRExtend Varlink service, aligning it with how the verity and imds measurements already work. Some notes on decisions taken: - The volume key is sent over the wire. systemd-pcrextend will do the hmac. The socket is root only. Otherwise we would need to do bank negotiation via varlink and pollute the interface with it. - `tpm2-measure-bank=` deprecated/dropped. Same reason as above. - `tpm2-device=` now only affects unlocking. The device for measurements is selected by pcrextend. - Measuring requires the presence of `systemd-pcrextend.socket` in the initrd, should be already given as systemd-veritysetup relies on it, too. - Logs are done on the pcrextend side.
This commit is contained in:
6
NEWS
6
NEWS
@@ -31,6 +31,12 @@ CHANGES WITH 262:
|
||||
older systemd versions, however systemd continues to accept TPM-sealed
|
||||
credentials that were created before this change.
|
||||
|
||||
* The "tpm2-measure-bank=" crypttab option of systemd-cryptsetup is
|
||||
now deprecated and has no effect. Volume key measurements are
|
||||
performed via varlink call to systemd-pcrextend, which automatically
|
||||
selects all suitable TPM2 PCR banks; the banks can no longer be
|
||||
restricted per volume.
|
||||
|
||||
* The legacy socket for controlling systemd-udevd has been removed, and
|
||||
udevadm now unconditionally uses Varlink IPC. The legacy UNIX socket
|
||||
/run/udev/control, which is sometimes used to check whether udevd is
|
||||
|
||||
@@ -939,11 +939,8 @@
|
||||
<varlistentry>
|
||||
<term><option>tpm2-measure-bank=</option></term>
|
||||
|
||||
<listitem><para>Selects one or more TPM2 PCR banks to measure the volume key into, as configured with
|
||||
<option>tpm2-measure-pcr=</option> above. Multiple banks may be specified, separated by a colon
|
||||
character. If not specified, automatically determines available and used banks. Expects a message
|
||||
digest name (e.g. <literal>sha1</literal>, <literal>sha256</literal>, …) as argument, to identify the
|
||||
bank.</para>
|
||||
<listitem><para>This option is deprecated and has no effect since version 262. All suitable PCR
|
||||
banks are used.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v253"/></listitem>
|
||||
</varlistentry>
|
||||
|
||||
@@ -7,12 +7,10 @@
|
||||
#include "sd-device.h"
|
||||
#include "sd-event.h"
|
||||
#include "sd-json.h"
|
||||
#include "sd-messages.h"
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "ask-password-api.h"
|
||||
#include "build.h"
|
||||
#include "crypto-util.h"
|
||||
#include "cryptsetup-fido2.h"
|
||||
#include "cryptsetup-keyfile.h"
|
||||
#include "cryptsetup-pkcs11.h"
|
||||
@@ -40,6 +38,7 @@
|
||||
#include "options.h"
|
||||
#include "parse-util.h"
|
||||
#include "path-util.h"
|
||||
#include "pcrextend-util.h"
|
||||
#include "pkcs11-util.h"
|
||||
#include "pretty-print.h"
|
||||
#include "process-util.h"
|
||||
@@ -123,7 +122,6 @@ static bool arg_tpm2_pin = false;
|
||||
static char *arg_tpm2_pcrlock = NULL;
|
||||
static usec_t arg_token_timeout_usec = 30*USEC_PER_SEC;
|
||||
static unsigned arg_tpm2_measure_pcr = UINT_MAX; /* This and the following field is about measuring the unlocked volume key to the local TPM */
|
||||
static char **arg_tpm2_measure_banks = NULL;
|
||||
static char *arg_tpm2_measure_keyslot_nvpcr = NULL;
|
||||
static char *arg_link_keyring = NULL;
|
||||
static char *arg_link_key_type = NULL;
|
||||
@@ -140,7 +138,6 @@ STATIC_DESTRUCTOR_REGISTER(arg_fido2_cid, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_fido2_rp_id, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_signature, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_measure_banks, strv_freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_measure_keyslot_nvpcr, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_pcrlock, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_link_keyring, freep);
|
||||
@@ -537,30 +534,9 @@ static int parse_one_option(const char *option) {
|
||||
|
||||
} else if ((val = startswith(option, "tpm2-measure-bank="))) {
|
||||
|
||||
#if HAVE_OPENSSL
|
||||
_cleanup_strv_free_ char **l = NULL;
|
||||
|
||||
r = dlopen_libcrypto(LOG_ERR);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
l = strv_split(val, ":");
|
||||
if (!l)
|
||||
return log_oom();
|
||||
|
||||
STRV_FOREACH(i, l) {
|
||||
const EVP_MD *implementation;
|
||||
|
||||
implementation = sym_EVP_get_digestbyname(*i);
|
||||
if (!implementation)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown bank '%s', refusing.", val);
|
||||
|
||||
if (strv_extend(&arg_tpm2_measure_banks, sym_EVP_MD_get0_name(implementation)) < 0)
|
||||
return log_oom();
|
||||
}
|
||||
#else
|
||||
log_error("Build lacks OpenSSL support, cannot measure to PCR banks, ignoring: %s", option);
|
||||
#endif
|
||||
/* Deprecated: the PCR banks to measure into are now chosen by systemd-pcrextend
|
||||
* (it extends all suitable banks). Kept for compatibility, but ignored. */
|
||||
log_warning("The tpm2-measure-bank= option is deprecated and has no effect, ignoring.");
|
||||
|
||||
} else if ((val = startswith(option, "tpm2-measure-keyslot-nvpcr="))) {
|
||||
|
||||
@@ -1041,57 +1017,21 @@ static int measure_volume_key(
|
||||
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)
|
||||
return r;
|
||||
|
||||
_cleanup_strv_free_ char **l = NULL;
|
||||
if (strv_isempty(arg_tpm2_measure_banks)) {
|
||||
r = tpm2_get_good_pcr_banks_strv(c, UINT32_C(1) << arg_tpm2_measure_pcr, &l);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Could not verify pcr banks: %m");
|
||||
}
|
||||
|
||||
_cleanup_free_ char *joined = strv_join(l ?: arg_tpm2_measure_banks, ", ");
|
||||
if (!joined)
|
||||
return log_oom();
|
||||
|
||||
/* Note: we don't directly measure the volume key, it might be a security problem to send an
|
||||
* unprotected direct hash of the secret volume key over the wire to the TPM. Hence let's instead
|
||||
* send a HMAC signature instead. */
|
||||
|
||||
_cleanup_free_ char *prefix = NULL;
|
||||
|
||||
/* Note: what is extended to the SHA256 bank here must match the expected hash of 'fixate-volume-key='
|
||||
* calculated by cryptsetup_get_volume_key_id(). */
|
||||
r = cryptsetup_get_volume_key_prefix(cd, name, &prefix);
|
||||
if (r)
|
||||
return log_error_errno(r, "Could not verify pcr banks: %m");
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to get volume key prefix: %m");
|
||||
|
||||
r = tpm2_pcr_extend_bytes(
|
||||
c,
|
||||
/* banks= */ l ?: arg_tpm2_measure_banks,
|
||||
/* pcr_index = */ arg_tpm2_measure_pcr,
|
||||
/* data = */ &IOVEC_MAKE_STRING(prefix),
|
||||
/* secret = */ &IOVEC_MAKE(volume_key, volume_key_size),
|
||||
/* event_type = */ TPM2_EVENT_VOLUME_KEY,
|
||||
/* description = */ prefix);
|
||||
/* Pass the volume key as HMAC secret. pcrextend extends HMAC(volume_key, prefix),
|
||||
* never a bare hash. Matches cryptsetup_get_volume_key_id(). */
|
||||
r = pcrextend_pcr_now(arg_tpm2_measure_pcr, prefix, &IOVEC_MAKE(volume_key, volume_key_size), "volume_key");
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Could not extend PCR: %m");
|
||||
|
||||
log_struct(LOG_INFO,
|
||||
LOG_MESSAGE_ID(SD_MESSAGE_TPM_PCR_EXTEND_STR),
|
||||
LOG_MESSAGE("Successfully extended PCR index %u with '%s' and volume key (banks %s).", arg_tpm2_measure_pcr, prefix, joined),
|
||||
LOG_ITEM("MEASURING=%s", prefix),
|
||||
LOG_ITEM("PCR=%u", arg_tpm2_measure_pcr),
|
||||
LOG_ITEM("BANKS=%s", joined));
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support disabled, not measuring volume key.");
|
||||
#endif
|
||||
}
|
||||
|
||||
static int measure_keyslot(
|
||||
@@ -1100,9 +1040,8 @@ static int measure_keyslot(
|
||||
const char *mechanism,
|
||||
int keyslot) {
|
||||
|
||||
#if HAVE_TPM2
|
||||
int r;
|
||||
#endif
|
||||
|
||||
assert(cd);
|
||||
assert(name);
|
||||
|
||||
@@ -1111,7 +1050,6 @@ static int measure_keyslot(
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if HAVE_TPM2
|
||||
r = efi_measured_os(LOG_WARNING);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@@ -1120,11 +1058,6 @@ static int measure_keyslot(
|
||||
return 0;
|
||||
}
|
||||
|
||||
_cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL;
|
||||
r = tpm2_context_new_or_warn(arg_tpm2_device, &c);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_free_ char *escaped = NULL;
|
||||
escaped = xescape(name, ":"); /* avoid ambiguity around ":" once we join things below */
|
||||
if (!escaped)
|
||||
@@ -1139,27 +1072,11 @@ static int measure_keyslot(
|
||||
if (!s)
|
||||
return log_oom();
|
||||
|
||||
r = tpm2_nvpcr_extend_bytes(
|
||||
c,
|
||||
/* session= */ NULL,
|
||||
arg_tpm2_measure_keyslot_nvpcr,
|
||||
&IOVEC_MAKE_STRING(s),
|
||||
/* secret= */ NULL,
|
||||
TPM2_EVENT_KEYSLOT,
|
||||
s);
|
||||
r = pcrextend_nvpcr_now(arg_tpm2_measure_keyslot_nvpcr, s, "keyslot");
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Could not extend NvPCR: %m");
|
||||
|
||||
log_struct(LOG_INFO,
|
||||
"MESSAGE_ID=" SD_MESSAGE_TPM_NVPCR_EXTEND_STR,
|
||||
LOG_MESSAGE("Successfully extended NvPCR index '%s' with '%s'.", arg_tpm2_measure_keyslot_nvpcr, s),
|
||||
"MEASURING=%s", s,
|
||||
"NVPCR=%s", arg_tpm2_measure_keyslot_nvpcr);
|
||||
|
||||
return 0;
|
||||
#else
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support disabled, not measuring keyslot.");
|
||||
#endif
|
||||
}
|
||||
|
||||
static int log_external_activation(int r, const char *volume) {
|
||||
|
||||
@@ -233,21 +233,22 @@ static int determine_banks(Tpm2Context *c, uint32_t target_pcr_mask) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int escape_and_truncate_data(const void *data, size_t size, char **ret) {
|
||||
static int escape_and_truncate_data(const struct iovec *data, char **ret) {
|
||||
_cleanup_free_ char *safe = NULL;
|
||||
|
||||
assert(data || size == 0);
|
||||
assert(data);
|
||||
assert(iovec_is_valid(data));
|
||||
assert(ret);
|
||||
|
||||
if (size > EXTENSION_STRING_SAFE_LIMIT) {
|
||||
safe = cescape_length(data, EXTENSION_STRING_SAFE_LIMIT);
|
||||
if (data->iov_len > EXTENSION_STRING_SAFE_LIMIT) {
|
||||
safe = cescape_length(data->iov_base, EXTENSION_STRING_SAFE_LIMIT);
|
||||
if (!safe)
|
||||
return -ENOMEM;
|
||||
|
||||
if (!strextend(&safe, "..."))
|
||||
return -ENOMEM;
|
||||
} else {
|
||||
safe = cescape_length(data, size);
|
||||
safe = cescape_length(data->iov_base, data->iov_len);
|
||||
if (!safe)
|
||||
return -ENOMEM;
|
||||
}
|
||||
@@ -300,8 +301,8 @@ static int tpm2_context_new_for_measurement(Tpm2Context **ret) {
|
||||
|
||||
static int extend_pcr_now(
|
||||
uint32_t pcr_mask,
|
||||
const void *data,
|
||||
size_t size,
|
||||
const struct iovec *data,
|
||||
const struct iovec *secret,
|
||||
Tpm2UserspaceEventType event) {
|
||||
|
||||
_cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL;
|
||||
@@ -325,13 +326,13 @@ static int extend_pcr_now(
|
||||
return log_oom();
|
||||
|
||||
_cleanup_free_ char *safe = NULL;
|
||||
if (escape_and_truncate_data(data, size, &safe) < 0)
|
||||
if (escape_and_truncate_data(data, &safe) < 0)
|
||||
return log_oom();
|
||||
|
||||
BIT_FOREACH(pcr, pcr_mask) {
|
||||
log_debug("Measuring '%s' into PCR index %i, banks %s.", safe, pcr, joined_banks);
|
||||
|
||||
r = tpm2_pcr_extend_bytes(c, arg_banks, pcr, &IOVEC_MAKE(data, size), /* secret= */ NULL, event, safe);
|
||||
r = tpm2_pcr_extend_bytes(c, arg_banks, pcr, data, secret, event, safe);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Could not extend PCR: %m");
|
||||
|
||||
@@ -348,8 +349,8 @@ static int extend_pcr_now(
|
||||
|
||||
static int extend_nvpcr_now(
|
||||
const char *name,
|
||||
const void *data,
|
||||
size_t size,
|
||||
const struct iovec *data,
|
||||
const struct iovec *secret,
|
||||
Tpm2UserspaceEventType event) {
|
||||
|
||||
_cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL;
|
||||
@@ -362,7 +363,7 @@ static int extend_nvpcr_now(
|
||||
return r;
|
||||
|
||||
_cleanup_free_ char *safe = NULL;
|
||||
if (escape_and_truncate_data(data, size, &safe) < 0)
|
||||
if (escape_and_truncate_data(data, &safe) < 0)
|
||||
return log_oom();
|
||||
|
||||
log_debug("Measuring '%s' into NvPCR index '%s'.", safe, name);
|
||||
@@ -371,8 +372,8 @@ static int extend_nvpcr_now(
|
||||
c,
|
||||
/* session= */ NULL,
|
||||
name,
|
||||
&IOVEC_MAKE(data, size),
|
||||
/* secret= */ NULL,
|
||||
data,
|
||||
secret,
|
||||
event,
|
||||
safe);
|
||||
if (r == -ENOBUFS)
|
||||
@@ -394,6 +395,7 @@ typedef struct MethodExtendParameters {
|
||||
const char *nvpcr;
|
||||
const char *text;
|
||||
struct iovec data;
|
||||
struct iovec secret;
|
||||
Tpm2UserspaceEventType event_type;
|
||||
} MethodExtendParameters;
|
||||
|
||||
@@ -401,6 +403,7 @@ static void method_extend_parameters_done(MethodExtendParameters *p) {
|
||||
assert(p);
|
||||
|
||||
iovec_done(&p->data);
|
||||
iovec_done_erase(&p->secret);
|
||||
}
|
||||
|
||||
static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_tpm2_userspace_event_type, Tpm2UserspaceEventType, tpm2_userspace_event_type_from_string);
|
||||
@@ -408,10 +411,11 @@ static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_tpm2_userspace_event_type, Tpm2Us
|
||||
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 },
|
||||
{ "secret", SD_JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(MethodExtendParameters, secret), 0 },
|
||||
{ "eventType", SD_JSON_VARIANT_STRING, json_dispatch_tpm2_userspace_event_type, offsetof(MethodExtendParameters, event_type), 0 },
|
||||
{}
|
||||
};
|
||||
@@ -457,13 +461,13 @@ static int vl_method_extend(sd_varlink *link, sd_json_variant *parameters, sd_va
|
||||
return sd_varlink_error_invalid_parameter_name(link, p.text ? "text" : "data");
|
||||
|
||||
if (p.nvpcr) {
|
||||
r = extend_nvpcr_now(p.nvpcr, extend_iovec->iov_base, extend_iovec->iov_len, p.event_type);
|
||||
r = extend_nvpcr_now(p.nvpcr, extend_iovec, &p.secret, p.event_type);
|
||||
if (IN_SET(r, -ENOENT, -ENODEV))
|
||||
return sd_varlink_error(link, "io.systemd.PCRExtend.NoSuchNvPCR", NULL);
|
||||
if (r == -ENOBUFS)
|
||||
return sd_varlink_error(link, "io.systemd.PCRExtend.NvPCRSpaceExhausted", NULL);
|
||||
} else
|
||||
r = extend_pcr_now(INDEX_TO_MASK(uint32_t, p.pcr), extend_iovec->iov_base, extend_iovec->iov_len, p.event_type);
|
||||
r = extend_pcr_now(INDEX_TO_MASK(uint32_t, p.pcr), extend_iovec, &p.secret, p.event_type);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
@@ -475,7 +479,7 @@ static int vl_server(void) {
|
||||
int r;
|
||||
|
||||
r = varlink_server_new(&varlink_server,
|
||||
SD_VARLINK_SERVER_ROOT_ONLY | SD_VARLINK_SERVER_MYSELF_ONLY,
|
||||
SD_VARLINK_SERVER_ROOT_ONLY | SD_VARLINK_SERVER_MYSELF_ONLY | SD_VARLINK_SERVER_INPUT_SENSITIVE,
|
||||
/* userdata= */ NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to allocate Varlink server: %m");
|
||||
@@ -594,9 +598,9 @@ static int run(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
if (arg_nvpcr_name)
|
||||
r = extend_nvpcr_now(arg_nvpcr_name, word, strlen(word), event);
|
||||
r = extend_nvpcr_now(arg_nvpcr_name, &IOVEC_MAKE(word, strlen(word)), NULL, event);
|
||||
else
|
||||
r = extend_pcr_now(arg_pcr_mask, word, strlen(word), event);
|
||||
r = extend_pcr_now(arg_pcr_mask, &IOVEC_MAKE(word, strlen(word)), NULL, event);
|
||||
/* Both extend paths report "TPM cannot be used for this measurement" (no PCR bank, missing crypto,
|
||||
* no TPM device — see tpm2_context_new_for_measurement()) as -EOPNOTSUPP. Under --graceful we skip
|
||||
* those rather than fail and block boot. Genuine faults keep their own errno and are never
|
||||
|
||||
@@ -25,6 +25,99 @@
|
||||
#include "tpm2-pcr.h"
|
||||
#include "user-record.h"
|
||||
|
||||
int pcrextend_pcr_now(unsigned pcr, const char *word, const struct iovec *secret, const char *event) {
|
||||
|
||||
#if HAVE_TPM2
|
||||
int r;
|
||||
|
||||
assert(word);
|
||||
|
||||
if (!secret)
|
||||
secret = &iovec_empty;
|
||||
|
||||
_cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
|
||||
r = sd_varlink_connect_address(&vl, "/run/systemd/io.systemd.PCRExtend");
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Build the parameters explicitly so that, when they carry the secret we can mark the
|
||||
* variant sensitive: this keeps the secret out of the debug log and ensures its heap
|
||||
* buffer is erased rather than merely freed. */
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *parameters = NULL;
|
||||
r = sd_json_buildo(
|
||||
¶meters,
|
||||
SD_JSON_BUILD_PAIR_INTEGER("pcr", pcr),
|
||||
SD_JSON_BUILD_PAIR_STRING("text", word),
|
||||
SD_JSON_BUILD_PAIR_CONDITION(iovec_is_set(secret),
|
||||
"secret",
|
||||
SD_JSON_BUILD_BASE64(secret->iov_base, secret->iov_len)),
|
||||
SD_JSON_BUILD_PAIR_STRING("eventType", event));
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to build io.systemd.PCRExtend.Extend() parameters: %m");
|
||||
|
||||
if (iovec_is_set(secret))
|
||||
sd_json_variant_sensitive(parameters);
|
||||
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *reply = NULL;
|
||||
const char *error_id = NULL;
|
||||
r = sd_varlink_call(vl, "io.systemd.PCRExtend.Extend", parameters, &reply, &error_id);
|
||||
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 PCR %u completed.", word, pcr);
|
||||
return 1;
|
||||
#else
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support disabled, not measuring.");
|
||||
#endif
|
||||
}
|
||||
|
||||
int pcrextend_nvpcr_now(const char *nvpcr, const char *word, const char *event) {
|
||||
|
||||
#if HAVE_TPM2
|
||||
int r;
|
||||
|
||||
assert(nvpcr);
|
||||
assert(word);
|
||||
|
||||
_cleanup_(sd_varlink_unrefp) 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",
|
||||
&reply,
|
||||
&error_id,
|
||||
SD_JSON_BUILD_PAIR_STRING("nvpcr", nvpcr),
|
||||
SD_JSON_BUILD_PAIR_STRING("text", word),
|
||||
SD_JSON_BUILD_PAIR_STRING("eventType", event));
|
||||
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 NvPCR '%s' completed.", word, nvpcr);
|
||||
return 1;
|
||||
#else
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support disabled, not measuring.");
|
||||
#endif
|
||||
}
|
||||
|
||||
static int device_get_file_system_word(
|
||||
sd_device *d,
|
||||
const char *prefix,
|
||||
@@ -305,7 +398,6 @@ int pcrextend_verity_now(
|
||||
const struct iovec *root_hash,
|
||||
const struct iovec *root_hash_sig) {
|
||||
|
||||
#if HAVE_TPM2
|
||||
int r;
|
||||
|
||||
_cleanup_free_ char *word = NULL;
|
||||
@@ -317,36 +409,7 @@ int pcrextend_verity_now(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_(sd_varlink_unrefp) 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 'verity' NvPCR completed.", word);
|
||||
return 1;
|
||||
#else
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support disabled, not measuring Verity root hashes and signatures.");
|
||||
#endif
|
||||
return pcrextend_nvpcr_now("verity", word, "dm_verity");
|
||||
}
|
||||
|
||||
#define IMDS_USERDATA_TRUNCATED_MAX 256U
|
||||
@@ -375,8 +438,6 @@ int pcrextend_imds_userdata_word(const struct iovec *data, char **ret) {
|
||||
}
|
||||
|
||||
int pcrextend_imds_userdata_now(const struct iovec *data) {
|
||||
|
||||
#if HAVE_TPM2
|
||||
int r;
|
||||
|
||||
_cleanup_free_ char *word = NULL;
|
||||
@@ -384,34 +445,5 @@ int pcrextend_imds_userdata_now(const struct iovec *data) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_(sd_varlink_unrefp) 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_INTEGER("pcr", TPM2_PCR_KERNEL_CONFIG),
|
||||
SD_JSON_BUILD_PAIR_STRING("text", word),
|
||||
SD_JSON_BUILD_PAIR_STRING("eventType", "imds_userdata"));
|
||||
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 PCR 12 completed.", word);
|
||||
return 1;
|
||||
#else
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support disabled, not measuring IMDS userdata.");
|
||||
#endif
|
||||
return pcrextend_pcr_now(TPM2_PCR_KERNEL_CONFIG, word, /* secret= */ NULL, "imds_userdata");
|
||||
}
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
|
||||
#include "forward.h"
|
||||
|
||||
int pcrextend_pcr_now(unsigned pcr, const char *word, const struct iovec *secret, const char *event);
|
||||
int pcrextend_nvpcr_now(const char *nvpcr, const char *word, const char *event);
|
||||
|
||||
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);
|
||||
|
||||
@@ -25,6 +25,8 @@ static SD_VARLINK_DEFINE_METHOD(
|
||||
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_FIELD_COMMENT("HMAC key, encoded in Base64. When set, the HMAC of the measured data keyed by this secret is extended rather than a plain hash."),
|
||||
SD_VARLINK_DEFINE_INPUT(secret, 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));
|
||||
|
||||
|
||||
111
test/units/TEST-70-TPM2.cryptsetup-measure.sh
Executable file
111
test/units/TEST-70-TPM2.cryptsetup-measure.sh
Executable file
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
set -eux
|
||||
set -o pipefail
|
||||
|
||||
# Tests that systemd-cryptsetup's tpm2-measure-pcr= option measures the volume key
|
||||
# through the io.systemd.PCRExtend Varlink service.
|
||||
#
|
||||
# Note: the tpm2-measure-keyslot-nvpcr= (NvPCR) path is intentionally not covered here. NvPCRs use a
|
||||
# signed PCR policy that must be anchored first (see TEST-70-TPM2.nvpcr.sh), which is heavy setup
|
||||
# orthogonal to what this test checks; the NvPCR extend-via-pcrextend path is already covered there.
|
||||
|
||||
# shellcheck source=test/units/util.sh
|
||||
. "$(dirname "$0")"/util.sh
|
||||
|
||||
export SYSTEMD_LOG_LEVEL=debug
|
||||
|
||||
if ! command -v systemd-cryptsetup >/dev/null || ! tpm_has_pcr sha256 15; then
|
||||
echo "systemd-cryptsetup or PCR 15 (sha256) not available, skipping cryptsetup measurement test"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
IMAGE=""
|
||||
|
||||
at_exit() {
|
||||
if [[ $? -ne 0 ]]; then
|
||||
# Dump the event log on failure, to ease debugging
|
||||
jq --seq --slurp </run/log/systemd/tpm2-measure.log || :
|
||||
fi
|
||||
|
||||
set +e
|
||||
|
||||
systemd-cryptsetup detach test-volume
|
||||
rm -rf /run/systemd/system/systemd-pcrextend.socket.d
|
||||
systemctl daemon-reload
|
||||
systemctl restart systemd-pcrextend.socket
|
||||
rm -f "${IMAGE:-}" /tmp/passphrase /tmp/vk /tmp/vk-hmac.bin \
|
||||
/tmp/oldpcr15 /tmp/newpcr15 /tmp/measure-bank.log
|
||||
}
|
||||
|
||||
trap at_exit EXIT
|
||||
|
||||
# The socket carries ConditionSecurity=measured-os, which does not hold in the
|
||||
# test VM, so the Varlink service would never activate. Drop the condition so
|
||||
# systemd-cryptsetup can reach it, exactly like TEST-70-TPM2.pcrextend.sh does.
|
||||
mkdir -p /run/systemd/system/systemd-pcrextend.socket.d
|
||||
cat >/run/systemd/system/systemd-pcrextend.socket.d/50-no-condition.conf <<EOF
|
||||
[Unit]
|
||||
ConditionSecurity=
|
||||
EOF
|
||||
systemctl daemon-reload
|
||||
systemctl restart systemd-pcrextend.socket
|
||||
|
||||
# Prepare a fresh LUKS2 volume unlocked by a passphrase keyfile
|
||||
IMAGE="$(mktemp /tmp/systemd-cryptsetup-measure-XXX.IMAGE)"
|
||||
truncate -s 20M "$IMAGE"
|
||||
echo -n passphrase >/tmp/passphrase
|
||||
chmod 0600 /tmp/passphrase
|
||||
cryptsetup luksFormat -q --pbkdf pbkdf2 --pbkdf-force-iterations 1000 --use-urandom "$IMAGE" /tmp/passphrase
|
||||
|
||||
UUID="$(cryptsetup luksUUID "$IMAGE")"
|
||||
|
||||
# Extract the raw volume key so we can recompute the expected HMAC below. The
|
||||
# volume key is measured as HMAC(volume_key, prefix) rather than a plain hash
|
||||
# (see cryptsetup_get_volume_key_prefix()/cryptsetup_get_volume_key_id()).
|
||||
cryptsetup luksDump -q --dump-volume-key --volume-key-file=/tmp/vk --key-file=/tmp/passphrase "$IMAGE"
|
||||
VK_HEX="$(od -An -v -tx1 /tmp/vk | tr -d ' \n')"
|
||||
|
||||
# prefix = "cryptsetup:" <escaped volume name> ":" <LUKS UUID>; "test-volume"
|
||||
# contains no ':' so it is unescaped.
|
||||
PREFIX="cryptsetup:test-volume:$UUID"
|
||||
|
||||
# Expected measured digest for the sha256 bank: HMAC-SHA256(volume_key, prefix)
|
||||
printf '%s' "$PREFIX" | openssl dgst -binary -sha256 -mac HMAC -macopt "hexkey:$VK_HEX" >/tmp/vk-hmac.bin
|
||||
DIGEST_EXPECTED="$(od -An -v -tx1 /tmp/vk-hmac.bin | tr -d ' \n')"
|
||||
|
||||
# Remember the current event log length so we can index the record we add.
|
||||
# (json-seq: each record is RS(0x1e)-prefixed, LF-suffixed.) The log is created
|
||||
# lazily on the first measurement, and this subtest may be the first thing to
|
||||
# measure (e.g. when run in isolation), so treat an absent log as zero records.
|
||||
RECORD_COUNT=0
|
||||
if [[ -f /run/log/systemd/tpm2-measure.log ]]; then
|
||||
RECORD_COUNT="$(jq --seq --slurp '. | length' </run/log/systemd/tpm2-measure.log | tr -d '\036')"
|
||||
fi
|
||||
|
||||
tpm2_pcrread sha256:15 -Q -o /tmp/oldpcr15
|
||||
|
||||
# Activate with measurement enabled. Unlock is via the keyfile (no TPM unlock),
|
||||
# to prove measurement is independent of the unlock mechanism.
|
||||
SYSTEMD_FORCE_MEASURE=1 systemd-cryptsetup attach test-volume "$IMAGE" /tmp/passphrase \
|
||||
tpm2-measure-pcr=15,headless=1
|
||||
|
||||
tpm2_pcrread sha256:15 -Q -o /tmp/newpcr15
|
||||
|
||||
# The TPM PCR 15 must have been extended with the HMAC:
|
||||
# new = sha256(old || HMAC(volume_key, prefix))
|
||||
diff /tmp/newpcr15 \
|
||||
<(cat /tmp/oldpcr15 /tmp/vk-hmac.bin | openssl dgst -binary -sha256)
|
||||
|
||||
# The volume-key measurement is the record we just added (the first one past RECORD_COUNT).
|
||||
test "$(jq --seq --slurp ".[$RECORD_COUNT].pcr" </run/log/systemd/tpm2-measure.log)" == "$(printf '\x1e15')"
|
||||
DIGEST_CURRENT="$(jq --seq --slurp --raw-output ".[$RECORD_COUNT].digests[] | select(.hashAlg == \"sha256\").digest" </run/log/systemd/tpm2-measure.log)"
|
||||
test "$DIGEST_CURRENT" == "$DIGEST_EXPECTED"
|
||||
|
||||
systemd-cryptsetup detach test-volume
|
||||
|
||||
# The deprecated tpm2-measure-bank= option must warn but not fail.
|
||||
SYSTEMD_FORCE_MEASURE=1 systemd-cryptsetup attach test-volume "$IMAGE" /tmp/passphrase \
|
||||
tpm2-measure-pcr=15,tpm2-measure-bank=sha256,headless=1 |& tee /tmp/measure-bank.log
|
||||
systemd-cryptsetup detach test-volume
|
||||
grep -i "deprecated" /tmp/measure-bank.log >/dev/null
|
||||
Reference in New Issue
Block a user