cryptenroll/cryptsetup: allow combined signed TPM2 PCR policy + pcrlock policy

So far you had to pick:

1. Use a signed PCR TPM2 policy to lock your disk to (i.e. UKI vendor
   blesses your setup via signature)
or
2. Use a pcrlock policy (i.e. local system blesses your setup via
   dynamic local policy stored in NV index)

It was not possible combine these two, because TPM2 access policies do
not allow the combination of PolicyAuthorize (used to implement #1
above) and PolicyAuthorizeNV (used to implement #2) in a single policy,
unless one is "further upstream" (and can simply remove the other from
the policy freely).

This is quite limiting of course, since we actually do want to enforce
on each TPM object that both the OS vendor policy and the local policy
must be fulfilled, without the chance for the vendor or the local system
to disable the other.

This patch addresses this: instead of trying to find a way to come up
with some adventurous scheme to combine both policy into one TPM2
policy, we simply shard the symmetric LUKS decryption key: one half we
protect via the signed PCR policy, and the other we protect via the
pcrlock policy. Only if both halves can be acquired the disk can be
decrypted.

This means:

1. we simply double the unlock key in length in case both policies shall
   be used.
2. We store two resulting TPM policy hashes in the LUKS token JSON, one
   for each policy
3. We store two sealed TPM policy key blobs in the LUKS token JSON, for
   both halves of the LUKS unlock key.

This patch keeps the "sharding" logic relatively generic (i.e. the low
level logic is actually fine with more than 2 shards), because I figure
sooner or later we might have to encode more shards, for example if we
add further TPM2-based access policies, for example when combining FIDO2
with TPM2, or implementing TOTP for this.
This commit is contained in:
Lennart Poettering
2024-09-06 15:55:28 +02:00
parent 664570f531
commit 8e6587679b
13 changed files with 699 additions and 273 deletions
+12 -1
View File
@@ -957,12 +957,18 @@ int encrypt_credential_and_warn(
if (r < 0)
return log_error_errno(r, "Could not calculate sealing policy digest: %m");
struct iovec *blobs = NULL;
size_t n_blobs = 0;
CLEANUP_ARRAY(blobs, n_blobs, iovec_array_free);
r = tpm2_seal(tpm2_context,
/* seal_key_handle= */ 0,
&tpm2_policy,
/* n_policy_hash= */ 1,
/* pin= */ NULL,
&tpm2_key,
&tpm2_blob,
&blobs,
&n_blobs,
&tpm2_primary_alg,
/* ret_srk= */ NULL);
if (r < 0) {
@@ -977,6 +983,9 @@ int encrypt_credential_and_warn(
if (!iovec_memdup(&IOVEC_MAKE(tpm2_policy.buffer, tpm2_policy.size), &tpm2_policy_hash))
return log_oom();
assert(n_blobs == 1);
tpm2_blob = TAKE_STRUCT(blobs[0]);
assert(tpm2_blob.iov_len <= CREDENTIAL_FIELD_SIZE_MAX);
assert(tpm2_policy_hash.iov_len <= CREDENTIAL_FIELD_SIZE_MAX);
}
@@ -1341,7 +1350,9 @@ int decrypt_credential_and_warn(
/* pcrlock_policy= */ NULL,
le16toh(t->primary_alg),
&IOVEC_MAKE(t->policy_hash_and_blob, le32toh(t->blob_size)),
/* n_blobs= */ 1,
&IOVEC_MAKE(t->policy_hash_and_blob + le32toh(t->blob_size), le32toh(t->policy_hash_size)),
/* n_policy_hash= */ 1,
/* srk= */ NULL,
&tpm2_key);
if (r < 0)
+47 -17
View File
@@ -70,8 +70,10 @@ int acquire_tpm2_key(
const char *key_file,
size_t key_file_size,
uint64_t key_file_offset,
const struct iovec *key_data,
const struct iovec *policy_hash,
const struct iovec blobs[],
size_t n_blobs,
const struct iovec policy_hash[],
size_t n_policy_hash,
const struct iovec *salt,
const struct iovec *srk,
const struct iovec *pcrlock_nv,
@@ -82,9 +84,8 @@ int acquire_tpm2_key(
struct iovec *ret_decrypted_key) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *signature_json = NULL;
_cleanup_free_ void *loaded_blob = NULL;
_cleanup_(iovec_done) struct iovec loaded_blob = {};
_cleanup_free_ char *auto_device = NULL;
struct iovec blob;
int r;
assert(iovec_is_valid(salt));
@@ -99,9 +100,7 @@ int acquire_tpm2_key(
device = auto_device;
}
if (iovec_is_set(key_data))
blob = *key_data;
else {
if (n_blobs == 0) {
_cleanup_free_ char *bindname = NULL;
/* If we read the salt via AF_UNIX, make this client recognizable */
@@ -114,11 +113,12 @@ int acquire_tpm2_key(
key_file_size == 0 ? SIZE_MAX : key_file_size,
READ_FULL_FILE_CONNECT_SOCKET,
bindname,
(char**) &loaded_blob, &blob.iov_len);
(char**) &loaded_blob.iov_base, &loaded_blob.iov_len);
if (r < 0)
return r;
blob.iov_base = loaded_blob;
blobs = &loaded_blob;
n_blobs = 1;
}
if (pubkey_pcr_mask != 0) {
@@ -158,8 +158,10 @@ int acquire_tpm2_key(
/* pin= */ NULL,
FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK) ? &pcrlock_policy : NULL,
primary_alg,
&blob,
blobs,
n_blobs,
policy_hash,
n_policy_hash,
srk,
ret_decrypted_key);
if (r < 0)
@@ -204,8 +206,10 @@ int acquire_tpm2_key(
b64_salted_pin,
FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK) ? &pcrlock_policy : NULL,
primary_alg,
&blob,
blobs,
n_blobs,
policy_hash,
n_policy_hash,
srk,
ret_decrypted_key);
if (r < 0) {
@@ -230,8 +234,10 @@ int find_tpm2_auto_data(
struct iovec *ret_pubkey,
uint32_t *ret_pubkey_pcr_mask,
uint16_t *ret_primary_alg,
struct iovec *ret_blob,
struct iovec *ret_policy_hash,
struct iovec **ret_blobs,
size_t *ret_n_blobs,
struct iovec **ret_policy_hash,
size_t *ret_n_policy_hash,
struct iovec *ret_salt,
struct iovec *ret_srk,
struct iovec *ret_pcrlock_nv,
@@ -242,15 +248,35 @@ int find_tpm2_auto_data(
int r, token;
assert(cd);
assert(ret_hash_pcr_mask);
assert(ret_pcrlock_nv);
assert(ret_pubkey);
assert(ret_pubkey_pcr_mask);
assert(ret_primary_alg);
assert(ret_blobs);
assert(ret_n_blobs);
assert(ret_policy_hash);
assert(ret_n_policy_hash);
assert(ret_salt);
assert(ret_srk);
assert(ret_pcrlock_nv);
assert(ret_flags);
assert(ret_keyslot);
assert(ret_token);
for (token = start_token; token < sym_crypt_token_max(CRYPT_LUKS2); token++) {
_cleanup_(iovec_done) struct iovec blob = {}, policy_hash = {}, pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {};
_cleanup_(iovec_done) struct iovec pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {};
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
struct iovec *blobs = NULL, *policy_hash = NULL;
size_t n_blobs = 0, n_policy_hash = 0;
uint32_t hash_pcr_mask, pubkey_pcr_mask;
uint16_t pcr_bank, primary_alg;
TPM2Flags flags;
int keyslot;
CLEANUP_ARRAY(blobs, n_blobs, iovec_array_free);
CLEANUP_ARRAY(policy_hash, n_policy_hash, iovec_array_free);
r = cryptsetup_get_token_as_json(cd, token, "systemd-tpm2", &v);
if (IN_SET(r, -ENOENT, -EINVAL, -EMEDIUMTYPE))
continue;
@@ -265,8 +291,10 @@ int find_tpm2_auto_data(
&pubkey,
&pubkey_pcr_mask,
&primary_alg,
&blob,
&blobs,
&n_blobs,
&policy_hash,
&n_policy_hash,
&salt,
&srk,
&pcrlock_nv,
@@ -287,8 +315,10 @@ int find_tpm2_auto_data(
*ret_pubkey = TAKE_STRUCT(pubkey);
*ret_pubkey_pcr_mask = pubkey_pcr_mask;
*ret_primary_alg = primary_alg;
*ret_blob = TAKE_STRUCT(blob);
*ret_policy_hash = TAKE_STRUCT(policy_hash);
*ret_blobs = TAKE_PTR(blobs);
*ret_n_blobs = n_blobs;
*ret_policy_hash = TAKE_PTR(policy_hash);
*ret_n_policy_hash = n_policy_hash;
*ret_salt = TAKE_STRUCT(salt);
*ret_keyslot = keyslot;
*ret_token = token;
+16 -8
View File
@@ -24,8 +24,10 @@ int acquire_tpm2_key(
const char *key_file,
size_t key_file_size,
uint64_t key_file_offset,
const struct iovec *key_data,
const struct iovec *policy_hash,
const struct iovec blobs[],
size_t n_blobs,
const struct iovec policy_hash[],
size_t n_policy_hash,
const struct iovec *salt,
const struct iovec *srk,
const struct iovec *pcrlock_nv,
@@ -44,8 +46,10 @@ int find_tpm2_auto_data(
struct iovec *ret_pubkey,
uint32_t *ret_pubkey_pcr_mask,
uint16_t *ret_primary_alg,
struct iovec *ret_blob,
struct iovec *ret_policy_hash,
struct iovec **ret_blobs,
size_t *ret_n_blobs,
struct iovec **ret_policy_hash,
size_t *ret_n_policy_hash,
struct iovec *ret_salt,
struct iovec *ret_srk,
struct iovec *ret_pcrlock_nv,
@@ -68,8 +72,10 @@ static inline int acquire_tpm2_key(
const char *key_file,
size_t key_file_size,
uint64_t key_file_offset,
const struct iovec *key_data,
const struct iovec *policy_hash,
const struct iovec blobs[],
size_t n_blobs,
const struct iovec policy_hash[],
size_t n_policy_hash,
const struct iovec *salt,
const struct iovec *srk,
const struct iovec *pcrlock_nv,
@@ -92,8 +98,10 @@ static inline int find_tpm2_auto_data(
struct iovec *ret_pubkey,
uint32_t *ret_pubkey_pcr_mask,
uint16_t *ret_primary_alg,
struct iovec *ret_blob,
struct iovec *ret_policy_hash,
struct iovec **ret_blobs,
size_t *ret_n_blobs,
struct iovec **ret_policy_hash,
size_t *ret_n_policy_hash,
struct iovec *ret_salt,
struct iovec *ret_srk,
struct iovec *ret_pcrlock_nv,
+323 -159
View File
@@ -4179,8 +4179,11 @@ int tpm2_calculate_sealing_policy(
assert(pcr_values || n_pcr_values == 0);
assert(digest);
/* The combination of signed PCR policies and pcrlock is not supported (because we cannot combine
* PolicyAuthorize and PolicyAuthorizeNV in one policy). Callers need to use "sharding" of the
* symmetric FDE unlock key to make policies like that work. */
if (public && pcrlock_policy)
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Policies with both signed PCR and pcrlock are currently not supported.");
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Policies that combined signed PCR and pcrlock are not supported.");
if (public) {
r = tpm2_calculate_policy_authorize(public, NULL, digest);
@@ -5375,10 +5378,12 @@ int tpm2_calculate_seal(
int tpm2_seal(Tpm2Context *c,
uint32_t seal_key_handle,
const TPM2B_DIGEST *policy,
const TPM2B_DIGEST policy[],
size_t n_policy,
const char *pin,
struct iovec *ret_secret,
struct iovec *ret_blob,
struct iovec **ret_blobs,
size_t *ret_n_blobs,
uint16_t *ret_primary_alg,
struct iovec *ret_srk) {
@@ -5386,7 +5391,8 @@ int tpm2_seal(Tpm2Context *c,
int r;
assert(ret_secret);
assert(ret_blob);
assert(ret_blobs);
assert(ret_n_blobs);
/* So here's what we do here: we connect to the TPM2 chip. It persistently contains a "seed" key that
* is randomized when the TPM2 is first initialized or reset and remains stable across boots. We
@@ -5424,7 +5430,6 @@ int tpm2_seal(Tpm2Context *c,
.objectAttributes = hmac_attributes,
.parameters.keyedHashDetail.scheme.scheme = TPM2_ALG_NULL,
.unique.keyedHash.size = SHA256_DIGEST_SIZE,
.authPolicy = policy ? *policy : TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE),
};
TPMS_SENSITIVE_CREATE hmac_sensitive = {
@@ -5443,12 +5448,6 @@ int tpm2_seal(Tpm2Context *c,
(void) tpm2_credit_random(c);
log_debug("Generating secret key data.");
r = crypto_random_bytes(hmac_sensitive.data.buffer, hmac_sensitive.data.size);
if (r < 0)
return log_debug_errno(r, "Failed to generate secret key: %m");
_cleanup_(tpm2_handle_freep) Tpm2Handle *primary_handle = NULL;
if (ret_srk) {
_cleanup_(Esys_Freep) TPM2B_PUBLIC *primary_public = NULL;
@@ -5526,24 +5525,51 @@ int tpm2_seal(Tpm2Context *c,
if (r < 0)
return r;
_cleanup_(Esys_Freep) TPM2B_PUBLIC *public = NULL;
_cleanup_(Esys_Freep) TPM2B_PRIVATE *private = NULL;
r = tpm2_create(c, primary_handle, encryption_session, &hmac_template, &hmac_sensitive, &public, &private);
if (r < 0)
return r;
log_debug("Generating secret key data.");
/* At least one shard, and if we have multiple policies, then we need one shard for each */
size_t n_shards = MAX(n_policy, 1U);
/* Create a large secret which covers all shards we need */
_cleanup_(iovec_done_erase) struct iovec secret = {};
secret.iov_base = memdup(hmac_sensitive.data.buffer, hmac_sensitive.data.size);
if (!secret.iov_base)
return log_oom_debug();
secret.iov_len = hmac_sensitive.data.size;
log_debug("Marshalling private and public part of HMAC key.");
_cleanup_(iovec_done) struct iovec blob = {};
r = tpm2_marshal_blob(public, private, /* seed= */ NULL, &blob.iov_base, &blob.iov_len);
r = crypto_random_bytes_allocate_iovec(hmac_sensitive.data.size * n_shards, &secret);
if (r < 0)
return log_debug_errno(r, "Could not create sealed blob: %m");
return log_debug_errno(r, "Failed to generate secret key: %m");
struct iovec *blobs = new0(struct iovec, n_shards);
size_t n_blobs = 0;
CLEANUP_ARRAY(blobs, n_blobs, iovec_array_free);
for (size_t shard = 0; shard < n_shards; shard++) {
/* Patch this shard's policy into the template */
if (shard < n_policy)
hmac_template.authPolicy = policy[shard];
else
hmac_template.authPolicy = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE);
/* Copy in this shard's secret key */
memcpy(hmac_sensitive.data.buffer,
(const uint8_t*) secret.iov_base + (hmac_sensitive.data.size * shard),
hmac_sensitive.data.size);
log_debug("Creating HMAC key on TPM for shard %zu.", shard);
_cleanup_(Esys_Freep) TPM2B_PUBLIC *public = NULL;
_cleanup_(Esys_Freep) TPM2B_PRIVATE *private = NULL;
r = tpm2_create(c, primary_handle, encryption_session, &hmac_template, &hmac_sensitive, &public, &private);
if (r < 0)
return r;
log_debug("Marshalling private and public part of HMAC key for shard %zu.", shard);
r = tpm2_marshal_blob(public, private, /* seed= */ NULL, &blobs[n_blobs].iov_base, &blobs[n_blobs].iov_len);
if (r < 0)
return log_debug_errno(r, "Could not create sealed blob: %m");
n_blobs++;
}
if (DEBUG_LOGGING)
log_debug("Completed TPM2 key sealing in %s.", FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - start, 1));
@@ -5571,7 +5597,8 @@ int tpm2_seal(Tpm2Context *c,
}
*ret_secret = TAKE_STRUCT(secret);
*ret_blob = TAKE_STRUCT(blob);
*ret_blobs = TAKE_PTR(blobs);
*ret_n_blobs = n_blobs;
if (ret_primary_alg)
*ret_primary_alg = primary_alg;
@@ -5590,16 +5617,17 @@ int tpm2_unseal(Tpm2Context *c,
const char *pin,
const Tpm2PCRLockPolicy *pcrlock_policy,
uint16_t primary_alg,
const struct iovec *blob,
const struct iovec *known_policy_hash,
const struct iovec blobs[],
size_t n_blobs,
const struct iovec known_policy_hash[],
size_t n_known_policy_hash,
const struct iovec *srk,
struct iovec *ret_secret) {
TSS2_RC rc;
int r;
assert(iovec_is_set(blob));
assert(iovec_is_valid(known_policy_hash));
assert(n_blobs > 0);
assert(iovec_is_valid(pubkey));
assert(ret_secret);
@@ -5616,12 +5644,11 @@ int tpm2_unseal(Tpm2Context *c,
usec_t start = now(CLOCK_MONOTONIC);
TPM2B_PUBLIC public;
TPM2B_PRIVATE private;
TPM2B_ENCRYPTED_SECRET seed = {};
r = tpm2_unmarshal_blob(blob->iov_base, blob->iov_len, &public, &private, &seed);
if (r < 0)
return log_debug_errno(r, "Could not extract parts from blob: %m");
size_t n_shards = pcrlock_policy && iovec_is_set(pubkey) ? 2 : 1;
if (n_blobs != n_shards)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Number of provided key blobs (%zu) does not match policy requirements (%zu).", n_blobs, n_shards);
if (n_known_policy_hash > 0 && n_known_policy_hash != n_shards)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Number of provided known policy hashes (%zu) does not match policy requirements (%zu or 0).", n_known_policy_hash, n_shards);
/* Older code did not save the pcr_bank, and unsealing needed to detect the best pcr bank to use,
* so we need to handle that legacy situation. */
@@ -5657,37 +5684,6 @@ int tpm2_unseal(Tpm2Context *c,
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"No SRK or primary alg provided.");
if (seed.size > 0) {
/* This is a calculated (or duplicated) sealed object, and must be imported. */
_cleanup_free_ TPM2B_PRIVATE *imported_private = NULL;
r = tpm2_import(c,
primary_handle,
/* session= */ NULL,
&public,
&private,
&seed,
/* encryption_key= */ NULL,
/* symmetric= */ NULL,
&imported_private);
if (r < 0)
return r;
private = *imported_private;
}
log_debug("Loading HMAC key into TPM.");
/*
* Nothing sensitive on the bus, no need for encryption. Even if an attacker
* gives you back a different key, the session initiation will fail. In the
* SRK model, the tpmKey is verified. In the non-srk model, with pin, the bindKey
* provides protections.
*/
_cleanup_(tpm2_handle_freep) Tpm2Handle *hmac_key = NULL;
r = tpm2_load(c, primary_handle, NULL, &public, &private, &hmac_key);
if (r < 0)
return r;
TPM2B_PUBLIC pubkey_tpm2b;
_cleanup_(iovec_done) struct iovec fp = {};
if (iovec_is_set(pubkey)) {
@@ -5700,100 +5696,145 @@ int tpm2_unseal(Tpm2Context *c,
return log_debug_errno(r, "Could not get key fingerprint: %m");
}
/*
* if a pin is set for the seal object, use it to bind the session
* key to that object. This prevents active bus interposers from
* faking a TPM and seeing the unsealed value. An active interposer
* could fake a TPM, satisfying the encrypted session, and just
* forward everything to the *real* TPM.
*/
r = tpm2_set_auth(c, hmac_key, pin);
if (r < 0)
return r;
_cleanup_(Esys_Freep) TPM2B_SENSITIVE_DATA* unsealed = NULL;
_cleanup_(iovec_done_erase) struct iovec secret = {};
for (unsigned i = RETRY_UNSEAL_MAX;; i--) {
_cleanup_(tpm2_handle_freep) Tpm2Handle *encryption_session = NULL;
r = tpm2_make_encryption_session(c, primary_handle, hmac_key, &encryption_session);
if (r < 0)
return r;
bool retry = false;
iovec_done_erase(&secret); /* clear data from previous unseal attempt */
_cleanup_(tpm2_handle_freep) Tpm2Handle *policy_session = NULL;
_cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL;
r = tpm2_make_policy_session(
c,
primary_handle,
encryption_session,
&policy_session);
if (r < 0)
return r;
for (size_t shard = 0; shard < n_blobs; shard++) {
TPM2B_PUBLIC public;
TPM2B_PRIVATE private;
TPM2B_ENCRYPTED_SECRET seed = {};
r = tpm2_unmarshal_blob(blobs[shard].iov_base, blobs[shard].iov_len, &public, &private, &seed);
if (r < 0)
return log_debug_errno(r, "Could not extract parts from blob: %m");
r = tpm2_build_sealing_policy(
c,
policy_session,
hash_pcr_mask,
pcr_bank,
iovec_is_set(pubkey) ? &pubkey_tpm2b : NULL,
fp.iov_base, fp.iov_len,
pubkey_pcr_mask,
signature,
!!pin,
pcrlock_policy,
&policy_digest);
if (r < 0)
return r;
if (seed.size > 0) {
/* This is a calculated (or duplicated) sealed object, and must be imported. */
_cleanup_free_ TPM2B_PRIVATE *imported_private = NULL;
r = tpm2_import(c,
primary_handle,
/* session= */ NULL,
&public,
&private,
&seed,
/* encryption_key= */ NULL,
/* symmetric= */ NULL,
&imported_private);
if (r < 0)
return r;
/* If we know the policy hash to expect, and it doesn't match, we can shortcut things here, and not
* wait until the TPM2 tells us to go away. */
if (iovec_is_set(known_policy_hash) && memcmp_nn(policy_digest->buffer,
private = *imported_private;
}
log_debug("Loading HMAC key into TPM for shard %zu.", shard);
/* Nothing sensitive on the bus, no need for encryption. Even if an attacker gives
* you back a different key, the session initiation will fail. In the SRK model, the
* tpmKey is verified. In the non-srk model, with pin, the bindKey provides
* protections. */
_cleanup_(tpm2_handle_freep) Tpm2Handle *hmac_key = NULL;
r = tpm2_load(c, primary_handle, NULL, &public, &private, &hmac_key);
if (r < 0)
return r;
/* If a PIN is set for the seal object, use it to bind the session key to that
* object. This prevents active bus interposers from faking a TPM and seeing the
* unsealed value. An active interposer could fake a TPM, satisfying the encrypted
* session, and just forward everything to the *real* TPM. */
r = tpm2_set_auth(c, hmac_key, pin);
if (r < 0)
return r;
_cleanup_(tpm2_handle_freep) Tpm2Handle *encryption_session = NULL;
r = tpm2_make_encryption_session(c, primary_handle, hmac_key, &encryption_session);
if (r < 0)
return r;
_cleanup_(tpm2_handle_freep) Tpm2Handle *policy_session = NULL;
_cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL;
r = tpm2_make_policy_session(
c,
primary_handle,
encryption_session,
&policy_session);
if (r < 0)
return r;
/* If both public PCR key and pcrlock policies are requested, then generate the
* public PCR policy for the first shared, and the pcrlock policy for the 2nd */
r = tpm2_build_sealing_policy(
c,
policy_session,
hash_pcr_mask,
pcr_bank,
shard == 0 && iovec_is_set(pubkey) ? &pubkey_tpm2b : NULL,
fp.iov_base, fp.iov_len,
shard == 0 ? pubkey_pcr_mask : 0,
signature,
!!pin,
(shard == 1 || !iovec_is_set(pubkey)) ? pcrlock_policy : NULL,
&policy_digest);
if (r < 0)
return r;
/* If we know the policy hash to expect, and it doesn't match, we can shortcut things here, and not
* wait until the TPM2 tells us to go away. */
if (n_known_policy_hash > 0 && memcmp_nn(policy_digest->buffer,
policy_digest->size,
known_policy_hash->iov_base,
known_policy_hash->iov_len) != 0) {
known_policy_hash[shard].iov_base,
known_policy_hash[shard].iov_len) != 0) {
#if HAVE_OPENSSL
if (iovec_is_set(pubkey) &&
pubkey_tpm2b.publicArea.type == TPM2_ALG_RSA &&
pubkey_tpm2b.publicArea.parameters.rsaDetail.exponent == TPM2_RSA_DEFAULT_EXPONENT) {
/* Due to bug #30546, if using RSA pubkey with the default exponent, we may
* need to set the exponent to the TPM special-case value of 0 and retry. */
log_debug("Policy hash mismatch, retrying with RSA pubkey exponent set to 0.");
pubkey_tpm2b.publicArea.parameters.rsaDetail.exponent = 0;
continue;
} else
if (shard == 0 &&
iovec_is_set(pubkey) &&
pubkey_tpm2b.publicArea.type == TPM2_ALG_RSA &&
pubkey_tpm2b.publicArea.parameters.rsaDetail.exponent == TPM2_RSA_DEFAULT_EXPONENT) {
/* Due to bug #30546, if using RSA pubkey with the default exponent, we may
* need to set the exponent to the TPM special-case value of 0 and retry. */
log_debug("Policy hash mismatch, retrying with RSA pubkey exponent set to 0.");
pubkey_tpm2b.publicArea.parameters.rsaDetail.exponent = 0;
retry = true;
break;
}
#endif
return log_debug_errno(SYNTHETIC_ERRNO(EPERM),
"Current policy digest does not match stored policy digest, cancelling "
"TPM2 authentication attempt.");
}
log_debug("Unsealing HMAC key for shard %zu.", shard);
_cleanup_(Esys_Freep) TPM2B_SENSITIVE_DATA* unsealed = NULL;
rc = sym_Esys_Unseal(
c->esys_context,
hmac_key->esys_handle,
policy_session->esys_handle,
encryption_session->esys_handle, /* use HMAC session to enable parameter encryption */
ESYS_TR_NONE,
&unsealed);
if (rc == TPM2_RC_PCR_CHANGED && i > 0) {
log_debug("A PCR value changed during the TPM2 policy session, restarting HMAC key unsealing (%u tries left).", i);
retry = true;
break;
}
if (rc != TPM2_RC_SUCCESS)
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to unseal HMAC key in TPM: %s", sym_Tss2_RC_Decode(rc));
if (!iovec_append(&secret, &IOVEC_MAKE(unsealed->buffer, unsealed->size)))
return log_oom_debug();
explicit_bzero_safe(unsealed->buffer, unsealed->size);
}
log_debug("Unsealing HMAC key.");
rc = sym_Esys_Unseal(
c->esys_context,
hmac_key->esys_handle,
policy_session->esys_handle,
encryption_session->esys_handle, /* use HMAC session to enable parameter encryption */
ESYS_TR_NONE,
&unsealed);
if (rc == TSS2_RC_SUCCESS)
if (!retry)
break;
if (rc != TPM2_RC_PCR_CHANGED || i == 0)
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to unseal HMAC key in TPM: %s", sym_Tss2_RC_Decode(rc));
log_debug("A PCR value changed during the TPM2 policy session, restarting HMAC key unsealing (%u tries left).", i);
}
_cleanup_(iovec_done_erase) struct iovec secret = {};
secret.iov_base = memdup(unsealed->buffer, unsealed->size);
explicit_bzero_safe(unsealed->buffer, unsealed->size);
if (!secret.iov_base)
return log_oom_debug();
secret.iov_len = unsealed->size;
if (DEBUG_LOGGING)
log_debug("Completed TPM2 key unsealing in %s.", FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - start, 1));
*ret_secret = TAKE_STRUCT(secret);
return 0;
}
@@ -7344,6 +7385,46 @@ int tpm2_parse_pcr_json_array(sd_json_variant *v, uint32_t *ret) {
return 0;
}
static int tpm2_make_shard_array(
const struct iovec data[],
size_t n_data,
int (*encode_iovec)(sd_json_variant **, const void*, size_t n), /* pass sd_json_variant_new_base64() or sd_json_variant_new_hex() */
sd_json_variant **ret) {
int r;
/* Turns a series of struct iovec into either an array of base64/hex strings, or a single string
* thereof. Used for generated "tpm2-blob" or "tpm2-policy-hash" fields. */
assert(data);
assert(n_data > 0);
assert(encode_iovec);
assert(ret);
/* Only one item? Then create only one encoded string, for compatibility with older versions which
* didn't support the "sharding" scheme */
if (n_data == 1)
return encode_iovec(ret, data[0].iov_base, data[0].iov_len);
/* Multiple items? Then generate an array of encoded strings */
_cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL;
FOREACH_ARRAY(d, data, n_data) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *item = NULL;
r = encode_iovec(&item, d->iov_base, d->iov_len);
if (r < 0)
return r;
r = sd_json_variant_append_array(&j, item);
if (r < 0)
return r;
}
*ret = TAKE_PTR(j);
return 0;
}
int tpm2_make_luks2_json(
int keyslot,
uint32_t hash_pcr_mask,
@@ -7351,8 +7432,10 @@ int tpm2_make_luks2_json(
const struct iovec *pubkey,
uint32_t pubkey_pcr_mask,
uint16_t primary_alg,
const struct iovec *blob,
const struct iovec *policy_hash,
const struct iovec blobs[],
size_t n_blobs,
const struct iovec policy_hash[],
size_t n_policy_hash,
const struct iovec *salt,
const struct iovec *srk,
const struct iovec *pcrlock_nv,
@@ -7364,8 +7447,8 @@ int tpm2_make_luks2_json(
int r;
assert(iovec_is_valid(pubkey));
assert(iovec_is_valid(blob));
assert(iovec_is_valid(policy_hash));
assert(n_blobs >= 1);
assert(n_policy_hash >= 1);
if (asprintf(&keyslot_as_string, "%i", keyslot) < 0)
return -ENOMEM;
@@ -7380,6 +7463,16 @@ int tpm2_make_luks2_json(
return r;
}
_cleanup_(sd_json_variant_unrefp) sd_json_variant *phj = NULL;
r = tpm2_make_shard_array(policy_hash, n_policy_hash, sd_json_variant_new_hex, &phj);
if (r < 0)
return r;
_cleanup_(sd_json_variant_unrefp) sd_json_variant *bj = NULL;
r = tpm2_make_shard_array(blobs, n_blobs, sd_json_variant_new_base64, &bj);
if (r < 0)
return r;
/* Note: We made the mistake of using "-" in the field names, which isn't particular compatible with
* other programming languages. Let's not make things worse though, i.e. future additions to the JSON
* object should use "_" rather than "-" in field names. */
@@ -7388,11 +7481,11 @@ int tpm2_make_luks2_json(
&v,
SD_JSON_BUILD_PAIR("type", JSON_BUILD_CONST_STRING("systemd-tpm2")),
SD_JSON_BUILD_PAIR("keyslots", SD_JSON_BUILD_ARRAY(SD_JSON_BUILD_STRING(keyslot_as_string))),
SD_JSON_BUILD_PAIR("tpm2-blob", JSON_BUILD_IOVEC_BASE64(blob)),
SD_JSON_BUILD_PAIR("tpm2-blob", SD_JSON_BUILD_VARIANT(bj)),
SD_JSON_BUILD_PAIR("tpm2-pcrs", SD_JSON_BUILD_VARIANT(hmj)),
SD_JSON_BUILD_PAIR_CONDITION(pcr_bank != 0 && tpm2_hash_alg_to_string(pcr_bank), "tpm2-pcr-bank", SD_JSON_BUILD_STRING(tpm2_hash_alg_to_string(pcr_bank))),
SD_JSON_BUILD_PAIR_CONDITION(primary_alg != 0 && tpm2_asym_alg_to_string(primary_alg), "tpm2-primary-alg", SD_JSON_BUILD_STRING(tpm2_asym_alg_to_string(primary_alg))),
SD_JSON_BUILD_PAIR("tpm2-policy-hash", JSON_BUILD_IOVEC_HEX(policy_hash)),
SD_JSON_BUILD_PAIR("tpm2-policy-hash", SD_JSON_BUILD_VARIANT(phj)),
SD_JSON_BUILD_PAIR_CONDITION(FLAGS_SET(flags, TPM2_FLAGS_USE_PIN), "tpm2-pin", SD_JSON_BUILD_BOOLEAN(true)),
SD_JSON_BUILD_PAIR_CONDITION(FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK), "tpm2_pcrlock", SD_JSON_BUILD_BOOLEAN(true)),
SD_JSON_BUILD_PAIR_CONDITION(pubkey_pcr_mask != 0, "tpm2_pubkey_pcrs", SD_JSON_BUILD_VARIANT(pkmj)),
@@ -7409,6 +7502,63 @@ int tpm2_make_luks2_json(
return keyslot;
}
static int tpm2_parse_shard_array(
sd_json_variant *v,
const char *name,
int (*decode_iovec)(sd_json_variant*, struct iovec *ret), /* pass json_variant_unbase64_iovec() or json_variant_unhex_iovec() */
struct iovec **ret_data,
size_t *ret_n_data) {
int r;
assert(v);
assert(name);
assert(decode_iovec);
assert(ret_data);
assert(ret_n_data);
/* Parses the "tpm2-blob" or "tpm2-policy-hash" fields of our LUKS JSON serialization. This can
* either be an array of base64/hex strings, or a single such string. The former to allow for sharded
* keys. The latter mostly for compatibility with older versions where we didn't support sharded
* keys. */
struct iovec *data = NULL;
size_t n_data = 0;
CLEANUP_ARRAY(data, n_data, iovec_array_free);
if (sd_json_variant_is_array(v)) {
if (sd_json_variant_elements(v) == 0)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 token data contains empty '%s' array.", name);
data = new0(struct iovec, sd_json_variant_elements(v));
if (!data)
return log_oom_debug();
sd_json_variant *i;
JSON_VARIANT_ARRAY_FOREACH(i, v) {
r = decode_iovec(i, data + n_data);
if (r < 0)
return log_debug_errno(r, "Invalid data in '%s' field.", name);
n_data++;
}
} else {
data = new0(struct iovec, 1);
if (!data)
return log_oom_debug();
r = decode_iovec(v, data + 0);
if (r < 0)
return log_debug_errno(r, "Invalid data in '%s' field.", name);
n_data = 1;
}
*ret_data = TAKE_PTR(data);
*ret_n_data = n_data;
return 0;
}
int tpm2_parse_luks2_json(
sd_json_variant *v,
int *ret_keyslot,
@@ -7417,14 +7567,16 @@ int tpm2_parse_luks2_json(
struct iovec *ret_pubkey,
uint32_t *ret_pubkey_pcr_mask,
uint16_t *ret_primary_alg,
struct iovec *ret_blob,
struct iovec *ret_policy_hash,
struct iovec **ret_blobs,
size_t *ret_n_blobs,
struct iovec **ret_policy_hash,
size_t *ret_n_policy_hash,
struct iovec *ret_salt,
struct iovec *ret_srk,
struct iovec *ret_pcrlock_nv,
TPM2Flags *ret_flags) {
_cleanup_(iovec_done) struct iovec blob = {}, policy_hash = {}, pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {};
_cleanup_(iovec_done) struct iovec pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {};
uint32_t hash_pcr_mask = 0, pubkey_pcr_mask = 0;
uint16_t primary_alg = TPM2_ALG_ECC; /* ECC was the only supported algorithm in systemd < 250, use that as implied default, for compatibility */
uint16_t pcr_bank = UINT16_MAX; /* default: pick automatically */
@@ -7489,17 +7641,25 @@ int tpm2_parse_luks2_json(
if (!w)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 token data lacks 'tpm2-blob' field.");
r = json_variant_unbase64_iovec(w, &blob);
struct iovec *blobs = NULL;
size_t n_blobs = 0;
CLEANUP_ARRAY(blobs, n_blobs, iovec_array_free);
r = tpm2_parse_shard_array(w, "tpm2-blob", json_variant_unbase64_iovec, &blobs, &n_blobs);
if (r < 0)
return log_debug_errno(r, "Invalid base64 data in 'tpm2-blob' field.");
return r;
w = sd_json_variant_by_key(v, "tpm2-policy-hash");
if (!w)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 token data lacks 'tpm2-policy-hash' field.");
r = json_variant_unhex_iovec(w, &policy_hash);
struct iovec *policy_hash = NULL;
size_t n_policy_hash = 0;
CLEANUP_ARRAY(policy_hash, n_policy_hash, iovec_array_free);
r = tpm2_parse_shard_array(w, "tpm2-policy-hash", json_variant_unhex_iovec, &policy_hash, &n_policy_hash);
if (r < 0)
return log_debug_errno(r, "Invalid base64 data in 'tpm2-policy-hash' field.");
return r;
w = sd_json_variant_by_key(v, "tpm2-pin");
if (w) {
@@ -7565,10 +7725,14 @@ int tpm2_parse_luks2_json(
*ret_pubkey_pcr_mask = pubkey_pcr_mask;
if (ret_primary_alg)
*ret_primary_alg = primary_alg;
if (ret_blob)
*ret_blob = TAKE_STRUCT(blob);
if (ret_blobs)
*ret_blobs = TAKE_PTR(blobs);
if (ret_n_blobs)
*ret_n_blobs = n_blobs;
if (ret_policy_hash)
*ret_policy_hash = TAKE_STRUCT(policy_hash);
*ret_policy_hash = TAKE_PTR(policy_hash);
if (ret_n_policy_hash)
*ret_n_policy_hash = n_policy_hash;
if (ret_salt)
*ret_salt = TAKE_STRUCT(salt);
if (ret_srk)
+4 -4
View File
@@ -291,8 +291,8 @@ int tpm2_get_best_srk_template(Tpm2Context *c, TPMT_PUBLIC *ret_template);
int tpm2_get_srk(Tpm2Context *c, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle);
int tpm2_get_or_create_srk(Tpm2Context *c, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle);
int tpm2_seal(Tpm2Context *c, uint32_t seal_key_handle, const TPM2B_DIGEST *policy, const char *pin, struct iovec *ret_secret, struct iovec *ret_blob, uint16_t *ret_primary_alg, struct iovec *ret_srk);
int tpm2_unseal(Tpm2Context *c, uint32_t hash_pcr_mask, uint16_t pcr_bank, const struct iovec *pubkey, uint32_t pubkey_pcr_mask, sd_json_variant *signature, const char *pin, const Tpm2PCRLockPolicy *pcrlock_policy, uint16_t primary_alg, const struct iovec *blob, const struct iovec *policy_hash, const struct iovec *srk, struct iovec *ret_secret);
int tpm2_seal(Tpm2Context *c, uint32_t seal_key_handle, const TPM2B_DIGEST policy_hash[], size_t n_policy, const char *pin, struct iovec *ret_secret, struct iovec **ret_blobs, size_t *ret_n_blobs, uint16_t *ret_primary_alg, struct iovec *ret_srk);
int tpm2_unseal(Tpm2Context *c, uint32_t hash_pcr_mask, uint16_t pcr_bank, const struct iovec *pubkey, uint32_t pubkey_pcr_mask, sd_json_variant *signature, const char *pin, const Tpm2PCRLockPolicy *pcrlock_policy, uint16_t primary_alg, const struct iovec blobs[], size_t n_blobs, const struct iovec policy_hash[], size_t n_policy_hash, const struct iovec *srk, struct iovec *ret_secret);
#if HAVE_OPENSSL
int tpm2_tpm2b_public_to_openssl_pkey(const TPM2B_PUBLIC *public, EVP_PKEY **ret);
@@ -391,8 +391,8 @@ int tpm2_find_device_auto(char **ret);
int tpm2_make_pcr_json_array(uint32_t pcr_mask, sd_json_variant **ret);
int tpm2_parse_pcr_json_array(sd_json_variant *v, uint32_t *ret);
int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, const struct iovec *pubkey, uint32_t pubkey_pcr_mask, uint16_t primary_alg, const struct iovec *blob, const struct iovec *policy_hash, const struct iovec *salt, const struct iovec *srk, const struct iovec *pcrlock_nv, TPM2Flags flags, sd_json_variant **ret);
int tpm2_parse_luks2_json(sd_json_variant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, struct iovec *ret_pubkey, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, struct iovec *ret_blob, struct iovec *ret_policy_hash, struct iovec *ret_salt, struct iovec *ret_srk, struct iovec *pcrlock_nv, TPM2Flags *ret_flags);
int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, const struct iovec *pubkey, uint32_t pubkey_pcr_mask, uint16_t primary_alg, const struct iovec blobs[], size_t n_blobs, const struct iovec policy_hash[], size_t n_policy_hash, const struct iovec *salt, const struct iovec *srk, const struct iovec *pcrlock_nv, TPM2Flags flags, sd_json_variant **ret);
int tpm2_parse_luks2_json(sd_json_variant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, struct iovec *ret_pubkey, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, struct iovec **ret_blobs, size_t *ret_n_blobs, struct iovec **ret_policy_hash, size_t *ret_n_policy_hash, struct iovec *ret_salt, struct iovec *ret_srk, struct iovec *pcrlock_nv, TPM2Flags *ret_flags);
/* Default to PCR 7 only */
#define TPM2_PCR_INDEX_DEFAULT UINT32_C(7)