## Summary Closes #18280. `systemd-homed` currently writes fscrypt v1 policies, which bind the master key to the calling process's keyring. This means files in a homed-managed directory aren't readable when accessed through a container bind mount, a different mount namespace, or by any process other than the one that first unlocked the home. Reading the file from a context that has the key first warms the page cache, but the underlying problem remains. v2 policies (Linux 5.4+) route the master key through the filesystem keyring, via `FS_IOC_ADD_ENCRYPTION_KEY` / `FS_IOC_REMOVE_ENCRYPTION_KEY`, so the key is visible to every process accessing the filesystem. This PR switches `systemd-homed` to v2 by default and keeps existing v1 homes working. ## Changes **`shared/crypto-util`**: Add `kdf_hkdf_derive` (HKDF, RFC 5869) wrapper around OpenSSL's `EVP_KDF` "HKDF", alongside the existing SSKDF/KBKDF helpers. **`homed/fscrypt`**: - Read the existing policy via `FS_IOC_GET_ENCRYPTION_POLICY_EX`, falling back to the legacy ioctl on pre-5.4 kernels. - `HomeSetup` now carries a full `struct fscrypt_key_specifier` instead of a bare 8-byte descriptor. Slot decryption derives either the v1 descriptor (double SHA-512) or the v2 identifier (HKDF-SHA512 with the kernel's info string) from the unwrapped volume key and compares against the policy. - Install the master key the right way per version: `add_key("logon", ...)` to thread+user keyrings for v1, `FS_IOC_ADD_ENCRYPTION_KEY` for v2. - `home_flush_keyring_fscrypt` opens the image directory, reads the policy version, and either calls `FS_IOC_REMOVE_ENCRYPTION_KEY` (v2) or walks the user keyring (v1). - New homes default to v2; fall back to v1 only when the kernel rejects v2. The on-disk slot xattr format is unchanged - the volume key is the same, only how it binds to the directory changes. There is no v1 -> v2 migration; existing v1 homes continue to unlock, rekey, and deactivate as before.
This commit is contained in:
+43
-31
@@ -1343,10 +1343,13 @@ int kdf_argon2id_derive(
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Perform HKDF-SHA256 derivation, producing derive_size bytes of output.
|
||||
/* Perform HKDF (RFC 5869). 'key' is the input keying material (required, non-empty). 'salt' and 'info'
|
||||
* are optional: pass NULL or an empty iovec to omit. The output is written to 'ret' as a fresh allocation
|
||||
* of 'derive_size' bytes (must be ≤ 255 × HashLen).
|
||||
*
|
||||
* For more details see: https://docs.openssl.org/master/man7/EVP_KDF-HKDF.html */
|
||||
int kdf_hkdf_sha256(
|
||||
* For more details see: https://www.openssl.org/docs/manmaster/man7/EVP_KDF-HKDF.html */
|
||||
int kdf_hkdf_derive(
|
||||
const char *digest,
|
||||
const struct iovec *key,
|
||||
const struct iovec *salt,
|
||||
const struct iovec *info,
|
||||
@@ -1355,9 +1358,10 @@ int kdf_hkdf_sha256(
|
||||
|
||||
int r;
|
||||
|
||||
assert(!key || key->iov_len > 0);
|
||||
assert(!salt || salt->iov_len > 0);
|
||||
assert(!info || info->iov_len > 0);
|
||||
assert(digest);
|
||||
assert(iovec_is_set(key));
|
||||
assert(iovec_is_valid(salt));
|
||||
assert(iovec_is_valid(info));
|
||||
assert(derive_size > 0);
|
||||
assert(ret);
|
||||
|
||||
@@ -1365,9 +1369,9 @@ int kdf_hkdf_sha256(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_(EVP_KDF_freep) EVP_KDF *kdf = sym_EVP_KDF_fetch(/* propq= */ NULL, "HKDF", /* propq= */ NULL);
|
||||
_cleanup_(EVP_KDF_freep) EVP_KDF *kdf = sym_EVP_KDF_fetch(/* libctx= */ NULL, "HKDF", /* properties= */ NULL);
|
||||
if (!kdf)
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to create new EVP_KDF for HKDF");
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to create new EVP_KDF");
|
||||
|
||||
_cleanup_(EVP_KDF_CTX_freep) EVP_KDF_CTX *ctx = sym_EVP_KDF_CTX_new(kdf);
|
||||
if (!ctx)
|
||||
@@ -1377,38 +1381,46 @@ int kdf_hkdf_sha256(
|
||||
if (!bld)
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to create new OSSL_PARAM_BLD");
|
||||
|
||||
if (!sym_OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_KDF_PARAM_DIGEST, (char*) digest, /* bsize= */ 0))
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to add HKDF OSSL_KDF_PARAM_DIGEST");
|
||||
|
||||
if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_KEY, key->iov_base, key->iov_len))
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to add HKDF OSSL_KDF_PARAM_KEY");
|
||||
|
||||
if (iovec_is_set(salt))
|
||||
if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_SALT, salt->iov_base, salt->iov_len))
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to add HKDF OSSL_KDF_PARAM_SALT");
|
||||
|
||||
if (iovec_is_set(info))
|
||||
if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_INFO, info->iov_base, info->iov_len))
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to add HKDF OSSL_KDF_PARAM_INFO");
|
||||
|
||||
_cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = sym_OSSL_PARAM_BLD_to_param(bld);
|
||||
if (!params)
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to build HKDF OSSL_PARAM");
|
||||
|
||||
_cleanup_(erase_and_freep) void *buf = malloc(derive_size);
|
||||
if (!buf)
|
||||
return log_oom_debug();
|
||||
|
||||
if (!sym_OSSL_PARAM_BLD_push_utf8_string(bld, "digest", "SHA256", 0))
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to add HKDF digest");
|
||||
|
||||
if (key)
|
||||
if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, "key", key->iov_base, key->iov_len))
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to add HKDF key");
|
||||
|
||||
if (salt)
|
||||
if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, "salt", salt->iov_base, salt->iov_len))
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to add HKDF salt");
|
||||
|
||||
if (info)
|
||||
if (!sym_OSSL_PARAM_BLD_push_octet_string(bld, "info", info->iov_base, info->iov_len))
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to add HKDF info");
|
||||
|
||||
_cleanup_(OSSL_PARAM_freep) OSSL_PARAM *openssl_params = sym_OSSL_PARAM_BLD_to_param(bld);
|
||||
if (!openssl_params)
|
||||
return log_openssl_errors(LOG_DEBUG, "Failed to build HKDF OSSL_PARAM");
|
||||
|
||||
if (sym_EVP_KDF_derive(ctx, buf, derive_size, openssl_params) <= 0)
|
||||
if (sym_EVP_KDF_derive(ctx, buf, derive_size, params) <= 0)
|
||||
return log_openssl_errors(LOG_DEBUG, "OpenSSL HKDF derive failed");
|
||||
|
||||
ret->iov_base = TAKE_PTR(buf);
|
||||
ret->iov_len = derive_size;
|
||||
|
||||
*ret = IOVEC_MAKE(TAKE_PTR(buf), derive_size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Perform HKDF-SHA256 derivation, producing derive_size bytes of output. */
|
||||
int kdf_hkdf_sha256(
|
||||
const struct iovec *key,
|
||||
const struct iovec *salt,
|
||||
const struct iovec *info,
|
||||
size_t derive_size,
|
||||
struct iovec *ret) {
|
||||
|
||||
return kdf_hkdf_derive("SHA256", key, salt, info, derive_size, ret);
|
||||
}
|
||||
|
||||
/* Encrypt the key data using RSA-OAEP with the provided label and specified digest algorithm. Returns 0 on
|
||||
* success, -EOPNOTSUPP if the digest algorithm is not supported, or < 0 for any other error. */
|
||||
int rsa_oaep_encrypt_bytes(
|
||||
|
||||
@@ -392,6 +392,8 @@ int kdf_ss_derive(const char *digest, const void *key, size_t key_size, const vo
|
||||
|
||||
int kdf_kb_hmac_derive(const char *mode, const char *digest, const void *key, size_t key_size, const void *salt, size_t salt_size, const void *info, size_t info_size, const void *seed, size_t seed_size, size_t derive_size, void **ret);
|
||||
|
||||
int kdf_hkdf_derive(const char *digest, const struct iovec *key, const struct iovec *salt, const struct iovec *info, size_t derive_size, struct iovec *ret);
|
||||
|
||||
int rsa_oaep_encrypt_bytes(const EVP_PKEY *pkey, const char *digest_alg, const char *label, const void *decrypted_key, size_t decrypted_key_size, void **ret_encrypt_key, size_t *ret_encrypt_key_size);
|
||||
|
||||
int rsa_pkey_to_suitable_key_size(EVP_PKEY *pkey, size_t *ret_suitable_key_size);
|
||||
|
||||
Reference in New Issue
Block a user