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:
@@ -42,7 +42,7 @@ _public_ int cryptsetup_token_open_pin(
|
||||
void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) {
|
||||
|
||||
_cleanup_(erase_and_freep) char *base64_encoded = NULL, *pin_string = NULL;
|
||||
_cleanup_(iovec_done) struct iovec blob = {}, pubkey = {}, policy_hash = {}, salt = {}, srk = {}, pcrlock_nv = {};
|
||||
_cleanup_(iovec_done) struct iovec pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {};
|
||||
_cleanup_(iovec_done_erase) struct iovec decrypted_key = {};
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
|
||||
uint32_t hash_pcr_mask, pubkey_pcr_mask;
|
||||
@@ -76,6 +76,11 @@ _public_ int cryptsetup_token_open_pin(
|
||||
if (r < 0)
|
||||
return crypt_log_debug_errno(cd, r, "Failed to parse token JSON data: %m");
|
||||
|
||||
struct iovec *blobs = NULL, *policy_hash = NULL;
|
||||
size_t n_blobs = 0, n_policy_hash = 0;
|
||||
CLEANUP_ARRAY(blobs, n_blobs, iovec_array_free);
|
||||
CLEANUP_ARRAY(policy_hash, n_policy_hash, iovec_array_free);
|
||||
|
||||
r = tpm2_parse_luks2_json(
|
||||
v,
|
||||
/* ret_keyslot= */ NULL,
|
||||
@@ -84,8 +89,10 @@ _public_ int cryptsetup_token_open_pin(
|
||||
&pubkey,
|
||||
&pubkey_pcr_mask,
|
||||
&primary_alg,
|
||||
&blob,
|
||||
&blobs,
|
||||
&n_blobs,
|
||||
&policy_hash,
|
||||
&n_policy_hash,
|
||||
&salt,
|
||||
&srk,
|
||||
&pcrlock_nv,
|
||||
@@ -106,8 +113,10 @@ _public_ int cryptsetup_token_open_pin(
|
||||
pin_string,
|
||||
params.pcrlock_path,
|
||||
primary_alg,
|
||||
&blob,
|
||||
&policy_hash,
|
||||
blobs,
|
||||
n_blobs,
|
||||
policy_hash,
|
||||
n_policy_hash,
|
||||
&salt,
|
||||
&srk,
|
||||
&pcrlock_nv,
|
||||
@@ -167,8 +176,8 @@ _public_ void cryptsetup_token_dump(
|
||||
struct crypt_device *cd /* is always LUKS2 context */,
|
||||
const char *json /* validated 'systemd-tpm2' token if cryptsetup_token_validate is defined */) {
|
||||
|
||||
_cleanup_free_ char *hash_pcrs_str = NULL, *pubkey_pcrs_str = NULL, *blob_str = NULL, *policy_hash_str = NULL, *pubkey_str = NULL;
|
||||
_cleanup_(iovec_done) struct iovec blob = {}, pubkey = {}, policy_hash = {}, salt = {}, srk = {}, pcrlock_nv = {};
|
||||
_cleanup_free_ char *hash_pcrs_str = NULL, *pubkey_pcrs_str = NULL, *pubkey_str = NULL;
|
||||
_cleanup_(iovec_done) struct iovec pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {};
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
|
||||
uint32_t hash_pcr_mask, pubkey_pcr_mask;
|
||||
uint16_t pcr_bank, primary_alg;
|
||||
@@ -181,6 +190,11 @@ _public_ void cryptsetup_token_dump(
|
||||
if (r < 0)
|
||||
return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " JSON object: %m");
|
||||
|
||||
struct iovec *blobs = NULL, *policy_hash = NULL;
|
||||
size_t n_blobs = 0, n_policy_hash = 0;
|
||||
CLEANUP_ARRAY(blobs, n_blobs, iovec_array_free);
|
||||
CLEANUP_ARRAY(policy_hash, n_policy_hash, iovec_array_free);
|
||||
|
||||
r = tpm2_parse_luks2_json(
|
||||
v,
|
||||
NULL,
|
||||
@@ -189,8 +203,10 @@ _public_ void cryptsetup_token_dump(
|
||||
&pubkey,
|
||||
&pubkey_pcr_mask,
|
||||
&primary_alg,
|
||||
&blob,
|
||||
&blobs,
|
||||
&n_blobs,
|
||||
&policy_hash,
|
||||
&n_policy_hash,
|
||||
&salt,
|
||||
&srk,
|
||||
&pcrlock_nv,
|
||||
@@ -206,30 +222,40 @@ _public_ void cryptsetup_token_dump(
|
||||
if (!pubkey_pcrs_str)
|
||||
return (void) crypt_log_debug_errno(cd, ENOMEM, "Cannot format PCR hash mask: %m");
|
||||
|
||||
r = crypt_dump_buffer_to_hex_string(blob.iov_base, blob.iov_len, &blob_str);
|
||||
if (r < 0)
|
||||
return (void) crypt_log_debug_errno(cd, r, "Cannot dump " TOKEN_NAME " content: %m");
|
||||
|
||||
r = crypt_dump_buffer_to_hex_string(pubkey.iov_base, pubkey.iov_len, &pubkey_str);
|
||||
if (r < 0)
|
||||
return (void) crypt_log_debug_errno(cd, r, "Cannot dump " TOKEN_NAME " content: %m");
|
||||
|
||||
r = crypt_dump_buffer_to_hex_string(policy_hash.iov_base, policy_hash.iov_len, &policy_hash_str);
|
||||
if (r < 0)
|
||||
return (void) crypt_log_debug_errno(cd, r, "Cannot dump " TOKEN_NAME " content: %m");
|
||||
|
||||
crypt_log(cd, "\ttpm2-hash-pcrs: %s\n", strna(hash_pcrs_str));
|
||||
crypt_log(cd, "\ttpm2-pcr-bank: %s\n", strna(tpm2_hash_alg_to_string(pcr_bank)));
|
||||
crypt_log(cd, "\ttpm2-pubkey:" CRYPT_DUMP_LINE_SEP "%s\n", pubkey_str);
|
||||
crypt_log(cd, "\ttpm2-pubkey-pcrs: %s\n", strna(pubkey_pcrs_str));
|
||||
crypt_log(cd, "\ttpm2-primary-alg: %s\n", strna(tpm2_asym_alg_to_string(primary_alg)));
|
||||
crypt_log(cd, "\ttpm2-blob: %s\n", blob_str);
|
||||
crypt_log(cd, "\ttpm2-policy-hash:" CRYPT_DUMP_LINE_SEP "%s\n", policy_hash_str);
|
||||
crypt_log(cd, "\ttpm2-pin: %s\n", true_false(flags & TPM2_FLAGS_USE_PIN));
|
||||
crypt_log(cd, "\ttpm2-pcrlock: %s\n", true_false(flags & TPM2_FLAGS_USE_PCRLOCK));
|
||||
crypt_log(cd, "\ttpm2-salt: %s\n", true_false(iovec_is_set(&salt)));
|
||||
crypt_log(cd, "\ttpm2-srk: %s\n", true_false(iovec_is_set(&srk)));
|
||||
crypt_log(cd, "\ttpm2-pcrlock-nv: %s\n", true_false(iovec_is_set(&pcrlock_nv)));
|
||||
|
||||
FOREACH_ARRAY(p, policy_hash, n_policy_hash) {
|
||||
_cleanup_free_ char *policy_hash_str = NULL;
|
||||
|
||||
r = crypt_dump_buffer_to_hex_string(p->iov_base, p->iov_len, &policy_hash_str);
|
||||
if (r < 0)
|
||||
return (void) crypt_log_debug_errno(cd, r, "Cannot dump " TOKEN_NAME " content: %m");
|
||||
|
||||
crypt_log(cd, "\ttpm2-policy-hash:" CRYPT_DUMP_LINE_SEP "%s\n", policy_hash_str);
|
||||
}
|
||||
|
||||
FOREACH_ARRAY(b, blobs, n_blobs) {
|
||||
_cleanup_free_ char *blob_str = NULL;
|
||||
|
||||
r = crypt_dump_buffer_to_hex_string(b->iov_base, b->iov_len, &blob_str);
|
||||
if (r < 0)
|
||||
return (void) crypt_log_debug_errno(cd, r, "Cannot dump " TOKEN_NAME " content: %m");
|
||||
|
||||
crypt_log(cd, "\ttpm2-blob: %s\n", blob_str);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -313,9 +339,18 @@ _public_ int cryptsetup_token_validate(
|
||||
return 1;
|
||||
}
|
||||
|
||||
r = sd_json_variant_unbase64(w, NULL, NULL);
|
||||
if (r < 0)
|
||||
return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'tpm2-blob' field: %m");
|
||||
if (sd_json_variant_is_array(w)) {
|
||||
sd_json_variant *i;
|
||||
JSON_VARIANT_ARRAY_FOREACH(i, w) {
|
||||
r = sd_json_variant_unbase64(i, /* ret= */ NULL, /* ret_size= */ NULL);
|
||||
if (r < 0)
|
||||
return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'tpm2-blob' field: %m");
|
||||
}
|
||||
} else {
|
||||
r = sd_json_variant_unbase64(w, /* ret= */ NULL, /* ret_size= */ NULL);
|
||||
if (r < 0)
|
||||
return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'tpm2-blob' field: %m");
|
||||
}
|
||||
|
||||
w = sd_json_variant_by_key(v, "tpm2-policy-hash");
|
||||
if (!w) {
|
||||
@@ -323,9 +358,18 @@ _public_ int cryptsetup_token_validate(
|
||||
return 1;
|
||||
}
|
||||
|
||||
r = sd_json_variant_unhex(w, NULL, NULL);
|
||||
if (r < 0)
|
||||
return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'tpm2-policy-hash' field: %m");
|
||||
if (sd_json_variant_is_array(w)) {
|
||||
sd_json_variant *i;
|
||||
JSON_VARIANT_ARRAY_FOREACH(i, w) {
|
||||
r = sd_json_variant_unhex(i, /* ret= */ NULL, /* ret_size= */ NULL);
|
||||
if (r < 0)
|
||||
return crypt_log_debug_errno(cd, r, "Invalid hex data in 'tpm2-policy-hash' field: %m");
|
||||
}
|
||||
} else {
|
||||
r = sd_json_variant_unhex(w, /* ret= */ NULL, /* ret_size= */ NULL);
|
||||
if (r < 0)
|
||||
return crypt_log_debug_errno(cd, r, "Invalid hex data in 'tpm2-policy-hash' field: %m");
|
||||
}
|
||||
|
||||
w = sd_json_variant_by_key(v, "tpm2-pin");
|
||||
if (w) {
|
||||
|
||||
@@ -24,8 +24,10 @@ int acquire_luks2_key(
|
||||
const char *pin,
|
||||
const char *pcrlock_path,
|
||||
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,
|
||||
@@ -101,8 +103,10 @@ int acquire_luks2_key(
|
||||
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)
|
||||
|
||||
@@ -16,8 +16,10 @@ int acquire_luks2_key(
|
||||
const char *pin,
|
||||
const char *pcrlock_path,
|
||||
uint16_t primary_alg,
|
||||
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,
|
||||
|
||||
@@ -1862,8 +1862,9 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
|
||||
/* pcrlock_path= */ NULL,
|
||||
/* primary_alg= */ 0,
|
||||
key_file, arg_keyfile_size, arg_keyfile_offset,
|
||||
key_data,
|
||||
key_data, /* n_blobs= */ 1,
|
||||
/* policy_hash= */ NULL, /* we don't know the policy hash */
|
||||
/* n_policy_hash= */ 0,
|
||||
/* salt= */ NULL,
|
||||
/* srk= */ NULL,
|
||||
/* pcrlock_nv= */ NULL,
|
||||
@@ -1911,11 +1912,15 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
|
||||
|
||||
for (;;) {
|
||||
_cleanup_(iovec_done) struct iovec pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {};
|
||||
_cleanup_(iovec_done) struct iovec blob = {}, policy_hash = {};
|
||||
struct iovec *blobs = NULL, *policy_hash = NULL;
|
||||
uint32_t hash_pcr_mask, pubkey_pcr_mask;
|
||||
size_t n_blobs = 0, n_policy_hash = 0;
|
||||
uint16_t pcr_bank, primary_alg;
|
||||
TPM2Flags tpm2_flags;
|
||||
|
||||
CLEANUP_ARRAY(blobs, n_blobs, iovec_array_free);
|
||||
CLEANUP_ARRAY(policy_hash, n_policy_hash, iovec_array_free);
|
||||
|
||||
r = find_tpm2_auto_data(
|
||||
cd,
|
||||
arg_tpm2_pcr_mask, /* if != UINT32_MAX we'll only look for tokens with this PCR mask */
|
||||
@@ -1925,8 +1930,10 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
|
||||
&pubkey,
|
||||
&pubkey_pcr_mask,
|
||||
&primary_alg,
|
||||
&blob,
|
||||
&blobs,
|
||||
&n_blobs,
|
||||
&policy_hash,
|
||||
&n_policy_hash,
|
||||
&salt,
|
||||
&srk,
|
||||
&pcrlock_nv,
|
||||
@@ -1960,8 +1967,10 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
|
||||
arg_tpm2_pcrlock,
|
||||
primary_alg,
|
||||
/* key_file= */ NULL, /* key_file_size= */ 0, /* key_file_offset= */ 0, /* no key file */
|
||||
&blob,
|
||||
&policy_hash,
|
||||
blobs,
|
||||
n_blobs,
|
||||
policy_hash,
|
||||
n_policy_hash,
|
||||
&salt,
|
||||
&srk,
|
||||
&pcrlock_nv,
|
||||
|
||||
Reference in New Issue
Block a user