]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
creds-util: add a concept of "user-scoped" credentials
authorLennart Poettering <lennart@poettering.net>
Mon, 15 Jan 2024 16:36:44 +0000 (17:36 +0100)
committerLennart Poettering <lennart@poettering.net>
Tue, 30 Jan 2024 16:07:47 +0000 (17:07 +0100)
So far credentials are a concept for system services only: to encrypt or
decrypt credential you must be privileged, as only then you can access
the TPM and the host key.

Let's break this up a bit: let's add a "user-scoped" credential, that
are specific to users. Internally this works by adding another step to
the acquisition of the symmetric encryption key for the credential: if a
"user-scoped" credential is used we'll generate an symmetric encryption
key K as usual, but then we'll use it to calculate

    K' = HMAC(K, flags || uid || machine-id || username)

and then use the resulting K' as encryption key instead. This basically
includes the (public) user's identity in the encryption key, ensuring
that only if the right user credentials are specified the correct key
can be acquired.

mime/io.systemd.xml
src/core/exec-credential.c
src/creds/creds.c
src/pcrlock/pcrlock.c
src/shared/creds-util.c
src/shared/creds-util.h
src/shared/tpm2-util.c
src/test/test-creds.c

index f362006a478a215548971ea6006c835567a1417c..8314569ed31734c53e2ef5e6dd2324dbed3dc10d 100644 (file)
     <generic-icon name="security-high"/>
     <magic>
       <match type="string" value="Whxqht+dQJax1aZeCGLxm" offset="0"/>
+      <match type="string" value="VbntHThZTUOoMZ0uuzMqx" offset="0"/>
       <match type="string" value="DHzAexF2RZGcSwvqCLwg/" offset="0"/>
       <match type="string" value="+vfrk0HjQSyhpDb5Wik2L" offset="0"/>
       <match type="string" value="k6iUCUh0RJCQyvL8k8q1U" offset="0"/>
+      <match type="string" value="70rBNnmpSA6n22iJf58WX" offset="0"/>
       <match type="string" value="r0lQqEkTTrGnOEYwT/MMB" offset="0"/>
+      <match type="string" value="rbxMo++2QgG6iBtvLkCV6" offset="0"/>
       <match type="string" value="BYRp2vb1QySABUnaD46i+" offset="0"/>
     </magic>
   </mime-type>
index 41c0fce13b5ebe8ae575c247f2fe73bb46be26ba..80ebd96f971f51d63210ddc0da264c193d14830a 100644 (file)
@@ -281,8 +281,9 @@ static int maybe_decrypt_and_write_credential(
                                 now(CLOCK_REALTIME),
                                 /* tpm2_device= */ NULL,
                                 /* tpm2_signature_path= */ NULL,
+                                getuid(),
                                 &IOVEC_MAKE(data, size),
-                                /* flags= */ 0,
+                                CREDENTIAL_ANY_SCOPE,
                                 &plaintext);
                 if (r < 0)
                         return r;
@@ -707,8 +708,9 @@ static int acquire_credentials(
                                         now(CLOCK_REALTIME),
                                         /* tpm2_device= */ NULL,
                                         /* tpm2_signature_path= */ NULL,
+                                        getuid(),
                                         &IOVEC_MAKE(sc->data, sc->size),
-                                        /* flags= */ 0,
+                                        CREDENTIAL_ANY_SCOPE,
                                         &plaintext);
                         if (r < 0)
                                 return r;
index bbc705c0069b5c9833e5847c9ed172d66d99563a..a02ea2c44c6bc5cc66bf92be3bdd55fc10980243 100644 (file)
@@ -428,8 +428,9 @@ static int verb_cat(int argc, char **argv, void *userdata) {
                                         timestamp,
                                         arg_tpm2_device,
                                         arg_tpm2_signature,
+                                        getuid(),
                                         &IOVEC_MAKE(data, size),
-                                        /* flags= */ 0,
+                                        CREDENTIAL_ANY_SCOPE,
                                         &plaintext);
                         if (r < 0)
                                 return r;
@@ -501,6 +502,7 @@ static int verb_encrypt(int argc, char **argv, void *userdata) {
                         arg_tpm2_pcr_mask,
                         arg_tpm2_public_key,
                         arg_tpm2_public_key_pcr_mask,
+                        /* uid= */ UID_INVALID,
                         &plaintext,
                         /* flags= */ 0,
                         &output);
@@ -590,6 +592,7 @@ static int verb_decrypt(int argc, char **argv, void *userdata) {
                         timestamp,
                         arg_tpm2_device,
                         arg_tpm2_signature,
+                        /* uid= */ UID_INVALID,
                         &input,
                         /* flags= */ 0,
                         &plaintext);
@@ -1029,6 +1032,7 @@ static int vl_method_encrypt(Varlink *link, JsonVariant *parameters, VarlinkMeth
                         arg_tpm2_pcr_mask,
                         arg_tpm2_public_key,
                         arg_tpm2_public_key_pcr_mask,
+                        /* uid= */ UID_INVALID,
                         p.text ? &IOVEC_MAKE_STRING(p.text) : &p.data,
                         /* flags= */ 0,
                         &output);
@@ -1101,6 +1105,7 @@ static int vl_method_decrypt(Varlink *link, JsonVariant *parameters, VarlinkMeth
                         p.timestamp,
                         arg_tpm2_device,
                         arg_tpm2_signature,
+                        /* uid= */ UID_INVALID,
                         &p.blob,
                         /* flags= */ 0,
                         &output);
index 329153c65e43ed0b5ace59f4be84c2fcf1c326b6..9a9da049b27d60b8cc85259b34ad09ddaf342664 100644 (file)
@@ -4268,6 +4268,7 @@ static int write_boot_policy_file(const char *json_text) {
                         /* tpm2_hash_pcr_mask= */ 0,
                         /* tpm2_pubkey_path= */ NULL,
                         /* tpm2_pubkey_path_mask= */ 0,
+                        UID_INVALID,
                         &IOVEC_MAKE_STRING(json_text),
                         CREDENTIAL_ALLOW_NULL,
                         &encoded);
index 0325f6e129387c9b59c52f71efdf74d051b2d471..2e9af638f722185076be6d55c447a7a2dbf6b00a 100644 (file)
@@ -17,6 +17,7 @@
 #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"
@@ -28,6 +29,7 @@
 #include "sparse-endian.h"
 #include "stat-util.h"
 #include "tpm2-util.h"
+#include "user-util.h"
 
 #define PUBLIC_KEY_MAX (UINT32_C(1024) * UINT32_C(1024))
 
@@ -189,10 +191,11 @@ int read_credential_with_decryption(const char *name, void **ret, size_t *ret_si
         r = decrypt_credential_and_warn(
                         name,
                         now(CLOCK_REALTIME),
-                        /* tpm2_device = */ NULL,
-                        /* tpm2_signature_path = */ NULL,
+                        /* tpm2_device= */ NULL,
+                        /* tpm2_signature_path= */ NULL,
+                        getuid(),
                         &IOVEC_MAKE(data, sz),
-                        /* flags= */ 0,
+                        CREDENTIAL_ANY_SCOPE,
                         &ret_iovec);
         if (r < 0)
                 return r;
@@ -665,6 +668,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;
@@ -673,6 +681,23 @@ 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. */
@@ -714,6 +739,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,
@@ -723,6 +800,7 @@ int encrypt_credential_and_warn(
                 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) {
@@ -745,11 +823,15 @@ int encrypt_credential_and_warn(
         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_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));
 
@@ -770,18 +852,32 @@ int encrypt_credential_and_warn(
                         log_debug("Including not-after timestamp '%s' in encrypted credential.", format_timestamp(buf, sizeof(buf), not_after));
         }
 
+        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),
+                                (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_equal(with_key, _CRED_AUTO))
+                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 +885,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,20 +898,24 @@ 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.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");
@@ -872,7 +972,7 @@ int encrypt_credential_and_warn(
                 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");
@@ -886,15 +986,18 @@ int encrypt_credential_and_warn(
         }
 #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 (iovec_is_set(&host_key) && iovec_is_set(&tpm2_key))
-                        id = iovec_is_set(&pubkey) ? CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK : CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC;
-                else if (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 = CRED_AES256_GCM_BY_HOST;
+                        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_NULL;
                 else
@@ -911,6 +1014,12 @@ int encrypt_credential_and_warn(
         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);
@@ -951,6 +1060,7 @@ int encrypt_credential_and_warn(
                 ALIGN8(offsetof(struct encrypted_credential_header, iv) + ivsz) +
                 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->iov_len + 2U * (size_t) bsz +
                 tsz;
@@ -995,7 +1105,16 @@ int encrypt_credential_and_warn(
                 p += ALIGN8(offsetof(struct tpm2_public_key_credential_header, data) + pubkey.iov_len);
         }
 
-        /* Pass the encrypted + TPM2 header as AAD */
+        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(sizeof(struct scoped_credential_header));
+        }
+
+        /* 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));
@@ -1066,6 +1185,7 @@ int decrypt_credential_and_warn(
                 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) {
@@ -1076,7 +1196,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_tpm2_pk, with_host_key, with_null;
+        bool with_tpm2, with_tpm2_pk, with_host_key, with_null, with_scope;
         const EVP_CIPHER *cc;
         size_t p, hs;
         int r, added;
@@ -1090,10 +1210,11 @@ int decrypt_credential_and_warn(
         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;
+        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 && !with_null)
                 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unknown encryption format, or corrupted data: %m");
@@ -1124,6 +1245,17 @@ 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->iov_len < offsetof(struct encrypted_credential_header, iv))
                 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short.");
@@ -1144,6 +1276,7 @@ int decrypt_credential_and_warn(
             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.");
@@ -1172,6 +1305,7 @@ int decrypt_credential_and_warn(
                     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.");
@@ -1191,6 +1325,7 @@ int decrypt_credential_and_warn(
                         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.");
@@ -1226,6 +1361,22 @@ 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(/* flags= */ 0, &host_key);
                 if (r < 0)
@@ -1237,6 +1388,12 @@ int decrypt_credential_and_warn(
 
         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());
 
         /* Make sure cipher expectations match the header */
@@ -1368,11 +1525,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, CredentialFlags flags, 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, 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 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, 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.");
 }
 
index 9362d4e52c4c72109fe563490eedadf553803f0b..bd189e8adb3c309dba5ce406e6848d9346f36e83 100644 (file)
@@ -59,6 +59,7 @@ int get_credential_user_password(const char *username, char **ret_password, bool
 
 typedef enum CredentialFlags {
         CREDENTIAL_ALLOW_NULL = 1 << 0, /* allow decryption of NULL key, even if TPM is around */
+        CREDENTIAL_ANY_SCOPE  = 1 << 1, /* allow decryption of both system and user credentials */
 } CredentialFlags;
 
 /* The four modes we support: keyed only by on-disk key, only by TPM2 HMAC key, and by the combination of
@@ -66,11 +67,16 @@ typedef enum CredentialFlags {
  * 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_HOST_SCOPED        SD_ID128_MAKE(55,b9,ed,1d,38,59,4d,43,a8,31,9d,2e,bb,33,2a,c6)
 #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_TPM2_HMAC_WITH_PK  SD_ID128_MAKE(fa,f7,eb,93,41,e3,41,2c,a1,a4,36,f9,5a,29,36,2f)
 #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_HOST_AND_TPM2_HMAC_SCOPED            \
+                                              SD_ID128_MAKE(ef,4a,c1,36,79,a9,48,0e,a7,db,68,89,7f,9f,16,5d)
 #define CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK           \
                                               SD_ID128_MAKE(af,49,50,a8,49,13,4e,b1,a7,38,46,30,4f,f3,0c,05)
+#define CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED    \
+                                              SD_ID128_MAKE(ad,bc,4c,a3,ef,b6,42,01,ba,88,1b,6f,2e,40,95,ea)
 #define CRED_AES256_GCM_BY_NULL               SD_ID128_MAKE(05,84,69,da,f6,f5,43,24,80,05,49,da,0f,8e,a2,fb)
 
 /* Two special IDs to pick a general automatic mode (i.e. tpm2+host if TPM2 exists, only host otherwise) or
@@ -80,6 +86,7 @@ typedef enum CredentialFlags {
  * 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)
+#define _CRED_AUTO_SCOPED                     SD_ID128_MAKE(23,88,96,85,6f,74,48,8a,9c,78,6f,6a,b0,e7,3b,6a)
 
-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);
+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);
+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);
index 713ea2a49567f29c953c5f8249d64dd97f95f7bf..bd503c5c48768a0ac4991628d90b817a5de2c14b 100644 (file)
@@ -6891,6 +6891,7 @@ static int pcrlock_policy_load_credential(
                         now(CLOCK_REALTIME),
                         /* tpm2_device= */ NULL,
                         /* tpm2_signature_path= */ NULL,
+                        UID_INVALID,
                         data,
                         CREDENTIAL_ALLOW_NULL,
                         &decoded);
index e65aa819dd5751ee581522def3e60af6db20501d..b4beafc31d6279ff2ad2e4059cb4083fe451b844 100644 (file)
@@ -11,6 +11,7 @@
 #include "tests.h"
 #include "tmpfile-util.h"
 #include "tpm2-util.h"
+#include "user-util.h"
 
 TEST(read_credential_strings) {
         _cleanup_free_ char *x = NULL, *y = NULL, *saved = NULL, *p = NULL;
@@ -119,11 +120,14 @@ TEST(credential_glob_valid) {
         assert_se(credential_glob_valid(buf));
 }
 
-static void test_encrypt_decrypt_with(sd_id128_t mode) {
+static void test_encrypt_decrypt_with(sd_id128_t mode, uid_t uid) {
         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));
+        if (uid_is_valid(uid))
+                log_notice("Running encryption/decryption test with mode " SD_ID128_FORMAT_STR " for UID " UID_FMT ".", SD_ID128_FORMAT_VAL(mode), uid);
+        else
+                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(
@@ -135,6 +139,7 @@ static void test_encrypt_decrypt_with(sd_id128_t mode) {
                         /* tpm2_hash_pcr_mask= */ 0,
                         /* tpm2_pubkey_path= */ NULL,
                         /* tpm2_pubkey_pcr_mask= */ 0,
+                        uid,
                         &plaintext,
                         CREDENTIAL_ALLOW_NULL,
                         &encrypted);
@@ -155,6 +160,7 @@ static void test_encrypt_decrypt_with(sd_id128_t mode) {
                         /* validate_timestamp= */ USEC_INFINITY,
                         /* tpm2_device= */ NULL,
                         /* tpm2_signature_path= */ NULL,
+                        uid,
                         &encrypted,
                         CREDENTIAL_ALLOW_NULL,
                         &decrypted);
@@ -165,6 +171,7 @@ static void test_encrypt_decrypt_with(sd_id128_t mode) {
                         /* validate_timestamp= */ USEC_INFINITY,
                         /* tpm2_device= */ NULL,
                         /* tpm2_signature_path= */ NULL,
+                        uid,
                         &encrypted,
                         CREDENTIAL_ALLOW_NULL,
                         &decrypted);
@@ -192,7 +199,9 @@ 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);
+        log_set_max_level(LOG_DEBUG);
+
+        test_encrypt_decrypt_with(CRED_AES256_GCM_BY_NULL, UID_INVALID);
 
         assert_se(mkdtemp_malloc(NULL, &d) >= 0);
         j = path_join(d, "secret");
@@ -206,11 +215,13 @@ TEST(credential_encrypt_decrypt) {
 
         assert_se(setenv("SYSTEMD_CREDENTIAL_SECRET", j, true) >= 0);
 
-        test_encrypt_decrypt_with(CRED_AES256_GCM_BY_HOST);
+        test_encrypt_decrypt_with(CRED_AES256_GCM_BY_HOST, UID_INVALID);
+        test_encrypt_decrypt_with(CRED_AES256_GCM_BY_HOST_SCOPED, 0);
 
         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);
+                test_encrypt_decrypt_with(CRED_AES256_GCM_BY_TPM2_HMAC, UID_INVALID);
+                test_encrypt_decrypt_with(CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC, UID_INVALID);
+                test_encrypt_decrypt_with(CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED, 0);
         }
 
         if (ec)
@@ -221,10 +232,13 @@ TEST(mime_type_matches) {
 
         static const sd_id128_t tags[] = {
                 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_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED,
                 CRED_AES256_GCM_BY_NULL,
         };