diff options
author | Lennart Poettering <lennart@poettering.net> | 2022-04-14 14:46:40 +0200 |
---|---|---|
committer | Lennart Poettering <lennart@poettering.net> | 2022-04-20 17:49:17 +0200 |
commit | b6553329c03aec306351933843a5a3e0a5a7bfe2 (patch) | |
tree | c090de0a4d986a3e354be2f8f53948cd3edf02d5 /src | |
parent | 571d829ee49147c588e53a1f107c29fd23968581 (diff) | |
download | systemd-b6553329c03aec306351933843a5a3e0a5a7bfe2.tar.gz |
creds-util: permit credentials encrypted/signed by fixed zero length keys as fallback for systems lacking TPM2
This is supposed to be useful when generating credentials for immutable
initrd environments, where it is is relevant to support credentials even
on systems lacking a TPM2 chip.
With this, if `systemd-creds encrypt --with-key=auto-initrd` is used a
credential will be encrypted/signed with the TPM2 if it is available and
recognized by the firmware. Otherwise it will be encrypted/signed with
the fixed empty key, thus providing no confidentiality or authenticity.
The idea is that distributions use this mode to generically create
credentials that are as locked down as possible on the specific
platform.
Diffstat (limited to 'src')
-rw-r--r-- | src/creds/creds.c | 6 | ||||
-rw-r--r-- | src/shared/creds-util.c | 56 | ||||
-rw-r--r-- | src/shared/creds-util.h | 15 |
3 files changed, 67 insertions, 10 deletions
diff --git a/src/creds/creds.c b/src/creds/creds.c index 501eb2deb8..c5a1dc506c 100644 --- a/src/creds/creds.c +++ b/src/creds/creds.c @@ -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" @@ -685,12 +685,16 @@ static int parse_argv(int argc, char *argv[]) { case ARG_WITH_KEY: if (isempty(optarg) || streq(optarg, "auto")) 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); diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c index d8f1a597a8..95540979ad 100644 --- a/src/shared/creds-util.c +++ b/src/shared/creds-util.c @@ -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, @@ -482,9 +488,11 @@ int encrypt_credential_and_warn( 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_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)) @@ -534,6 +542,13 @@ 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 */ + + 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); @@ -550,7 +565,9 @@ int encrypt_credential_and_warn( &tpm2_pcr_bank, &tpm2_primary_alg); if (r < 0) { - if (!sd_id128_equal(with_key, _CRED_AUTO)) + 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"); @@ -561,7 +578,7 @@ int encrypt_credential_and_warn( } #endif - if (sd_id128_equal(with_key, _CRED_AUTO)) { + 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) @@ -570,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) @@ -733,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; @@ -749,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."); @@ -833,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()); diff --git a/src/shared/creds-util.h b/src/shared/creds-util.h index 7f0ce421ad..5e4b48220d 100644 --- a/src/shared/creds-util.h +++ b/src/shared/creds-util.h @@ -38,15 +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) -/* Special ID to pick automatic mode (i.e. tpm2+host if TPM2 exists, only host otherwise). This ID will never - * be stored on disk, but is useful only internally while figuring out what precisely to write to disk. To - * mark that this isn't a "real" type, we'll prefix it with an underscore. */ +/* 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); |