<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>
" --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"
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);
#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"
*
* 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,
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))
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);
&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");
}
#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)
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)
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;
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.");
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());
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);