summaryrefslogtreecommitdiffstats
path: root/src/shared/creds-util.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/shared/creds-util.c')
-rw-r--r--src/shared/creds-util.c826
1 files changed, 587 insertions, 239 deletions
diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c
index fa8ebe0..1d8bd91 100644
--- a/src/shared/creds-util.c
+++ b/src/shared/creds-util.c
@@ -12,23 +12,28 @@
#include "capability-util.h"
#include "chattr-util.h"
#include "constants.h"
+#include "copy.h"
#include "creds-util.h"
#include "efi-api.h"
#include "env-util.h"
#include "fd-util.h"
#include "fileio.h"
+#include "format-util.h"
#include "fs-util.h"
#include "io-util.h"
#include "memory-util.h"
-#include "mkdir.h"
+#include "mkdir-label.h"
#include "openssl-util.h"
#include "parse-util.h"
#include "path-util.h"
#include "random-util.h"
+#include "recurse-dir.h"
#include "sparse-endian.h"
#include "stat-util.h"
+#include "tmpfile-util.h"
#include "tpm2-util.h"
-#include "virt.h"
+#include "user-util.h"
+#include "varlink.h"
#define PUBLIC_KEY_MAX (UINT32_C(1024) * UINT32_C(1024))
@@ -100,6 +105,17 @@ int get_encrypted_credentials_dir(const char **ret) {
return get_credentials_dir_internal("ENCRYPTED_CREDENTIALS_DIRECTORY", ret);
}
+int open_credentials_dir(void) {
+ const char *d;
+ int r;
+
+ r = get_credentials_dir(&d);
+ if (r < 0)
+ return r;
+
+ return RET_NERRNO(open(d, O_CLOEXEC|O_DIRECTORY));
+}
+
int read_credential(const char *name, void **ret, size_t *ret_size) {
_cleanup_free_ char *fn = NULL;
const char *d;
@@ -127,14 +143,13 @@ int read_credential(const char *name, void **ret, size_t *ret_size) {
}
int read_credential_with_decryption(const char *name, void **ret, size_t *ret_size) {
+ _cleanup_(iovec_done_erase) struct iovec ret_iovec = {};
_cleanup_(erase_and_freep) void *data = NULL;
_cleanup_free_ char *fn = NULL;
size_t sz = 0;
const char *d;
int r;
- assert(ret);
-
/* Just like read_credential() but will also look for encrypted credentials. Note that services only
* receive decrypted credentials, hence use read_credential() for those. This helper here is for
* generators, i.e. code that runs outside of service context, and thus has no decrypted credentials
@@ -177,23 +192,37 @@ int read_credential_with_decryption(const char *name, void **ret, size_t *ret_si
if (r < 0)
return log_error_errno(r, "Failed to read encrypted credential data: %m");
- r = decrypt_credential_and_warn(
- name,
- now(CLOCK_REALTIME),
- /* tpm2_device = */ NULL,
- /* tpm2_signature_path = */ NULL,
- data,
- sz,
- ret,
- ret_size);
+ if (geteuid() != 0)
+ r = ipc_decrypt_credential(
+ name,
+ now(CLOCK_REALTIME),
+ getuid(),
+ &IOVEC_MAKE(data, sz),
+ CREDENTIAL_ANY_SCOPE,
+ &ret_iovec);
+ else
+ r = decrypt_credential_and_warn(
+ name,
+ now(CLOCK_REALTIME),
+ /* tpm2_device= */ NULL,
+ /* tpm2_signature_path= */ NULL,
+ getuid(),
+ &IOVEC_MAKE(data, sz),
+ CREDENTIAL_ANY_SCOPE,
+ &ret_iovec);
if (r < 0)
return r;
+ if (ret)
+ *ret = TAKE_PTR(ret_iovec.iov_base);
+ if (ret_size)
+ *ret_size = ret_iovec.iov_len;
+
return 1; /* found */
not_found:
- *ret = NULL;
-
+ if (ret)
+ *ret = NULL;
if (ret_size)
*ret_size = 0;
@@ -205,6 +234,7 @@ int read_credential_strings_many_internal(
...) {
_cleanup_free_ void *b = NULL;
+ bool all = true;
int r, ret = 0;
/* Reads a bunch of credentials into the specified buffers. If the specified buffers are already
@@ -220,10 +250,11 @@ int read_credential_strings_many_internal(
r = read_credential(first_name, &b, NULL);
if (r == -ENXIO) /* No creds passed at all? Bail immediately. */
return 0;
- if (r < 0) {
- if (r != -ENOENT)
- ret = r;
- } else
+ if (r == -ENOENT)
+ all = false;
+ else if (r < 0)
+ RET_GATHER(ret, r);
+ else
free_and_replace(*first_value, b);
va_list ap;
@@ -238,20 +269,19 @@ int read_credential_strings_many_internal(
if (!name)
break;
- value = va_arg(ap, char **);
- if (*value)
- continue;
+ value = ASSERT_PTR(va_arg(ap, char **));
r = read_credential(name, &bb, NULL);
- if (r < 0) {
- if (ret >= 0 && r != -ENOENT)
- ret = r;
- } else
+ if (r == -ENOENT)
+ all = false;
+ else if (r < 0)
+ RET_GATHER(ret, r);
+ else
free_and_replace(*value, bb);
}
va_end(ap);
- return ret;
+ return ret < 0 ? ret : all;
}
int read_credential_bool(const char *name) {
@@ -341,8 +371,7 @@ static int make_credential_host_secret(
CredentialSecretFlags flags,
const char *dirname,
const char *fn,
- void **ret_data,
- size_t *ret_size) {
+ struct iovec *ret) {
_cleanup_free_ char *t = NULL;
_cleanup_close_ int fd = -EBADF;
@@ -351,22 +380,9 @@ static int make_credential_host_secret(
assert(dfd >= 0);
assert(fn);
- /* For non-root users creating a temporary file using the openat(2) over "." will fail later, in the
- * linkat(2) step at the end. The reason is that linkat(2) requires the CAP_DAC_READ_SEARCH
- * capability when it uses the AT_EMPTY_PATH flag. */
- if (have_effective_cap(CAP_DAC_READ_SEARCH) > 0) {
- fd = openat(dfd, ".", O_CLOEXEC|O_WRONLY|O_TMPFILE, 0400);
- if (fd < 0)
- log_debug_errno(errno, "Failed to create temporary credential file with O_TMPFILE, proceeding without: %m");
- }
- if (fd < 0) {
- if (asprintf(&t, "credential.secret.%016" PRIx64, random_u64()) < 0)
- return -ENOMEM;
-
- fd = openat(dfd, t, O_CLOEXEC|O_WRONLY|O_CREAT|O_EXCL|O_NOFOLLOW, 0400);
- if (fd < 0)
- return -errno;
- }
+ fd = open_tmpfile_linkable_at(dfd, fn, O_CLOEXEC|O_WRONLY, &t);
+ if (fd < 0)
+ return log_debug_errno(fd, "Failed to create temporary file for credential host secret: %m");
r = chattr_secret(fd, 0);
if (r < 0)
@@ -386,44 +402,34 @@ static int make_credential_host_secret(
if (r < 0)
goto fail;
- if (fsync(fd) < 0) {
+ if (fchmod(fd, 0400) < 0) {
r = -errno;
goto fail;
}
- warn_not_encrypted(fd, flags, dirname, fn);
-
- if (t) {
- r = rename_noreplace(dfd, t, dfd, fn);
- if (r < 0)
- goto fail;
-
- t = mfree(t);
- } else if (linkat(fd, "", dfd, fn, AT_EMPTY_PATH) < 0) {
+ if (fsync(fd) < 0) {
r = -errno;
goto fail;
}
- if (fsync(dfd) < 0) {
- r = -errno;
+ warn_not_encrypted(fd, flags, dirname, fn);
+
+ r = link_tmpfile_at(fd, dfd, t, fn, LINK_TMPFILE_SYNC);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to link host key into place: %m");
goto fail;
}
- if (ret_data) {
+ if (ret) {
void *copy;
copy = memdup(buf.data, sizeof(buf.data));
- if (!copy) {
- r = -ENOMEM;
- goto fail;
- }
+ if (!copy)
+ return -ENOMEM;
- *ret_data = copy;
+ *ret = IOVEC_MAKE(copy, sizeof(buf.data));
}
- if (ret_size)
- *ret_size = sizeof(buf.data);
-
return 0;
fail:
@@ -433,7 +439,7 @@ fail:
return r;
}
-int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t *ret_size) {
+int get_credential_host_secret(CredentialSecretFlags flags, struct iovec *ret) {
_cleanup_free_ char *_dirname = NULL, *_filename = NULL;
_cleanup_close_ int dfd = -EBADF;
sd_id128_t machine_id;
@@ -501,7 +507,7 @@ int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t *
"Failed to open %s/%s: %m", dirname, filename);
- r = make_credential_host_secret(dfd, machine_id, flags, dirname, filename, ret, ret_size);
+ r = make_credential_host_secret(dfd, machine_id, flags, dirname, filename, ret);
if (r == -EEXIST) {
log_debug_errno(r, "Credential secret %s/%s appeared while we were creating it, rereading.",
dirname, filename);
@@ -549,7 +555,7 @@ int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t *
"Failed to read %s/%s: %m", dirname, filename);
if ((size_t) n != l) /* What? The size changed? */
return log_debug_errno(SYNTHETIC_ERRNO(EIO),
- "Failed to read %s/%s: %m", dirname, filename);
+ "Failed to read %s/%s.", dirname, filename);
if (sd_id128_equal(machine_id, f->machine_id)) {
size_t sz;
@@ -568,12 +574,9 @@ int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t *
if (!copy)
return log_oom_debug();
- *ret = copy;
+ *ret = IOVEC_MAKE(copy, sz);
}
- if (ret_size)
- *ret_size = sz;
-
return 0;
}
@@ -658,6 +661,11 @@ struct _packed_ tpm2_public_key_credential_header {
/* Followed by NUL bytes until next 8 byte boundary */
};
+struct _packed_ scoped_credential_header {
+ le64_t flags; /* SCOPE_HASH_DATA_BASE_FLAGS for now */
+};
+
+/* This header is encrypted */
struct _packed_ metadata_credential_header {
le64_t timestamp;
le64_t not_after;
@@ -666,23 +674,38 @@ struct _packed_ metadata_credential_header {
/* Followed by NUL bytes until next 8 byte boundary */
};
+struct _packed_ scoped_hash_data {
+ le64_t flags; /* copy of the scoped_credential_header.flags */
+ le32_t uid;
+ sd_id128_t machine_id;
+ char username[]; /* followed by the username */
+ /* Later on we might want to extend this: with a cgroup path to allow per-app secrets, and with the user's $HOME encryption key */
+};
+
+enum {
+ /* Flags for scoped_hash_data.flags and scoped_credential_header.flags */
+ SCOPE_HASH_DATA_HAS_UID = 1 << 0,
+ SCOPE_HASH_DATA_HAS_MACHINE = 1 << 1,
+ SCOPE_HASH_DATA_HAS_USERNAME = 1 << 2,
+
+ SCOPE_HASH_DATA_BASE_FLAGS = SCOPE_HASH_DATA_HAS_UID | SCOPE_HASH_DATA_HAS_USERNAME | SCOPE_HASH_DATA_HAS_MACHINE,
+};
+
/* Some generic limit for parts of the encrypted credential for which we don't know the right size ahead of
* time, but where we are really sure it won't be larger than this. Should be larger than any possible IV,
* padding, tag size and so on. This is purely used for early filtering out of invalid sizes. */
#define CREDENTIAL_FIELD_SIZE_MAX (16U*1024U)
static int sha256_hash_host_and_tpm2_key(
- const void *host_key,
- size_t host_key_size,
- const void *tpm2_key,
- size_t tpm2_key_size,
+ const struct iovec *host_key,
+ const struct iovec *tpm2_key,
uint8_t ret[static SHA256_DIGEST_LENGTH]) {
_cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *md = NULL;
unsigned l;
- assert(host_key_size == 0 || host_key);
- assert(tpm2_key_size == 0 || tpm2_key);
+ assert(iovec_is_valid(host_key));
+ assert(iovec_is_valid(tpm2_key));
assert(ret);
/* Combines the host key and the TPM2 HMAC hash into a SHA256 hash value we'll use as symmetric encryption key. */
@@ -694,10 +717,10 @@ static int sha256_hash_host_and_tpm2_key(
if (EVP_DigestInit_ex(md, EVP_sha256(), NULL) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initial SHA256 context.");
- if (host_key && EVP_DigestUpdate(md, host_key, host_key_size) != 1)
+ if (iovec_is_set(host_key) && EVP_DigestUpdate(md, host_key->iov_base, host_key->iov_len) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to hash host key.");
- if (tpm2_key && EVP_DigestUpdate(md, tpm2_key, tpm2_key_size) != 1)
+ if (iovec_is_set(tpm2_key) && EVP_DigestUpdate(md, tpm2_key->iov_base, tpm2_key->iov_len) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to hash TPM2 key.");
assert(EVP_MD_CTX_size(md) == SHA256_DIGEST_LENGTH);
@@ -709,6 +732,58 @@ static int sha256_hash_host_and_tpm2_key(
return 0;
}
+static int mangle_uid_into_key(
+ uid_t uid,
+ uint8_t md[static SHA256_DIGEST_LENGTH]) {
+
+ sd_id128_t mid;
+ int r;
+
+ assert(uid_is_valid(uid));
+ assert(md);
+
+ /* If we shall encrypt for a specific user, we HMAC() a structure with the user's credentials
+ * (specifically, UID, user name, machine ID) with the key we'd otherwise use for system credentials,
+ * and use the resulting hash as actual encryption key. */
+
+ errno = 0;
+ struct passwd *pw = getpwuid(uid);
+ if (!pw)
+ return log_error_errno(
+ IN_SET(errno, 0, ENOENT) ? SYNTHETIC_ERRNO(ESRCH) : errno,
+ "Failed to resolve UID " UID_FMT ": %m", uid);
+
+ r = sd_id128_get_machine(&mid);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read machine ID: %m");
+
+ size_t sz = offsetof(struct scoped_hash_data, username) + strlen(pw->pw_name) + 1;
+ _cleanup_free_ struct scoped_hash_data *d = malloc0(sz);
+ if (!d)
+ return log_oom();
+
+ d->flags = htole64(SCOPE_HASH_DATA_BASE_FLAGS);
+ d->uid = htole32(uid);
+ d->machine_id = mid;
+
+ strcpy(d->username, pw->pw_name);
+
+ _cleanup_(erase_and_freep) void *buf = NULL;
+ size_t buf_size = 0;
+ r = openssl_hmac_many(
+ "sha256",
+ md, SHA256_DIGEST_LENGTH,
+ &IOVEC_MAKE(d, sz), 1,
+ &buf, &buf_size);
+ if (r < 0)
+ return r;
+
+ assert(buf_size == SHA256_DIGEST_LENGTH);
+ memcpy(md, buf, buf_size);
+
+ return 0;
+}
+
int encrypt_credential_and_warn(
sd_id128_t with_key,
const char *name,
@@ -718,38 +793,39 @@ int encrypt_credential_and_warn(
uint32_t tpm2_hash_pcr_mask,
const char *tpm2_pubkey_path,
uint32_t tpm2_pubkey_pcr_mask,
- const void *input,
- size_t input_size,
- void **ret,
- size_t *ret_size) {
+ uid_t uid,
+ const struct iovec *input,
+ CredentialFlags flags,
+ struct iovec *ret) {
+ _cleanup_(iovec_done) struct iovec tpm2_blob = {}, tpm2_policy_hash = {}, iv = {}, pubkey = {};
+ _cleanup_(iovec_done_erase) struct iovec tpm2_key = {}, output = {}, host_key = {};
_cleanup_(EVP_CIPHER_CTX_freep) EVP_CIPHER_CTX *context = NULL;
- _cleanup_(erase_and_freep) void *host_key = NULL, *tpm2_key = NULL;
- size_t host_key_size = 0, tpm2_key_size = 0, tpm2_blob_size = 0, tpm2_policy_hash_size = 0, output_size, p, ml;
- _cleanup_free_ void *tpm2_blob = NULL, *tpm2_policy_hash = NULL, *iv = NULL, *output = NULL;
_cleanup_free_ struct metadata_credential_header *m = NULL;
uint16_t tpm2_pcr_bank = 0, tpm2_primary_alg = 0;
struct encrypted_credential_header *h;
int ksz, bsz, ivsz, tsz, added, r;
- _cleanup_free_ void *pubkey = NULL;
- size_t pubkey_size = 0;
uint8_t md[SHA256_DIGEST_LENGTH];
const EVP_CIPHER *cc;
sd_id128_t id;
+ size_t p, ml;
- assert(input || input_size == 0);
+ assert(iovec_is_valid(input));
assert(ret);
- assert(ret_size);
if (!sd_id128_in_set(with_key,
_CRED_AUTO,
_CRED_AUTO_INITRD,
+ _CRED_AUTO_SCOPED,
CRED_AES256_GCM_BY_HOST,
+ CRED_AES256_GCM_BY_HOST_SCOPED,
CRED_AES256_GCM_BY_TPM2_HMAC,
CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK,
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC,
+ CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED,
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK,
- CRED_AES256_GCM_BY_TPM2_ABSENT))
+ CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED,
+ CRED_AES256_GCM_BY_NULL))
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))
@@ -770,18 +846,31 @@ int encrypt_credential_and_warn(
}
if (sd_id128_in_set(with_key,
+ _CRED_AUTO_SCOPED,
+ CRED_AES256_GCM_BY_HOST_SCOPED,
+ CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED,
+ CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED)) {
+ if (!uid_is_valid(uid))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Scoped credential selected, but no UID specified.");
+ } else
+ uid = UID_INVALID;
+
+ if (sd_id128_in_set(with_key,
_CRED_AUTO,
+ _CRED_AUTO_SCOPED,
CRED_AES256_GCM_BY_HOST,
+ CRED_AES256_GCM_BY_HOST_SCOPED,
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC,
- CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK)) {
+ CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED,
+ CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK,
+ CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED)) {
r = get_credential_host_secret(
CREDENTIAL_SECRET_GENERATE|
CREDENTIAL_SECRET_WARN_NOT_ENCRYPTED|
- (sd_id128_equal(with_key, _CRED_AUTO) ? CREDENTIAL_SECRET_FAIL_ON_TEMPORARY_FS : 0),
- &host_key,
- &host_key_size);
- if (r == -ENOMEDIUM && sd_id128_equal(with_key, _CRED_AUTO))
+ (sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_SCOPED) ? CREDENTIAL_SECRET_FAIL_ON_TEMPORARY_FS : 0),
+ &host_key);
+ if (r == -ENOMEDIUM && sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_SCOPED))
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");
@@ -789,7 +878,7 @@ int encrypt_credential_and_warn(
#if HAVE_TPM2
bool try_tpm2;
- if (sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD)) {
+ if (sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD, _CRED_AUTO_SCOPED)) {
/* If automatic mode is selected lets see if a TPM2 it is present. If we are running in a
* container tpm2_support will detect this, and will return a different flag combination of
* TPM2_SUPPORT_FULL, effectively skipping the use of TPM2 when inside one. */
@@ -802,27 +891,31 @@ int encrypt_credential_and_warn(
CRED_AES256_GCM_BY_TPM2_HMAC,
CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK,
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC,
- CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK);
+ CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED,
+ CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK,
+ CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED);
if (try_tpm2) {
if (sd_id128_in_set(with_key,
_CRED_AUTO,
_CRED_AUTO_INITRD,
+ _CRED_AUTO_SCOPED,
CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK,
- CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK)) {
+ CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK,
+ CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED)) {
/* Load public key for PCR policies, if one is specified, or explicitly requested */
- r = tpm2_load_pcr_public_key(tpm2_pubkey_path, &pubkey, &pubkey_size);
+ r = tpm2_load_pcr_public_key(tpm2_pubkey_path, &pubkey.iov_base, &pubkey.iov_len);
if (r < 0) {
- if (tpm2_pubkey_path || r != -ENOENT || !sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD))
+ if (tpm2_pubkey_path || r != -ENOENT || !sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD, _CRED_AUTO_SCOPED))
return log_error_errno(r, "Failed read TPM PCR public key: %m");
log_debug_errno(r, "Failed to read TPM2 PCR public key, proceeding without: %m");
}
}
- if (!pubkey)
+ if (!iovec_is_set(&pubkey))
tpm2_pubkey_pcr_mask = 0;
_cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL;
@@ -844,8 +937,8 @@ int encrypt_credential_and_warn(
return log_error_errno(r, "Could not read PCR values: %m");
TPM2B_PUBLIC public;
- if (pubkey) {
- r = tpm2_tpm2b_public_from_pem(pubkey, pubkey_size, &public);
+ if (iovec_is_set(&pubkey)) {
+ r = tpm2_tpm2b_public_from_pem(pubkey.iov_base, pubkey.iov_len, &public);
if (r < 0)
return log_error_errno(r, "Could not convert public key to TPM2B_PUBLIC: %m");
}
@@ -854,7 +947,7 @@ int encrypt_credential_and_warn(
r = tpm2_calculate_sealing_policy(
tpm2_hash_pcr_values,
tpm2_n_hash_pcr_values,
- pubkey ? &public : NULL,
+ iovec_is_set(&pubkey) ? &public : NULL,
/* use_pin= */ false,
/* pcrlock_policy= */ NULL,
&tpm2_policy);
@@ -865,56 +958,61 @@ int encrypt_credential_and_warn(
/* seal_key_handle= */ 0,
&tpm2_policy,
/* pin= */ NULL,
- &tpm2_key, &tpm2_key_size,
- &tpm2_blob, &tpm2_blob_size,
+ &tpm2_key,
+ &tpm2_blob,
&tpm2_primary_alg,
- /* ret_srk_buf= */ NULL,
- /* ret_srk_buf_size= */ NULL);
+ /* ret_srk= */ NULL);
if (r < 0) {
if (sd_id128_equal(with_key, _CRED_AUTO_INITRD))
log_warning("TPM2 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))
+ else if (!sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_SCOPED))
return log_error_errno(r, "Failed to seal to TPM2: %m");
log_notice_errno(r, "TPM2 sealing didn't work, continuing without TPM2: %m");
}
- tpm2_policy_hash_size = tpm2_policy.size;
- tpm2_policy_hash = malloc(tpm2_policy_hash_size);
- if (!tpm2_policy_hash)
+ if (!iovec_memdup(&IOVEC_MAKE(tpm2_policy.buffer, tpm2_policy.size), &tpm2_policy_hash))
return log_oom();
- memcpy(tpm2_policy_hash, tpm2_policy.buffer, tpm2_policy_hash_size);
- assert(tpm2_blob_size <= CREDENTIAL_FIELD_SIZE_MAX);
- assert(tpm2_policy_hash_size <= CREDENTIAL_FIELD_SIZE_MAX);
+ assert(tpm2_blob.iov_len <= CREDENTIAL_FIELD_SIZE_MAX);
+ assert(tpm2_policy_hash.iov_len <= CREDENTIAL_FIELD_SIZE_MAX);
}
#endif
- if (sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD)) {
+ if (sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD, _CRED_AUTO_SCOPED)) {
/* Let's settle the key type in auto mode now. */
- if (host_key && tpm2_key)
- id = pubkey ? CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK : CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC;
- else if (tpm2_key)
- id = pubkey ? CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK : CRED_AES256_GCM_BY_TPM2_HMAC;
- else if (host_key)
- id = CRED_AES256_GCM_BY_HOST;
+ if (iovec_is_set(&host_key) && iovec_is_set(&tpm2_key))
+ id = iovec_is_set(&pubkey) ? (sd_id128_equal(with_key, _CRED_AUTO_SCOPED) ?
+ CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED : CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK)
+ : (sd_id128_equal(with_key, _CRED_AUTO_SCOPED) ?
+ CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED : CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC);
+ else if (iovec_is_set(&tpm2_key) && !sd_id128_equal(with_key, _CRED_AUTO_SCOPED))
+ id = iovec_is_set(&pubkey) ? CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK : CRED_AES256_GCM_BY_TPM2_HMAC;
+ else if (iovec_is_set(&host_key))
+ id = sd_id128_equal(with_key, _CRED_AUTO_SCOPED) ? CRED_AES256_GCM_BY_HOST_SCOPED : CRED_AES256_GCM_BY_HOST;
else if (sd_id128_equal(with_key, _CRED_AUTO_INITRD))
- id = CRED_AES256_GCM_BY_TPM2_ABSENT;
+ id = CRED_AES256_GCM_BY_NULL;
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))
+ 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 */
- r = sha256_hash_host_and_tpm2_key(host_key, host_key_size, tpm2_key, tpm2_key_size, md);
+ r = sha256_hash_host_and_tpm2_key(&host_key, &tpm2_key, md);
if (r < 0)
return r;
+ if (uid_is_valid(uid)) {
+ r = mangle_uid_into_key(uid, md);
+ if (r < 0)
+ return r;
+ }
+
assert_se(cc = EVP_aes_256_gcm());
ksz = EVP_CIPHER_key_length(cc);
@@ -928,11 +1026,13 @@ int encrypt_credential_and_warn(
if (ivsz > 0) {
assert((size_t) ivsz <= CREDENTIAL_FIELD_SIZE_MAX);
- iv = malloc(ivsz);
- if (!iv)
+ iv.iov_base = malloc(ivsz);
+ if (!iv.iov_base)
return log_oom();
- r = crypto_random_bytes(iv, ivsz);
+ iv.iov_len = ivsz;
+
+ r = crypto_random_bytes(iv.iov_base, iv.iov_len);
if (r < 0)
return log_error_errno(r, "Failed to acquired randomized IV: %m");
}
@@ -944,61 +1044,71 @@ int encrypt_credential_and_warn(
return log_error_errno(SYNTHETIC_ERRNO(ENOMEM), "Failed to allocate encryption object: %s",
ERR_error_string(ERR_get_error(), NULL));
- if (EVP_EncryptInit_ex(context, cc, NULL, md, iv) != 1)
+ if (EVP_EncryptInit_ex(context, cc, NULL, md, iv.iov_base) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize encryption context: %s",
ERR_error_string(ERR_get_error(), NULL));
/* Just an upper estimate */
- output_size =
+ output.iov_len =
ALIGN8(offsetof(struct encrypted_credential_header, iv) + ivsz) +
- ALIGN8(tpm2_key ? offsetof(struct tpm2_credential_header, policy_hash_and_blob) + tpm2_blob_size + tpm2_policy_hash_size : 0) +
- ALIGN8(pubkey ? offsetof(struct tpm2_public_key_credential_header, data) + pubkey_size : 0) +
+ ALIGN8(iovec_is_set(&tpm2_key) ? offsetof(struct tpm2_credential_header, policy_hash_and_blob) + tpm2_blob.iov_len + tpm2_policy_hash.iov_len : 0) +
+ ALIGN8(iovec_is_set(&pubkey) ? offsetof(struct tpm2_public_key_credential_header, data) + pubkey.iov_len : 0) +
+ ALIGN8(uid_is_valid(uid) ? sizeof(struct scoped_credential_header) : 0) +
ALIGN8(offsetof(struct metadata_credential_header, name) + strlen_ptr(name)) +
- input_size + 2U * (size_t) bsz +
+ input->iov_len + 2U * (size_t) bsz +
tsz;
- output = malloc0(output_size);
- if (!output)
+ output.iov_base = malloc0(output.iov_len);
+ if (!output.iov_base)
return log_oom();
- h = (struct encrypted_credential_header*) output;
+ h = (struct encrypted_credential_header*) output.iov_base;
h->id = id;
h->block_size = htole32(bsz);
h->key_size = htole32(ksz);
h->tag_size = htole32(tsz);
h->iv_size = htole32(ivsz);
- memcpy(h->iv, iv, ivsz);
+ memcpy(h->iv, iv.iov_base, ivsz);
p = ALIGN8(offsetof(struct encrypted_credential_header, iv) + ivsz);
- if (tpm2_key) {
+ if (iovec_is_set(&tpm2_key)) {
struct tpm2_credential_header *t;
- t = (struct tpm2_credential_header*) ((uint8_t*) output + p);
+ t = (struct tpm2_credential_header*) ((uint8_t*) output.iov_base + p);
t->pcr_mask = htole64(tpm2_hash_pcr_mask);
t->pcr_bank = htole16(tpm2_pcr_bank);
t->primary_alg = htole16(tpm2_primary_alg);
- t->blob_size = htole32(tpm2_blob_size);
- t->policy_hash_size = htole32(tpm2_policy_hash_size);
- memcpy(t->policy_hash_and_blob, tpm2_blob, tpm2_blob_size);
- memcpy(t->policy_hash_and_blob + tpm2_blob_size, tpm2_policy_hash, tpm2_policy_hash_size);
+ t->blob_size = htole32(tpm2_blob.iov_len);
+ t->policy_hash_size = htole32(tpm2_policy_hash.iov_len);
+ memcpy(t->policy_hash_and_blob, tpm2_blob.iov_base, tpm2_blob.iov_len);
+ memcpy(t->policy_hash_and_blob + tpm2_blob.iov_len, tpm2_policy_hash.iov_base, tpm2_policy_hash.iov_len);
- p += ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) + tpm2_blob_size + tpm2_policy_hash_size);
+ p += ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) + tpm2_blob.iov_len + tpm2_policy_hash.iov_len);
}
- if (pubkey) {
+ if (iovec_is_set(&pubkey)) {
struct tpm2_public_key_credential_header *z;
- z = (struct tpm2_public_key_credential_header*) ((uint8_t*) output + p);
+ z = (struct tpm2_public_key_credential_header*) ((uint8_t*) output.iov_base + p);
z->pcr_mask = htole64(tpm2_pubkey_pcr_mask);
- z->size = htole32(pubkey_size);
- memcpy(z->data, pubkey, pubkey_size);
+ z->size = htole32(pubkey.iov_len);
+ memcpy(z->data, pubkey.iov_base, pubkey.iov_len);
+
+ p += ALIGN8(offsetof(struct tpm2_public_key_credential_header, data) + pubkey.iov_len);
+ }
+
+ if (uid_is_valid(uid)) {
+ struct scoped_credential_header *w;
+
+ w = (struct scoped_credential_header*) ((uint8_t*) output.iov_base + p);
+ w->flags = htole64(SCOPE_HASH_DATA_BASE_FLAGS);
- p += ALIGN8(offsetof(struct tpm2_public_key_credential_header, data) + pubkey_size);
+ p += ALIGN8(sizeof(struct scoped_credential_header));
}
- /* Pass the encrypted + TPM2 header as AAD */
- if (EVP_EncryptUpdate(context, NULL, &added, output, p) != 1)
+ /* Pass the encrypted + TPM2 header + scoped header as AAD */
+ if (EVP_EncryptUpdate(context, NULL, &added, output.iov_base, p) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to write AAD data: %s",
ERR_error_string(ERR_get_error(), NULL));
@@ -1014,53 +1124,52 @@ int encrypt_credential_and_warn(
memcpy_safe(m->name, name, ml);
/* And encrypt the metadata header */
- if (EVP_EncryptUpdate(context, (uint8_t*) output + p, &added, (const unsigned char*) m, ALIGN8(offsetof(struct metadata_credential_header, name) + ml)) != 1)
+ if (EVP_EncryptUpdate(context, (uint8_t*) output.iov_base + p, &added, (const unsigned char*) m, ALIGN8(offsetof(struct metadata_credential_header, name) + ml)) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to encrypt metadata header: %s",
ERR_error_string(ERR_get_error(), NULL));
assert(added >= 0);
- assert((size_t) added <= output_size - p);
+ assert((size_t) added <= output.iov_len - p);
p += added;
/* Then encrypt the plaintext */
- if (EVP_EncryptUpdate(context, (uint8_t*) output + p, &added, input, input_size) != 1)
+ if (EVP_EncryptUpdate(context, (uint8_t*) output.iov_base + p, &added, input->iov_base, input->iov_len) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to encrypt data: %s",
ERR_error_string(ERR_get_error(), NULL));
assert(added >= 0);
- assert((size_t) added <= output_size - p);
+ assert((size_t) added <= output.iov_len - p);
p += added;
/* Finalize */
- if (EVP_EncryptFinal_ex(context, (uint8_t*) output + p, &added) != 1)
+ if (EVP_EncryptFinal_ex(context, (uint8_t*) output.iov_base + p, &added) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finalize data encryption: %s",
ERR_error_string(ERR_get_error(), NULL));
assert(added >= 0);
- assert((size_t) added <= output_size - p);
+ assert((size_t) added <= output.iov_len - p);
p += added;
- assert(p <= output_size - tsz);
+ assert(p <= output.iov_len - tsz);
/* Append tag */
- if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_GET_TAG, tsz, (uint8_t*) output + p) != 1)
+ if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_GET_TAG, tsz, (uint8_t*) output.iov_base + p) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get tag: %s",
ERR_error_string(ERR_get_error(), NULL));
p += tsz;
- assert(p <= output_size);
+ assert(p <= output.iov_len);
+ output.iov_len = p;
- if (DEBUG_LOGGING && input_size > 0) {
+ if (DEBUG_LOGGING && input->iov_len > 0) {
size_t base64_size;
- base64_size = DIV_ROUND_UP(p * 4, 3); /* Include base64 size increase in debug output */
- assert(base64_size >= input_size);
- log_debug("Input of %zu bytes grew to output of %zu bytes (+%2zu%%).", input_size, base64_size, base64_size * 100 / input_size - 100);
+ base64_size = DIV_ROUND_UP(output.iov_len * 4, 3); /* Include base64 size increase in debug output */
+ assert(base64_size >= input->iov_len);
+ log_debug("Input of %zu bytes grew to output of %zu bytes (+%2zu%%).", input->iov_len, base64_size, base64_size * 100 / input->iov_len - 100);
}
- *ret = TAKE_PTR(output);
- *ret_size = p;
-
+ *ret = TAKE_STRUCT(output);
return 0;
}
@@ -1069,39 +1178,39 @@ int decrypt_credential_and_warn(
usec_t validate_timestamp,
const char *tpm2_device,
const char *tpm2_signature_path,
- const void *input,
- size_t input_size,
- void **ret,
- size_t *ret_size) {
+ uid_t uid,
+ const struct iovec *input,
+ CredentialFlags flags,
+ struct iovec *ret) {
- _cleanup_(erase_and_freep) void *host_key = NULL, *tpm2_key = NULL, *plaintext = NULL;
+ _cleanup_(iovec_done_erase) struct iovec host_key = {}, plaintext = {}, tpm2_key = {};
_cleanup_(json_variant_unrefp) JsonVariant *signature_json = NULL;
_cleanup_(EVP_CIPHER_CTX_freep) EVP_CIPHER_CTX *context = NULL;
- size_t host_key_size = 0, tpm2_key_size = 0, plaintext_size, p, hs;
struct encrypted_credential_header *h;
struct metadata_credential_header *m;
uint8_t md[SHA256_DIGEST_LENGTH];
- bool with_tpm2, with_host_key, is_tpm2_absent, with_tpm2_pk;
+ bool with_tpm2, with_tpm2_pk, with_host_key, with_null, with_scope;
const EVP_CIPHER *cc;
+ size_t p, hs;
int r, added;
- assert(input || input_size == 0);
+ assert(iovec_is_valid(input));
assert(ret);
- assert(ret_size);
- h = (struct encrypted_credential_header*) input;
+ h = (struct encrypted_credential_header*) input->iov_base;
/* The ID must fit in, for the current and all future formats */
- if (input_size < sizeof(h->id))
+ if (input->iov_len < sizeof(h->id))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short.");
- with_host_key = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_HOST, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK);
- with_tpm2_pk = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK);
- with_tpm2 = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC) || with_tpm2_pk;
- is_tpm2_absent = sd_id128_equal(h->id, CRED_AES256_GCM_BY_TPM2_ABSENT);
+ with_host_key = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_HOST, CRED_AES256_GCM_BY_HOST_SCOPED, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED);
+ with_tpm2_pk = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED);
+ with_tpm2 = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED) || with_tpm2_pk;
+ with_null = sd_id128_equal(h->id, CRED_AES256_GCM_BY_NULL);
+ with_scope = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_HOST_SCOPED, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED);
- if (!with_host_key && !with_tpm2 && !is_tpm2_absent)
- return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unknown encryption format, or corrupted data: %m");
+ if (!with_host_key && !with_tpm2 && !with_null)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unknown encryption format, or corrupted data.");
if (with_tpm2_pk) {
r = tpm2_load_pcr_signature(tpm2_signature_path, &signature_json);
@@ -1109,7 +1218,7 @@ int decrypt_credential_and_warn(
return log_error_errno(r, "Failed to load pcr signature: %m");
}
- if (is_tpm2_absent) {
+ 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
@@ -1129,8 +1238,19 @@ int decrypt_credential_and_warn(
log_debug("Credential uses fixed key for use when TPM2 is absent, and TPM2 indeed is absent. Accepting.");
}
+ if (with_scope) {
+ if (!uid_is_valid(uid))
+ return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Encrypted file is scoped to a user, but no user selected.");
+ } else {
+ /* Refuse to unlock system credentials if user scope is requested. */
+ if (uid_is_valid(uid) && !FLAGS_SET(flags, CREDENTIAL_ANY_SCOPE))
+ return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Encrypted file is scoped to the system, but user scope selected.");
+
+ uid = UID_INVALID;
+ }
+
/* Now we know the minimum header size */
- if (input_size < offsetof(struct encrypted_credential_header, iv))
+ if (input->iov_len < offsetof(struct encrypted_credential_header, iv))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short.");
/* Verify some basic header values */
@@ -1145,10 +1265,11 @@ int decrypt_credential_and_warn(
/* Ensure we have space for the full header now (we don't know the size of the name hence this is a
* lower limit only) */
- if (input_size <
+ if (input->iov_len <
ALIGN8(offsetof(struct encrypted_credential_header, iv) + le32toh(h->iv_size)) +
ALIGN8(with_tpm2 ? offsetof(struct tpm2_credential_header, policy_hash_and_blob) : 0) +
ALIGN8(with_tpm2_pk ? offsetof(struct tpm2_public_key_credential_header, data) : 0) +
+ ALIGN8(with_scope ? sizeof(struct scoped_credential_header) : 0) +
ALIGN8(offsetof(struct metadata_credential_header, name)) +
le32toh(h->tag_size))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short.");
@@ -1157,7 +1278,7 @@ int decrypt_credential_and_warn(
if (with_tpm2) {
#if HAVE_TPM2
- struct tpm2_credential_header* t = (struct tpm2_credential_header*) ((uint8_t*) input + p);
+ struct tpm2_credential_header* t = (struct tpm2_credential_header*) ((uint8_t*) input->iov_base + p);
struct tpm2_public_key_credential_header *z = NULL;
if (!TPM2_PCR_MASK_VALID(t->pcr_mask))
@@ -1173,10 +1294,11 @@ int decrypt_credential_and_warn(
/* Ensure we have space for the full TPM2 header now (still don't know the name, and its size
* though, hence still just a lower limit test only) */
- if (input_size <
- ALIGN8(offsetof(struct encrypted_credential_header, iv) + le32toh(h->iv_size)) +
+ if (input->iov_len <
+ p +
ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) + le32toh(t->blob_size) + le32toh(t->policy_hash_size)) +
ALIGN8(with_tpm2_pk ? offsetof(struct tpm2_public_key_credential_header, data) : 0) +
+ ALIGN8(with_scope ? sizeof(struct scoped_credential_header) : 0) +
ALIGN8(offsetof(struct metadata_credential_header, name)) +
le32toh(h->tag_size))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short.");
@@ -1186,17 +1308,17 @@ int decrypt_credential_and_warn(
le32toh(t->policy_hash_size));
if (with_tpm2_pk) {
- z = (struct tpm2_public_key_credential_header*) ((uint8_t*) input + p);
+ z = (struct tpm2_public_key_credential_header*) ((uint8_t*) input->iov_base + p);
if (!TPM2_PCR_MASK_VALID(le64toh(z->pcr_mask)) || le64toh(z->pcr_mask) == 0)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "TPM2 PCR mask out of range.");
if (le32toh(z->size) > PUBLIC_KEY_MAX)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Unexpected public key size.");
- if (input_size <
- ALIGN8(offsetof(struct encrypted_credential_header, iv) + le32toh(h->iv_size)) +
- ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) + le32toh(t->blob_size) + le32toh(t->policy_hash_size)) +
+ if (input->iov_len <
+ p +
ALIGN8(offsetof(struct tpm2_public_key_credential_header, data) + le32toh(z->size)) +
+ ALIGN8(with_scope ? sizeof(struct scoped_credential_header) : 0) +
ALIGN8(offsetof(struct metadata_credential_header, name)) +
le32toh(h->tag_size))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short.");
@@ -1215,21 +1337,16 @@ int decrypt_credential_and_warn(
r = tpm2_unseal(tpm2_context,
le64toh(t->pcr_mask),
le16toh(t->pcr_bank),
- z ? z->data : NULL,
- z ? le32toh(z->size) : 0,
+ z ? &IOVEC_MAKE(z->data, le32toh(z->size)) : NULL,
z ? le64toh(z->pcr_mask) : 0,
signature_json,
/* pin= */ NULL,
/* pcrlock_policy= */ NULL,
le16toh(t->primary_alg),
- t->policy_hash_and_blob,
- le32toh(t->blob_size),
- t->policy_hash_and_blob + le32toh(t->blob_size),
- le32toh(t->policy_hash_size),
- /* srk_buf= */ NULL,
- /* srk_buf_size= */ 0,
- &tpm2_key,
- &tpm2_key_size);
+ &IOVEC_MAKE(t->policy_hash_and_blob, le32toh(t->blob_size)),
+ &IOVEC_MAKE(t->policy_hash_and_blob + le32toh(t->blob_size), le32toh(t->policy_hash_size)),
+ /* srk= */ NULL,
+ &tpm2_key);
if (r < 0)
return log_error_errno(r, "Failed to unseal secret using TPM2: %m");
#else
@@ -1237,19 +1354,38 @@ int decrypt_credential_and_warn(
#endif
}
+ if (with_scope) {
+ struct scoped_credential_header* sh = (struct scoped_credential_header*) ((uint8_t*) input->iov_base + p);
+
+ if (le64toh(sh->flags) != SCOPE_HASH_DATA_BASE_FLAGS)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Scoped credential with unsupported flags.");
+
+ if (input->iov_len <
+ p +
+ sizeof(struct scoped_credential_header) +
+ ALIGN8(offsetof(struct metadata_credential_header, name)) +
+ le32toh(h->tag_size))
+ return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short.");
+
+ p += sizeof(struct scoped_credential_header);
+ }
+
if (with_host_key) {
- r = get_credential_host_secret(
- 0,
- &host_key,
- &host_key_size);
+ r = get_credential_host_secret(/* flags= */ 0, &host_key);
if (r < 0)
return log_error_errno(r, "Failed to determine local credential key: %m");
}
- if (is_tpm2_absent)
+ 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, host_key_size, tpm2_key, tpm2_key_size, md);
+ sha256_hash_host_and_tpm2_key(&host_key, &tpm2_key, md);
+
+ if (with_scope) {
+ r = mangle_uid_into_key(uid, md);
+ if (r < 0)
+ return r;
+ }
assert_se(cc = EVP_aes_256_gcm());
@@ -1276,41 +1412,41 @@ int decrypt_credential_and_warn(
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set IV and key: %s",
ERR_error_string(ERR_get_error(), NULL));
- if (EVP_DecryptUpdate(context, NULL, &added, input, p) != 1)
+ if (EVP_DecryptUpdate(context, NULL, &added, input->iov_base, p) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to write AAD data: %s",
ERR_error_string(ERR_get_error(), NULL));
- plaintext = malloc(input_size - p - le32toh(h->tag_size));
- if (!plaintext)
+ plaintext.iov_base = malloc(input->iov_len - p - le32toh(h->tag_size));
+ if (!plaintext.iov_base)
return -ENOMEM;
if (EVP_DecryptUpdate(
context,
- plaintext,
+ plaintext.iov_base,
&added,
- (uint8_t*) input + p,
- input_size - p - le32toh(h->tag_size)) != 1)
+ (uint8_t*) input->iov_base + p,
+ input->iov_len - p - le32toh(h->tag_size)) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to decrypt data: %s",
ERR_error_string(ERR_get_error(), NULL));
assert(added >= 0);
- assert((size_t) added <= input_size - p - le32toh(h->tag_size));
- plaintext_size = added;
+ assert((size_t) added <= input->iov_len - p - le32toh(h->tag_size));
+ plaintext.iov_len = added;
- if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_TAG, le32toh(h->tag_size), (uint8_t*) input + input_size - le32toh(h->tag_size)) != 1)
+ if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_TAG, le32toh(h->tag_size), (uint8_t*) input->iov_base + input->iov_len - le32toh(h->tag_size)) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set tag: %s",
ERR_error_string(ERR_get_error(), NULL));
- if (EVP_DecryptFinal_ex(context, (uint8_t*) plaintext + plaintext_size, &added) != 1)
+ if (EVP_DecryptFinal_ex(context, (uint8_t*) plaintext.iov_base + plaintext.iov_len, &added) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Decryption failed (incorrect key?): %s",
ERR_error_string(ERR_get_error(), NULL));
- plaintext_size += added;
+ plaintext.iov_len += added;
- if (plaintext_size < ALIGN8(offsetof(struct metadata_credential_header, name)))
+ if (plaintext.iov_len < ALIGN8(offsetof(struct metadata_credential_header, name)))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Metadata header incomplete.");
- m = plaintext;
+ m = plaintext.iov_base;
if (le64toh(m->timestamp) != USEC_INFINITY &&
le64toh(m->not_after) != USEC_INFINITY &&
@@ -1321,7 +1457,7 @@ int decrypt_credential_and_warn(
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Embedded credential name too long, refusing.");
hs = ALIGN8(offsetof(struct metadata_credential_header, name) + le32toh(m->name_size));
- if (plaintext_size < hs)
+ if (plaintext.iov_len < hs)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Metadata header incomplete.");
if (le32toh(m->name_size) > 0) {
@@ -1336,7 +1472,7 @@ int decrypt_credential_and_warn(
if (validate_name && !streq(embedded_name, validate_name)) {
- r = getenv_bool_secure("SYSTEMD_CREDENTIAL_VALIDATE_NAME");
+ r = secure_getenv_bool("SYSTEMD_CREDENTIAL_VALIDATE_NAME");
if (r < 0 && r != -ENXIO)
log_debug_errno(r, "Failed to parse $SYSTEMD_CREDENTIAL_VALIDATE_NAME: %m");
if (r != 0)
@@ -1352,7 +1488,7 @@ int decrypt_credential_and_warn(
if (le64toh(m->not_after) != USEC_INFINITY && le64toh(m->not_after) < validate_timestamp) {
- r = getenv_bool_secure("SYSTEMD_CREDENTIAL_VALIDATE_NOT_AFTER");
+ r = secure_getenv_bool("SYSTEMD_CREDENTIAL_VALIDATE_NOT_AFTER");
if (r < 0 && r != -ENXIO)
log_debug_errno(r, "Failed to parse $SYSTEMD_CREDENTIAL_VALIDATE_NOT_AFTER: %m");
if (r != 0)
@@ -1363,33 +1499,245 @@ int decrypt_credential_and_warn(
}
if (ret) {
- char *without_metadata;
+ _cleanup_(iovec_done_erase) struct iovec without_metadata = {};
- without_metadata = memdup((uint8_t*) plaintext + hs, plaintext_size - hs);
- if (!without_metadata)
+ without_metadata.iov_len = plaintext.iov_len - hs;
+ without_metadata.iov_base = memdup_suffix0((uint8_t*) plaintext.iov_base + hs, without_metadata.iov_len);
+ if (!without_metadata.iov_base)
return log_oom();
- *ret = without_metadata;
+ *ret = TAKE_STRUCT(without_metadata);
}
- if (ret_size)
- *ret_size = plaintext_size - hs;
-
return 0;
}
#else
-int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t *ret_size) {
+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 void *input, size_t input_size, void **ret, size_t *ret_size) {
+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, uid_t uid, 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 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 char *tpm2_signature_path, uid_t uid, const struct iovec *input, CredentialFlags flags, struct iovec *ret) {
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Support for encrypted credentials not available.");
}
#endif
+
+int ipc_encrypt_credential(const char *name, usec_t timestamp, usec_t not_after, uid_t uid, const struct iovec *input, CredentialFlags flags, struct iovec *ret) {
+ _cleanup_(varlink_unrefp) Varlink *vl = NULL;
+ int r;
+
+ assert(input && iovec_is_valid(input));
+ assert(ret);
+
+ r = varlink_connect_address(&vl, "/run/systemd/io.systemd.Credentials");
+ if (r < 0)
+ return log_error_errno(r, "Failed to connect to io.systemd.Credentials: %m");
+
+ /* Mark anything we get from the service as sensitive, given that it might use a NULL cypher, at least in theory */
+ r = varlink_set_input_sensitive(vl);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enable sensitive Varlink input: %m");
+
+ /* Create the input data blob object separately, so that we can mark it as sensitive */
+ _cleanup_(json_variant_unrefp) JsonVariant *jinput = NULL;
+ r = json_build(&jinput, JSON_BUILD_IOVEC_BASE64(input));
+ if (r < 0)
+ return log_error_errno(r, "Failed to create input object: %m");
+
+ json_variant_sensitive(jinput);
+
+ _cleanup_(json_variant_unrefp) JsonVariant *reply = NULL;
+ const char *error_id = NULL;
+ r = varlink_callb(vl,
+ "io.systemd.Credentials.Encrypt",
+ &reply,
+ &error_id,
+ JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR_CONDITION(name, "name", JSON_BUILD_STRING(name)),
+ JSON_BUILD_PAIR("data", JSON_BUILD_VARIANT(jinput)),
+ JSON_BUILD_PAIR_CONDITION(timestamp != USEC_INFINITY, "timestamp", JSON_BUILD_UNSIGNED(timestamp)),
+ JSON_BUILD_PAIR_CONDITION(not_after != USEC_INFINITY, "notAfter", JSON_BUILD_UNSIGNED(not_after)),
+ JSON_BUILD_PAIR_CONDITION(!FLAGS_SET(flags, CREDENTIAL_ANY_SCOPE), "scope", JSON_BUILD_STRING(uid_is_valid(uid) ? "user" : "system")),
+ JSON_BUILD_PAIR_CONDITION(uid_is_valid(uid), "uid", JSON_BUILD_UNSIGNED(uid))));
+ if (r < 0)
+ return log_error_errno(r, "Failed to call Encrypt() varlink call.");
+ if (!isempty(error_id)) {
+ if (streq(error_id, "io.systemd.Credentials.NoSuchUser"))
+ return log_error_errno(SYNTHETIC_ERRNO(ESRCH), "No such user.");
+
+ return log_error_errno(varlink_error_to_errno(error_id, reply), "Failed to encrypt: %s", error_id);
+ }
+
+ r = json_dispatch(
+ reply,
+ (const JsonDispatch[]) {
+ { "blob", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, PTR_TO_SIZE(ret), JSON_MANDATORY },
+ {},
+ },
+ JSON_LOG|JSON_ALLOW_EXTENSIONS,
+ /* userdata= */ NULL);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+int ipc_decrypt_credential(const char *validate_name, usec_t validate_timestamp, uid_t uid, const struct iovec *input, CredentialFlags flags, struct iovec *ret) {
+ _cleanup_(varlink_unrefp) Varlink *vl = NULL;
+ int r;
+
+ assert(input && iovec_is_valid(input));
+ assert(ret);
+
+ r = varlink_connect_address(&vl, "/run/systemd/io.systemd.Credentials");
+ if (r < 0)
+ return log_error_errno(r, "Failed to connect to io.systemd.Credentials: %m");
+
+ r = varlink_set_input_sensitive(vl);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enable sensitive Varlink input: %m");
+
+ /* Create the input data blob object separately, so that we can mark it as sensitive (it's supposed
+ * to be encrypted, but who knows maybe it uses the NULL cypher). */
+ _cleanup_(json_variant_unrefp) JsonVariant *jinput = NULL;
+ r = json_build(&jinput, JSON_BUILD_IOVEC_BASE64(input));
+ if (r < 0)
+ return log_error_errno(r, "Failed to create input object: %m");
+
+ json_variant_sensitive(jinput);
+
+ _cleanup_(json_variant_unrefp) JsonVariant *reply = NULL;
+ const char *error_id = NULL;
+ r = varlink_callb(vl,
+ "io.systemd.Credentials.Decrypt",
+ &reply,
+ &error_id,
+ JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR_CONDITION(validate_name, "name", JSON_BUILD_STRING(validate_name)),
+ JSON_BUILD_PAIR("blob", JSON_BUILD_VARIANT(jinput)),
+ JSON_BUILD_PAIR_CONDITION(validate_timestamp != USEC_INFINITY, "timestamp", JSON_BUILD_UNSIGNED(validate_timestamp)),
+ JSON_BUILD_PAIR_CONDITION(!FLAGS_SET(flags, CREDENTIAL_ANY_SCOPE), "scope", JSON_BUILD_STRING(uid_is_valid(uid) ? "user" : "system")),
+ JSON_BUILD_PAIR_CONDITION(uid_is_valid(uid), "uid", JSON_BUILD_UNSIGNED(uid))));
+ if (r < 0)
+ return log_error_errno(r, "Failed to call Decrypt() varlink call.");
+ if (!isempty(error_id)) {
+ if (streq(error_id, "io.systemd.Credentials.BadFormat"))
+ return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Bad credential format.");
+ if (streq(error_id, "io.systemd.Credentials.NameMismatch"))
+ return log_error_errno(SYNTHETIC_ERRNO(EREMOTE), "Name in credential doesn't match expectations.");
+ if (streq(error_id, "io.systemd.Credentials.TimeMismatch"))
+ return log_error_errno(SYNTHETIC_ERRNO(ESTALE), "Outside of credential validity time window.");
+ if (streq(error_id, "io.systemd.Credentials.NoSuchUser"))
+ return log_error_errno(SYNTHETIC_ERRNO(ESRCH), "No such user.");
+ if (streq(error_id, "io.systemd.Credentials.BadScope"))
+ return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Scope mismtach.");
+
+ return log_error_errno(varlink_error_to_errno(error_id, reply), "Failed to decrypt: %s", error_id);
+ }
+
+ r = json_dispatch(
+ reply,
+ (const JsonDispatch[]) {
+ { "data", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, PTR_TO_SIZE(ret), JSON_MANDATORY },
+ {},
+ },
+ JSON_LOG|JSON_ALLOW_EXTENSIONS,
+ /* userdata= */ NULL);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+static int pick_up_credential_one(
+ int credential_dir_fd,
+ const char *credential_name,
+ const PickUpCredential *table_entry) {
+
+ _cleanup_free_ char *fn = NULL, *target_path = NULL;
+ const char *e;
+ int r;
+
+ assert(credential_dir_fd >= 0);
+ assert(credential_name);
+ assert(table_entry);
+
+ e = startswith(credential_name, table_entry->credential_prefix);
+ if (!e)
+ return 0; /* unmatched */
+
+ fn = strjoin(e, table_entry->filename_suffix);
+ if (!fn)
+ return log_oom();
+
+ if (!filename_is_valid(fn))
+ return log_warning_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Passed credential '%s' would result in invalid filename '%s'.",
+ credential_name, fn);
+
+ r = mkdir_p_label(table_entry->target_dir, 0755);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to create '%s': %m", table_entry->target_dir);
+
+ target_path = path_join(table_entry->target_dir, fn);
+ if (!target_path)
+ return log_oom();
+
+ r = copy_file_at(
+ credential_dir_fd, credential_name,
+ AT_FDCWD, target_path,
+ /* open_flags= */ 0,
+ 0644,
+ /* flags= */ 0);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to copy credential %s → file %s: %m",
+ credential_name, target_path);
+
+ log_info("Installed %s from credential.", target_path);
+ return 1; /* done */
+}
+
+int pick_up_credentials(const PickUpCredential *table, size_t n_table_entry) {
+ _cleanup_close_ int credential_dir_fd = -EBADF;
+ int r, ret = 0;
+
+ assert(table);
+ assert(n_table_entry > 0);
+
+ credential_dir_fd = open_credentials_dir();
+ if (IN_SET(credential_dir_fd, -ENXIO, -ENOENT)) {
+ /* Credential env var not set, or dir doesn't exist. */
+ log_debug("No credentials found.");
+ return 0;
+ }
+ if (credential_dir_fd < 0)
+ return log_error_errno(credential_dir_fd, "Failed to open credentials directory: %m");
+
+ _cleanup_free_ DirectoryEntries *des = NULL;
+ r = readdir_all(credential_dir_fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE, &des);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enumerate credentials: %m");
+
+ FOREACH_ARRAY(i, des->entries, des->n_entries) {
+ struct dirent *de = *i;
+
+ if (de->d_type != DT_REG)
+ continue;
+
+ FOREACH_ARRAY(t, table, n_table_entry) {
+ r = pick_up_credential_one(credential_dir_fd, de->d_name, t);
+ if (r != 0) {
+ RET_GATHER(ret, r);
+ break; /* Done, or failed. Let's move to the next credential. */
+ }
+ }
+ }
+
+ return ret;
+}