mirror of
https://github.com/systemd/systemd.git
synced 2025-01-26 14:04:03 +03:00
Merge pull request #23084 from poettering/creds-no-tpm2-fallback
creds: add semi-automatic fallback support for initrd credentials on systems lacking TPM2
This commit is contained in:
commit
f4bdbae725
6
TODO
6
TODO
@ -114,12 +114,6 @@ Features:
|
||||
- sd-stub: automatically pick up microcode from ESP (/loader/microcode/*)
|
||||
and synthesize initrd from it, and measure it. Signing is not necessary, as
|
||||
microcode does that on its own. Pass as first initrd to kernel.
|
||||
- systemd-creds should have a fallback logic that uses neither TPM nor the
|
||||
system key in /var for encryption and instead some fixed key. This should
|
||||
be opt in (since it provides no security properties) but be used by
|
||||
kernel-install when encrypting the creds it generates on systems that lack
|
||||
a TPM, so that we can have very similar codepaths on TPM and TPM-less
|
||||
systems. i.e. --with-key=tpm-graceful or so.
|
||||
- sd-stub should measure the kernel/initrd/… into a separate PCR, so that we
|
||||
have one PCR we can bind the encrypted creds to that is not effected by
|
||||
anything else but what we drop in via kernel-install, i.e. by earlier EFI
|
||||
|
@ -263,23 +263,32 @@
|
||||
<term><option>-H</option></term>
|
||||
<term><option>-T</option></term>
|
||||
|
||||
<listitem><para>When specified with the <command>encrypt</command> command controls the encryption
|
||||
key to use. Takes one of <literal>host</literal>, <literal>tpm2</literal>,
|
||||
<literal>host+tpm2</literal> or <literal>auto</literal>. See above for details on the three key
|
||||
types. If set to <literal>auto</literal> (which is the default) the TPM2 key is used if a TPM2 device
|
||||
is found and not running in a container. The host key is used if
|
||||
<filename>/var/lib/systemd/</filename> is on persistent media. This means on typical systems the
|
||||
encryption is by default bound to both the TPM2 chip and the OS installation, and both need to be
|
||||
available to decrypt the credential again. If <literal>auto</literal> is selected but neither TPM2 is
|
||||
available (or running in container) nor <filename>/var/lib/systemd/</filename> is on persistent
|
||||
media, encryption will fail.</para>
|
||||
<listitem><para>When specified with the <command>encrypt</command> command controls the
|
||||
encryption/signature key to use. Takes one of <literal>host</literal>, <literal>tpm2</literal>,
|
||||
<literal>host+tpm2</literal>, <literal>tpm2-absent</literal>, <literal>auto</literal>,
|
||||
<literal>auto-initrd</literal>. See above for details on the three key types. If set to
|
||||
<literal>auto</literal> (which is the default) the TPM2 key is used if a TPM2 device is found and not
|
||||
running in a container. The host key is used if <filename>/var/lib/systemd/</filename> is on
|
||||
persistent media. This means on typical systems the encryption is by default bound to both the TPM2
|
||||
chip and the OS installation, and both need to be available to decrypt the credential again. If
|
||||
<literal>auto</literal> is selected but neither TPM2 is available (or running in container) nor
|
||||
<filename>/var/lib/systemd/</filename> is on persistent media, encryption will fail. If set to
|
||||
<literal>tpm2-absent</literal> a fixed zero length key is used (thus, in this mode no confidentiality
|
||||
nor authenticity are provided!). This logic is useful to cover for systems that lack a TPM2 chip but
|
||||
where credentials shall be generated. Note that decryption of such credentials is refused on systems
|
||||
that have a TPM2 chip and where UEFI SecureBoot is enabled (this is done so that such a locked down
|
||||
system cannot be tricked into loading a credential generated this way that lacks authentication
|
||||
information). If set to <literal>auto-initrd</literal> a TPM2 key is used if a TPM2 is found. If not
|
||||
a fixed zero length key is used, equivalent to <literal>tpm2-absent</literal> mode. This option is
|
||||
particularly useful to generate credentials files that are encrypted/authenticated against TPM2 where
|
||||
available but still work on systems lacking support for this.</para>
|
||||
|
||||
<para>The <option>-H</option> switch is a shortcut for <option>--with-key=host</option>. Similar,
|
||||
<option>-T</option> is a shortcut for <option>--with-key=tpm2</option>.</para>
|
||||
|
||||
<para>When encrypting credentials that shall be used in the initial RAM disk (initrd) where
|
||||
<filename>/var/lib/systemd/</filename> is typically not available make sure to use
|
||||
<option>--with-key=tpm2</option> mode, to disable binding against the host secret.</para>
|
||||
<option>--with-key=auto-initrd</option> mode, to disable binding against the host secret.</para>
|
||||
|
||||
<para>This switch has no effect on the <command>decrypt</command> command, as information on which
|
||||
key to use for decryption is included in the encrypted credential already.</para></listitem>
|
||||
|
@ -40,7 +40,7 @@ static bool arg_legend = true;
|
||||
static bool arg_system = false;
|
||||
static TranscodeMode arg_transcode = TRANSCODE_OFF;
|
||||
static int arg_newline = -1;
|
||||
static sd_id128_t arg_with_key = SD_ID128_NULL;
|
||||
static sd_id128_t arg_with_key = _CRED_AUTO;
|
||||
static const char *arg_tpm2_device = NULL;
|
||||
static uint32_t arg_tpm2_pcr_mask = UINT32_MAX;
|
||||
static const char *arg_name = NULL;
|
||||
@ -560,7 +560,7 @@ static int verb_help(int argc, char **argv, void *userdata) {
|
||||
" --timestamp=TIME Include specified timestamp in encrypted credential\n"
|
||||
" --not-after=TIME Include specified invalidation time in encrypted\n"
|
||||
" credential\n"
|
||||
" --with-key=host|tpm2|host+tpm2|auto\n"
|
||||
" --with-key=host|tpm2|host+tpm2|tpm2-absent|auto|auto-initrd\n"
|
||||
" Which keys to encrypt with\n"
|
||||
" -H Shortcut for --with-key=host\n"
|
||||
" -T Shortcut for --with-key=tpm2\n"
|
||||
@ -684,13 +684,17 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
|
||||
case ARG_WITH_KEY:
|
||||
if (isempty(optarg) || streq(optarg, "auto"))
|
||||
arg_with_key = SD_ID128_NULL;
|
||||
arg_with_key = _CRED_AUTO;
|
||||
else if (streq(optarg, "auto-initrd"))
|
||||
arg_with_key = _CRED_AUTO_INITRD;
|
||||
else if (streq(optarg, "host"))
|
||||
arg_with_key = CRED_AES256_GCM_BY_HOST;
|
||||
else if (streq(optarg, "tpm2"))
|
||||
arg_with_key = CRED_AES256_GCM_BY_TPM2_HMAC;
|
||||
else if (STR_IN_SET(optarg, "host+tpm2", "tpm2+host"))
|
||||
arg_with_key = CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC;
|
||||
else if (streq(optarg, "tpm2-absent"))
|
||||
arg_with_key = CRED_AES256_GCM_BY_TPM2_ABSENT;
|
||||
else
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown key type: %s", optarg);
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "blockdev-util.h"
|
||||
#include "chattr-util.h"
|
||||
#include "creds-util.h"
|
||||
#include "efi-api.h"
|
||||
#include "env-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
@ -366,6 +367,11 @@ int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t *
|
||||
*
|
||||
* 3. The concatenation of the above.
|
||||
*
|
||||
* 4. Or a fixed "empty" key. This will not provide confidentiality or authenticity, of course, but is
|
||||
* useful to encode credentials for the initrd on TPM-less systems, where we simply have no better
|
||||
* concept to bind things to. Note that decryption of a key set up like this will be refused on
|
||||
* systems that have a TPM and have SecureBoot enabled.
|
||||
*
|
||||
* The above is hashed with SHA256 which is then used as encryption key for AES256-GCM. The encrypted
|
||||
* credential is a short (unencrypted) header describing which of the three keys to use, the IV to use for
|
||||
* AES256-GCM and some more meta information (sizes of certain objects) that is strictly speaking redundant,
|
||||
@ -474,15 +480,21 @@ int encrypt_credential_and_warn(
|
||||
int ksz, bsz, ivsz, tsz, added, r;
|
||||
uint8_t md[SHA256_DIGEST_LENGTH];
|
||||
const EVP_CIPHER *cc;
|
||||
#if HAVE_TPM2
|
||||
bool try_tpm2 = false;
|
||||
#endif
|
||||
sd_id128_t id;
|
||||
|
||||
assert(input || input_size == 0);
|
||||
assert(ret);
|
||||
assert(ret_size);
|
||||
|
||||
if (!sd_id128_in_set(with_key,
|
||||
_CRED_AUTO,
|
||||
_CRED_AUTO_INITRD,
|
||||
CRED_AES256_GCM_BY_HOST,
|
||||
CRED_AES256_GCM_BY_TPM2_HMAC,
|
||||
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC,
|
||||
CRED_AES256_GCM_BY_TPM2_ABSENT))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid key type: " SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(with_key));
|
||||
|
||||
if (name && !credential_name_valid(name))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid credential name: %s", name);
|
||||
|
||||
@ -500,23 +512,26 @@ int encrypt_credential_and_warn(
|
||||
log_debug("Including not-after timestamp '%s' in encrypted credential.", format_timestamp(buf, sizeof(buf), not_after));
|
||||
}
|
||||
|
||||
if (sd_id128_is_null(with_key) ||
|
||||
sd_id128_in_set(with_key, CRED_AES256_GCM_BY_HOST, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC)) {
|
||||
if (sd_id128_in_set(with_key,
|
||||
_CRED_AUTO,
|
||||
CRED_AES256_GCM_BY_HOST,
|
||||
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC)) {
|
||||
|
||||
r = get_credential_host_secret(
|
||||
CREDENTIAL_SECRET_GENERATE|
|
||||
CREDENTIAL_SECRET_WARN_NOT_ENCRYPTED|
|
||||
(sd_id128_is_null(with_key) ? CREDENTIAL_SECRET_FAIL_ON_TEMPORARY_FS : 0),
|
||||
(sd_id128_equal(with_key, _CRED_AUTO) ? CREDENTIAL_SECRET_FAIL_ON_TEMPORARY_FS : 0),
|
||||
&host_key,
|
||||
&host_key_size);
|
||||
if (r == -ENOMEDIUM && sd_id128_is_null(with_key))
|
||||
if (r == -ENOMEDIUM && sd_id128_equal(with_key, _CRED_AUTO))
|
||||
log_debug_errno(r, "Credential host secret location on temporary file system, not using.");
|
||||
else if (r < 0)
|
||||
return log_error_errno(r, "Failed to determine local credential host secret: %m");
|
||||
}
|
||||
|
||||
#if HAVE_TPM2
|
||||
if (sd_id128_is_null(with_key)) {
|
||||
bool try_tpm2;
|
||||
if (sd_id128_equal(with_key, _CRED_AUTO)) {
|
||||
/* If automatic mode is selected and we are running in a container, let's not try TPM2. OTOH
|
||||
* if user picks TPM2 explicitly, let's always honour the request and try. */
|
||||
|
||||
@ -527,11 +542,17 @@ int encrypt_credential_and_warn(
|
||||
log_debug("Running in container, not attempting to use TPM2.");
|
||||
|
||||
try_tpm2 = r <= 0;
|
||||
}
|
||||
} else if (sd_id128_equal(with_key, _CRED_AUTO_INITRD)) {
|
||||
/* If automatic mode for initrds is selected, we'll use the TPM2 key if the firmware does it,
|
||||
* otherwise we'll use a fixed key */
|
||||
|
||||
if (try_tpm2 ||
|
||||
sd_id128_in_set(with_key, CRED_AES256_GCM_BY_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC)) {
|
||||
try_tpm2 = efi_has_tpm2();
|
||||
if (!try_tpm2)
|
||||
log_debug("Firmware lacks TPM2 support, not attempting to use TPM2.");
|
||||
} else
|
||||
try_tpm2 = sd_id128_in_set(with_key, CRED_AES256_GCM_BY_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC);
|
||||
|
||||
if (try_tpm2) {
|
||||
r = tpm2_seal(tpm2_device,
|
||||
tpm2_pcr_mask,
|
||||
NULL,
|
||||
@ -544,7 +565,9 @@ int encrypt_credential_and_warn(
|
||||
&tpm2_pcr_bank,
|
||||
&tpm2_primary_alg);
|
||||
if (r < 0) {
|
||||
if (!sd_id128_is_null(with_key))
|
||||
if (sd_id128_equal(with_key, _CRED_AUTO_INITRD))
|
||||
log_warning("Firmware reported a TPM2 being present and used, but we didn't manage to talk to it. Credential will be refused if SecureBoot is enabled.");
|
||||
else if (!sd_id128_equal(with_key, _CRED_AUTO))
|
||||
return r;
|
||||
|
||||
log_debug_errno(r, "TPM2 sealing didn't work, not using: %m");
|
||||
@ -555,7 +578,7 @@ int encrypt_credential_and_warn(
|
||||
}
|
||||
#endif
|
||||
|
||||
if (sd_id128_is_null(with_key)) {
|
||||
if (sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD)) {
|
||||
/* Let's settle the key type in auto mode now. */
|
||||
|
||||
if (host_key && tpm2_key)
|
||||
@ -564,12 +587,17 @@ int encrypt_credential_and_warn(
|
||||
id = CRED_AES256_GCM_BY_TPM2_HMAC;
|
||||
else if (host_key)
|
||||
id = CRED_AES256_GCM_BY_HOST;
|
||||
else if (sd_id128_equal(with_key, _CRED_AUTO_INITRD))
|
||||
id = CRED_AES256_GCM_BY_TPM2_ABSENT;
|
||||
else
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
|
||||
"TPM2 not available and host key located on temporary file system, no encryption key available.");
|
||||
} else
|
||||
id = with_key;
|
||||
|
||||
if (sd_id128_equal(id, CRED_AES256_GCM_BY_TPM2_ABSENT))
|
||||
log_warning("Using a null key for encryption and signing. Confidentiality or authenticity will not be provided.");
|
||||
|
||||
/* Let's now take the host key and the TPM2 key and hash it together, to use as encryption key for the data */
|
||||
r = sha256_hash_host_and_tpm2_key(host_key, host_key_size, tpm2_key, tpm2_key_size, md);
|
||||
if (r < 0)
|
||||
@ -727,7 +755,7 @@ int decrypt_credential_and_warn(
|
||||
struct encrypted_credential_header *h;
|
||||
struct metadata_credential_header *m;
|
||||
uint8_t md[SHA256_DIGEST_LENGTH];
|
||||
bool with_tpm2, with_host_key;
|
||||
bool with_tpm2, with_host_key, is_tpm2_absent;
|
||||
const EVP_CIPHER *cc;
|
||||
int r, added;
|
||||
|
||||
@ -743,10 +771,31 @@ int decrypt_credential_and_warn(
|
||||
|
||||
with_host_key = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_HOST, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC);
|
||||
with_tpm2 = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC);
|
||||
is_tpm2_absent = sd_id128_equal(h->id, CRED_AES256_GCM_BY_TPM2_ABSENT);
|
||||
|
||||
if (!with_host_key && !with_tpm2)
|
||||
if (!with_host_key && !with_tpm2 && !is_tpm2_absent)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unknown encryption format, or corrupted data: %m");
|
||||
|
||||
if (is_tpm2_absent) {
|
||||
/* So this is a credential encrypted with a zero length key. We support this to cover for the
|
||||
* case where neither a host key not a TPM2 are available (specifically: initrd environments
|
||||
* where the host key is not yet accessible and no TPM2 chip exists at all), to minimize
|
||||
* different codeflow for TPM2 and non-TPM2 codepaths. Of course, credentials encoded this
|
||||
* way offer no confidentiality nor authenticity. Because of that it's important we refuse to
|
||||
* use them on systems that actually *do* have a TPM2 chip – if we are in SecureBoot
|
||||
* mode. Otherwise an attacker could hand us credentials like this and we'd use them thinking
|
||||
* they are trusted, even though they are not. */
|
||||
|
||||
if (efi_has_tpm2()) {
|
||||
if (is_efi_secure_boot())
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
|
||||
"Credential uses fixed key for fallback use when TPM2 is absent — but TPM2 is present, and SecureBoot is enabled, refusing.");
|
||||
|
||||
log_warning("Credential uses fixed key for use when TPM2 is absent, but TPM2 is present! Accepting anyway, since SecureBoot is disabled.");
|
||||
} else
|
||||
log_debug("Credential uses fixed key for use when TPM2 is absent, and TPM2 indeed is absent. Accepting.");
|
||||
}
|
||||
|
||||
/* Now we know the minimum header size */
|
||||
if (input_size < offsetof(struct encrypted_credential_header, iv))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short.");
|
||||
@ -827,6 +876,9 @@ int decrypt_credential_and_warn(
|
||||
return log_error_errno(r, "Failed to determine local credential key: %m");
|
||||
}
|
||||
|
||||
if (is_tpm2_absent)
|
||||
log_warning("Warning: using a null key for decryption and authentication. Confidentiality or authenticity are not provided.");
|
||||
|
||||
sha256_hash_host_and_tpm2_key(host_key, host_key_size, tpm2_key, tpm2_key_size, md);
|
||||
|
||||
assert_se(cc = EVP_aes_256_gcm());
|
||||
|
@ -38,10 +38,22 @@ typedef enum CredentialSecretFlags {
|
||||
|
||||
int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t *ret_size);
|
||||
|
||||
/* The three modes we support: keyed only by on-disk key, only by TPM2 HMAC key, and by the combination of both */
|
||||
/* The four modes we support: keyed only by on-disk key, only by TPM2 HMAC key, and by the combination of
|
||||
* both, as well as one with a fixed zero length key if TPM2 is missing (the latter of course provides no
|
||||
* authenticity or confidentiality, but is still useful for integrity protection, and makes things simpler
|
||||
* for us to handle). */
|
||||
#define CRED_AES256_GCM_BY_HOST SD_ID128_MAKE(5a,1c,6a,86,df,9d,40,96,b1,d5,a6,5e,08,62,f1,9a)
|
||||
#define CRED_AES256_GCM_BY_TPM2_HMAC SD_ID128_MAKE(0c,7c,c0,7b,11,76,45,91,9c,4b,0b,ea,08,bc,20,fe)
|
||||
#define CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC SD_ID128_MAKE(93,a8,94,09,48,74,44,90,90,ca,f2,fc,93,ca,b5,53)
|
||||
#define CRED_AES256_GCM_BY_TPM2_ABSENT SD_ID128_MAKE(05,84,69,da,f6,f5,43,24,80,05,49,da,0f,8e,a2,fb)
|
||||
|
||||
/* Two special IDs to pick a general automatic mode (i.e. tpm2+host if TPM2 exists, only host otherwise) or
|
||||
* an initrd-specific automatic mode (i.e. tpm2 if firmware can do it, otherwise fixed zero-length key, and
|
||||
* never involve host keys). These IDs will never be stored on disk, but are useful only internally while
|
||||
* figuring out what precisely to write to disk. To mark that these aren't a "real" type, we'll prefix them
|
||||
* with an underscore. */
|
||||
#define _CRED_AUTO SD_ID128_MAKE(a2,19,cb,07,85,b2,4c,04,b1,6d,18,ca,b9,d2,ee,01)
|
||||
#define _CRED_AUTO_INITRD SD_ID128_MAKE(02,dc,8e,de,3a,02,43,ab,a9,ec,54,9c,05,e6,a0,71)
|
||||
|
||||
int encrypt_credential_and_warn(sd_id128_t with_key, const char *name, usec_t timestamp, usec_t not_after, const char *tpm2_device, uint32_t tpm2_pcr_mask, const void *input, size_t input_size, void **ret, size_t *ret_size);
|
||||
int decrypt_credential_and_warn(const char *validate_name, usec_t validate_timestamp, const char *tpm2_device, const void *input, size_t input_size, void **ret, size_t *ret_size);
|
||||
|
Loading…
x
Reference in New Issue
Block a user