From 9c3d8db990ae9601ac434f56e9f1d2f82026bda6 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 20 Nov 2023 18:00:35 +0100 Subject: [PATCH 1/2] creds-util: optionally, allow NULL credentials even with TPM --- src/core/exec-credential.c | 2 ++ src/creds/creds.c | 5 +++++ src/shared/creds-util.c | 13 ++++++++----- src/shared/creds-util.h | 8 ++++++-- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/core/exec-credential.c b/src/core/exec-credential.c index 513087d069..4aa3e35bd8 100644 --- a/src/core/exec-credential.c +++ b/src/core/exec-credential.c @@ -283,6 +283,7 @@ static int maybe_decrypt_and_write_credential( /* tpm2_device= */ NULL, /* tpm2_signature_path= */ NULL, &IOVEC_MAKE(data, size), + /* flags= */ 0, &plaintext); if (r < 0) return r; @@ -708,6 +709,7 @@ static int acquire_credentials( /* tpm2_device= */ NULL, /* tpm2_signature_path= */ NULL, &IOVEC_MAKE(sc->data, sc->size), + /* flags= */ 0, &plaintext); if (r < 0) return r; diff --git a/src/creds/creds.c b/src/creds/creds.c index 01b2844dd3..c9d1a6e8d9 100644 --- a/src/creds/creds.c +++ b/src/creds/creds.c @@ -429,6 +429,7 @@ static int verb_cat(int argc, char **argv, void *userdata) { arg_tpm2_device, arg_tpm2_signature, &IOVEC_MAKE(data, size), + /* flags= */ 0, &plaintext); if (r < 0) return r; @@ -501,6 +502,7 @@ static int verb_encrypt(int argc, char **argv, void *userdata) { arg_tpm2_public_key, arg_tpm2_public_key_pcr_mask, &plaintext, + /* flags= */ 0, &output); if (r < 0) return r; @@ -589,6 +591,7 @@ static int verb_decrypt(int argc, char **argv, void *userdata) { arg_tpm2_device, arg_tpm2_signature, &input, + /* flags= */ 0, &plaintext); if (r < 0) return r; @@ -1029,6 +1032,7 @@ static int vl_method_encrypt(Varlink *link, JsonVariant *parameters, VarlinkMeth arg_tpm2_public_key, arg_tpm2_public_key_pcr_mask, p.text ? &IOVEC_MAKE_STRING(p.text) : &p.data, + /* flags= */ 0, &output); if (r < 0) return r; @@ -1103,6 +1107,7 @@ static int vl_method_decrypt(Varlink *link, JsonVariant *parameters, VarlinkMeth arg_tpm2_device, arg_tpm2_signature, &p.blob, + /* flags= */ 0, &output); if (r == -EBADMSG) return varlink_error(link, "io.systemd.Credentials.BadFormat", NULL); diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c index 716543a2a7..08d915cb8d 100644 --- a/src/shared/creds-util.c +++ b/src/shared/creds-util.c @@ -193,6 +193,7 @@ int read_credential_with_decryption(const char *name, void **ret, size_t *ret_si /* tpm2_device = */ NULL, /* tpm2_signature_path = */ NULL, &IOVEC_MAKE(data, sz), + /* flags= */ 0, &ret_iovec); if (r < 0) return r; @@ -723,6 +724,7 @@ int encrypt_credential_and_warn( const char *tpm2_pubkey_path, uint32_t tpm2_pubkey_pcr_mask, const struct iovec *input, + CredentialFlags flags, struct iovec *ret) { _cleanup_(iovec_done) struct iovec tpm2_blob = {}, tpm2_policy_hash = {}, iv = {}, pubkey = {}; @@ -901,7 +903,7 @@ int encrypt_credential_and_warn( } else id = with_key; - if (sd_id128_equal(id, CRED_AES256_GCM_BY_NULL)) + if (sd_id128_equal(id, CRED_AES256_GCM_BY_NULL) && !FLAGS_SET(flags, CREDENTIAL_ALLOW_NULL)) 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 */ @@ -1065,6 +1067,7 @@ int decrypt_credential_and_warn( const char *tpm2_device, const char *tpm2_signature_path, const struct iovec *input, + CredentialFlags flags, struct iovec *ret) { _cleanup_(iovec_done_erase) struct iovec host_key = {}, plaintext = {}, tpm2_key = {}; @@ -1101,7 +1104,7 @@ int decrypt_credential_and_warn( return log_error_errno(r, "Failed to load pcr signature: %m"); } - if (with_null) { + if (with_null && !FLAGS_SET(flags, CREDENTIAL_ALLOW_NULL)) { /* 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 @@ -1230,7 +1233,7 @@ int decrypt_credential_and_warn( return log_error_errno(r, "Failed to determine local credential key: %m"); } - if (with_null) + if (with_null && !FLAGS_SET(flags, CREDENTIAL_ALLOW_NULL)) 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, &tpm2_key, md); @@ -1366,11 +1369,11 @@ int get_credential_host_secret(CredentialSecretFlags flags, struct iovec *ret) { return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Support for encrypted credentials not available."); } -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_hash_pcr_mask, const char *tpm2_pubkey_path, uint32_t tpm2_pubkey_pcr_mask, const struct iovec *input, struct iovec *ret) { +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_hash_pcr_mask, const char *tpm2_pubkey_path, uint32_t tpm2_pubkey_pcr_mask, const struct iovec *input, CredentialFlags flags, struct iovec *ret) { return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Support for encrypted credentials not available."); } -int decrypt_credential_and_warn(const char *validate_name, usec_t validate_timestamp, const char *tpm2_device, const char *tpm2_signature_path, const struct iovec *input, struct iovec *ret) { +int decrypt_credential_and_warn(const char *validate_name, usec_t validate_timestamp, const char *tpm2_device, const char *tpm2_signature_path, const struct iovec *input, CredentialFlags flags, struct iovec *ret) { return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Support for encrypted credentials not available."); } diff --git a/src/shared/creds-util.h b/src/shared/creds-util.h index 38d5086e8c..9362d4e52c 100644 --- a/src/shared/creds-util.h +++ b/src/shared/creds-util.h @@ -57,6 +57,10 @@ int get_credential_host_secret(CredentialSecretFlags flags, struct iovec *ret); int get_credential_user_password(const char *username, char **ret_password, bool *ret_is_hashed); +typedef enum CredentialFlags { + CREDENTIAL_ALLOW_NULL = 1 << 0, /* allow decryption of NULL key, even if TPM is around */ +} CredentialFlags; + /* 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 @@ -77,5 +81,5 @@ int get_credential_user_password(const char *username, char **ret_password, bool #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_hash_pcr_mask, const char *tpm2_pubkey_path, uint32_t tpm2_pubkey_pcr_mask, const struct iovec *input, struct iovec *ret); -int decrypt_credential_and_warn(const char *validate_name, usec_t validate_timestamp, const char *tpm2_device, const char *tpm2_signature_path, const struct iovec *input, struct iovec *ret); +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_hash_pcr_mask, const char *tpm2_pubkey_path, uint32_t tpm2_pubkey_pcr_mask, const struct iovec *input, CredentialFlags flags, struct iovec *ret); +int decrypt_credential_and_warn(const char *validate_name, usec_t validate_timestamp, const char *tpm2_device, const char *tpm2_signature_path, const struct iovec *input, CredentialFlags flags, struct iovec *ret); From 3a3315c705b83fa0c09e01bd38046dc8d1e004c7 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 21 Nov 2023 14:17:31 +0100 Subject: [PATCH 2/2] test: add credential encryption/decryption test --- src/test/test-creds.c | 102 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/src/test/test-creds.c b/src/test/test-creds.c index acb198c1c1..778bb6ab8f 100644 --- a/src/test/test-creds.c +++ b/src/test/test-creds.c @@ -2,10 +2,13 @@ #include "creds-util.h" #include "fileio.h" +#include "id128-util.h" +#include "iovec-util.h" #include "path-util.h" #include "rm-rf.h" #include "tests.h" #include "tmpfile-util.h" +#include "tpm2-util.h" TEST(read_credential_strings) { _cleanup_free_ char *x = NULL, *y = NULL, *saved = NULL, *p = NULL; @@ -118,4 +121,103 @@ TEST(credential_glob_valid) { assert_se(credential_glob_valid(buf)); } +static void test_encrypt_decrypt_with(sd_id128_t mode) { + static const struct iovec plaintext = CONST_IOVEC_MAKE_STRING("this is a super secret string"); + int r; + + log_notice("Running encryption/decryption test with mode " SD_ID128_FORMAT_STR ".", SD_ID128_FORMAT_VAL(mode)); + + _cleanup_(iovec_done) struct iovec encrypted = {}; + r = encrypt_credential_and_warn( + mode, + "foo", + /* timestamp= */ USEC_INFINITY, + /* not_after=*/ USEC_INFINITY, + /* tpm2_device= */ NULL, + /* tpm2_hash_pcr_mask= */ 0, + /* tpm2_pubkey_path= */ NULL, + /* tpm2_pubkey_pcr_mask= */ 0, + &plaintext, + CREDENTIAL_ALLOW_NULL, + &encrypted); + if (ERRNO_IS_NEG_MACHINE_ID_UNSET(r)) { + log_notice_errno(r, "Skipping test encryption mode " SD_ID128_FORMAT_STR ", because /etc/machine-id is not initialized.", SD_ID128_FORMAT_VAL(mode)); + return; + } + if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) { + log_notice_errno(r, "Skipping test encryption mode " SD_ID128_FORMAT_STR ", because encrypted credentials are not supported.", SD_ID128_FORMAT_VAL(mode)); + return; + } + + assert_se(r >= 0); + + _cleanup_(iovec_done) struct iovec decrypted = {}; + r = decrypt_credential_and_warn( + "bar", + /* validate_timestamp= */ USEC_INFINITY, + /* tpm2_device= */ NULL, + /* tpm2_signature_path= */ NULL, + &encrypted, + CREDENTIAL_ALLOW_NULL, + &decrypted); + assert_se(r == -EREMOTE); /* name didn't match */ + + r = decrypt_credential_and_warn( + "foo", + /* validate_timestamp= */ USEC_INFINITY, + /* tpm2_device= */ NULL, + /* tpm2_signature_path= */ NULL, + &encrypted, + CREDENTIAL_ALLOW_NULL, + &decrypted); + assert_se(r >= 0); + + assert_se(iovec_memcmp(&plaintext, &decrypted) == 0); +} + +static bool try_tpm2(void) { +#if HAVE_TPM2 + _cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL; + int r; + + r = tpm2_context_new(/* device= */ NULL, &tpm2_context); + if (r < 0) + log_notice_errno(r, "Failed to create TPM2 context, assuming no TPM2 support or privileges: %m"); + + return r >= 0; +#else + return false; +#endif +} + +TEST(credential_encrypt_decrypt) { + _cleanup_(rm_rf_physical_and_freep) char *d = NULL; + _cleanup_free_ char *j = NULL; + + test_encrypt_decrypt_with(CRED_AES256_GCM_BY_NULL); + + assert_se(mkdtemp_malloc(NULL, &d) >= 0); + j = path_join(d, "secret"); + assert_se(j); + + const char *e = getenv("SYSTEMD_CREDENTIAL_SECRET"); + _cleanup_free_ char *ec = NULL; + + if (e) + assert_se(ec = strdup(e)); + + assert_se(setenv("SYSTEMD_CREDENTIAL_SECRET", j, true) >= 0); + + test_encrypt_decrypt_with(CRED_AES256_GCM_BY_HOST); + + if (try_tpm2()) { + test_encrypt_decrypt_with(CRED_AES256_GCM_BY_TPM2_HMAC); + test_encrypt_decrypt_with(CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC); + } + + if (ec) + assert_se(setenv("SYSTEMD_CREDENTIAL_SECRET", ec, true) >= 0); + +} + DEFINE_TEST_MAIN(LOG_INFO);