mirror of
https://github.com/Dasharo/systemd.git
synced 2026-03-06 15:02:31 -08:00
Merge pull request #30790 from poettering/null-creds-allow-with-tpm
creds: allow using NULL encryption if explicitly requested even if TPM is available, and add a comprehensive credential encryption/decrpytion test
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user