1
0
mirror of https://github.com/systemd/systemd.git synced 2024-12-22 17:35:35 +03:00

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-08-29 16:16:10 +02:00
parent 664570f531
commit 8e6587679b
13 changed files with 699 additions and 273 deletions

View File

@ -10,6 +10,7 @@
#include "errno-util.h" #include "errno-util.h"
#include "fileio.h" #include "fileio.h"
#include "hexdecoct.h" #include "hexdecoct.h"
#include "json-util.h"
#include "log.h" #include "log.h"
#include "memory-util.h" #include "memory-util.h"
#include "random-util.h" #include "random-util.h"
@ -18,20 +19,20 @@
static int search_policy_hash( static int search_policy_hash(
struct crypt_device *cd, struct crypt_device *cd,
const struct iovec *hash) { const struct iovec policy_hash[],
size_t n_policy_hash) {
int r; int r;
assert(cd); assert(cd);
assert(iovec_is_valid(hash));
if (!iovec_is_set(hash)) /* Searches among the already enrolled TPM2 tokens for one that matches the exact set of policies specified */
if (n_policy_hash == 0)
return -ENOENT; return -ENOENT;
for (int token = 0; token < sym_crypt_token_max(CRYPT_LUKS2); token++) { for (int token = 0; token < sym_crypt_token_max(CRYPT_LUKS2); token++) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
_cleanup_free_ void *thash = NULL;
size_t thash_size = 0;
int keyslot; int keyslot;
sd_json_variant *w; sd_json_variant *w;
@ -54,12 +55,45 @@ static int search_policy_hash(
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"TPM2 token data lacks 'tpm2-policy-hash' field."); "TPM2 token data lacks 'tpm2-policy-hash' field.");
r = sd_json_variant_unhex(w, &thash, &thash_size); /* This is either an array of strings (for sharded enrollments), or a single string */
if (r < 0) if (sd_json_variant_is_array(w)) {
return log_error_errno(r, "Invalid base64 data in 'tpm2-policy-hash' field: %m");
if (memcmp_nn(hash->iov_base, hash->iov_len, thash, thash_size) == 0) if (sd_json_variant_elements(w) == n_policy_hash) {
return keyslot; /* Found entry with same hash. */ sd_json_variant *i;
bool match = true;
size_t j = 0;
JSON_VARIANT_ARRAY_FOREACH(i, w) {
_cleanup_(iovec_done) struct iovec thash = {};
r = sd_json_variant_unhex(i, &thash.iov_base, &thash.iov_len);
if (r < 0)
return log_error_errno(r, "Invalid hex data in 'tpm2-policy-hash' field item : %m");
if (iovec_memcmp(policy_hash + j, &thash) != 0) {
match = false;
break;
}
j++;
}
assert(j == n_policy_hash);
if (match) /* Found entry with the exact same set of hashes */
return keyslot;
}
} else if (n_policy_hash == 1) {
_cleanup_(iovec_done) struct iovec thash = {};
r = sd_json_variant_unhex(w, &thash.iov_base, &thash.iov_len);
if (r < 0)
return log_error_errno(r, "Invalid hex data in 'tpm2-policy-hash' field: %m");
if (iovec_memcmp(policy_hash + 0, &thash) == 0)
return keyslot; /* Found entry with same hash. */
}
} }
return -ENOENT; /* Not found */ return -ENOENT; /* Not found */
@ -154,12 +188,16 @@ int load_volume_key_tpm2(
for (;;) { for (;;) {
_cleanup_(iovec_done) struct iovec pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {}; _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;
size_t n_blobs = 0, n_policy_hash = 0;
uint32_t hash_pcr_mask, pubkey_pcr_mask; uint32_t hash_pcr_mask, pubkey_pcr_mask;
uint16_t pcr_bank, primary_alg; uint16_t pcr_bank, primary_alg;
TPM2Flags tpm2_flags; TPM2Flags tpm2_flags;
int keyslot; int keyslot;
CLEANUP_ARRAY(policy_hash, n_policy_hash, iovec_array_free);
CLEANUP_ARRAY(blobs, n_blobs, iovec_array_free);
r = find_tpm2_auto_data( r = find_tpm2_auto_data(
cd, cd,
UINT32_MAX, UINT32_MAX,
@ -169,8 +207,10 @@ int load_volume_key_tpm2(
&pubkey, &pubkey,
&pubkey_pcr_mask, &pubkey_pcr_mask,
&primary_alg, &primary_alg,
&blob, &blobs,
&n_blobs,
&policy_hash, &policy_hash,
&n_policy_hash,
&salt, &salt,
&srk, &srk,
&pcrlock_nv, &pcrlock_nv,
@ -202,8 +242,10 @@ int load_volume_key_tpm2(
/* pcrlock_path= */ NULL, /* pcrlock_path= */ NULL,
primary_alg, primary_alg,
/* key_file= */ NULL, /* key_file_size= */ 0, /* key_file_offset= */ 0, /* no key file */ /* key_file= */ NULL, /* key_file_size= */ 0, /* key_file_offset= */ 0, /* no key file */
&blob, blobs,
&policy_hash, n_blobs,
policy_hash,
n_policy_hash,
&salt, &salt,
&srk, &srk,
&pcrlock_nv, &pcrlock_nv,
@ -257,7 +299,7 @@ int enroll_tpm2(struct crypt_device *cd,
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL, *signature_json = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL, *signature_json = NULL;
_cleanup_(erase_and_freep) char *base64_encoded = NULL; _cleanup_(erase_and_freep) char *base64_encoded = NULL;
_cleanup_(iovec_done) struct iovec srk = {}, blob = {}, pubkey = {}; _cleanup_(iovec_done) struct iovec srk = {}, pubkey = {};
_cleanup_(iovec_done_erase) struct iovec secret = {}; _cleanup_(iovec_done_erase) struct iovec secret = {};
const char *node; const char *node;
_cleanup_(erase_and_freep) char *pin_str = NULL; _cleanup_(erase_and_freep) char *pin_str = NULL;
@ -304,10 +346,7 @@ int enroll_tpm2(struct crypt_device *cd,
} }
TPM2B_PUBLIC public = {}; TPM2B_PUBLIC public = {};
/* Load the PCR public key if specified explicitly, or if no pcrlock policy was specified and if (pcr_pubkey_path || load_pcr_pubkey) {
* automatic loading of PCR public keys wasn't disabled explicitly. The reason we turn this off when
* pcrlock is configured is simply that we currently not support both in combination. */
if (pcr_pubkey_path || (load_pcr_pubkey && !pcrlock_path)) {
r = tpm2_load_pcr_public_key(pcr_pubkey_path, &pubkey.iov_base, &pubkey.iov_len); r = tpm2_load_pcr_public_key(pcr_pubkey_path, &pubkey.iov_base, &pubkey.iov_len);
if (r < 0) { if (r < 0) {
if (pcr_pubkey_path || signature_path || r != -ENOENT) if (pcr_pubkey_path || signature_path || r != -ENOENT)
@ -398,42 +437,88 @@ int enroll_tpm2(struct crypt_device *cd,
return log_error_errno(r, "Failed to determine best PCR bank: %m"); return log_error_errno(r, "Failed to determine best PCR bank: %m");
} }
TPM2B_DIGEST policy = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); /* Unfortunately TPM2 policy semantics make it very hard to combine PolicyAuthorize (which we need
* for signed PCR policies) and PolicyAuthorizeNV (which we need for pcrlock policies). Hence, let's
* use a "sharded" secret, and lock the first shard to the signed PCR policy, and the 2nd to the
* pcrlock if both are requested. */
TPM2B_DIGEST policy_hash[2] = {
TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE),
TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE),
};
size_t n_policy_hash = 1;
/* If both PCR public key unlock and pcrlock unlock is selected, then we create the one for PCR public key unlock first. */
r = tpm2_calculate_sealing_policy( r = tpm2_calculate_sealing_policy(
hash_pcr_values, hash_pcr_values,
n_hash_pcr_values, n_hash_pcr_values,
iovec_is_set(&pubkey) ? &public : NULL, iovec_is_set(&pubkey) ? &public : NULL,
use_pin, use_pin,
pcrlock_path ? &pcrlock_policy : NULL, pcrlock_path && !iovec_is_set(&pubkey) ? &pcrlock_policy : NULL,
&policy); policy_hash + 0);
if (r < 0) if (r < 0)
return r; return r;
if (device_key) if (pcrlock_path && iovec_is_set(&pubkey)) {
r = tpm2_calculate_sealing_policy(
hash_pcr_values,
n_hash_pcr_values,
/* public= */ NULL, /* This one is off now */
use_pin,
&pcrlock_policy, /* And this one on instead. */
policy_hash + 1);
if (r < 0)
return r;
n_policy_hash ++;
}
struct iovec *blobs = NULL;
size_t n_blobs = 0;
CLEANUP_ARRAY(blobs, n_blobs, iovec_array_free);
if (device_key) {
if (n_policy_hash > 1)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"Combined signed PCR policies and pcrlock policies cannot be calculated offline, currently.");
blobs = new0(struct iovec, 1);
if (!blobs)
return log_oom();
n_blobs = 1;
r = tpm2_calculate_seal( r = tpm2_calculate_seal(
seal_key_handle, seal_key_handle,
&device_key_public, &device_key_public,
/* attributes= */ NULL, /* attributes= */ NULL,
/* secret= */ NULL, /* secret= */ NULL,
&policy, policy_hash + 0,
pin_str, pin_str,
&secret, &secret,
&blob, blobs + 0,
&srk); &srk);
else } else
r = tpm2_seal(tpm2_context, r = tpm2_seal(tpm2_context,
seal_key_handle, seal_key_handle,
&policy, policy_hash,
n_policy_hash,
pin_str, pin_str,
&secret, &secret,
&blob, &blobs,
&n_blobs,
/* ret_primary_alg= */ NULL, /* ret_primary_alg= */ NULL,
&srk); &srk);
if (r < 0) if (r < 0)
return log_error_errno(r, "Failed to seal to TPM2: %m"); return log_error_errno(r, "Failed to seal to TPM2: %m");
struct iovec policy_hash_as_iovec[2] = {
IOVEC_MAKE(policy_hash[0].buffer, policy_hash[0].size),
IOVEC_MAKE(policy_hash[1].buffer, policy_hash[1].size),
};
/* Let's see if we already have this specific PCR policy hash enrolled, if so, exit early. */ /* Let's see if we already have this specific PCR policy hash enrolled, if so, exit early. */
r = search_policy_hash(cd, &IOVEC_MAKE(policy.buffer, policy.size)); r = search_policy_hash(cd, policy_hash_as_iovec, n_policy_hash);
if (r == -ENOENT) if (r == -ENOENT)
log_debug_errno(r, "PCR policy hash not yet enrolled, enrolling now."); log_debug_errno(r, "PCR policy hash not yet enrolled, enrolling now.");
else if (r < 0) else if (r < 0)
@ -461,8 +546,10 @@ int enroll_tpm2(struct crypt_device *cd,
pin_str, pin_str,
pcrlock_path ? &pcrlock_policy : NULL, pcrlock_path ? &pcrlock_policy : NULL,
/* primary_alg= */ 0, /* primary_alg= */ 0,
&blob, blobs,
&IOVEC_MAKE(policy.buffer, policy.size), n_blobs,
policy_hash_as_iovec,
n_policy_hash,
&srk, &srk,
&secret2); &secret2);
if (r < 0) if (r < 0)
@ -498,8 +585,10 @@ int enroll_tpm2(struct crypt_device *cd,
&pubkey, &pubkey,
pubkey_pcr_mask, pubkey_pcr_mask,
/* primary_alg= */ 0, /* primary_alg= */ 0,
&blob, blobs,
&IOVEC_MAKE(policy.buffer, policy.size), n_blobs,
policy_hash_as_iovec,
n_policy_hash,
use_pin ? &IOVEC_MAKE(binary_salt, sizeof(binary_salt)) : NULL, use_pin ? &IOVEC_MAKE(binary_salt, sizeof(binary_salt)) : NULL,
&srk, &srk,
pcrlock_path ? &pcrlock_policy.nv_handle : NULL, pcrlock_path ? &pcrlock_policy.nv_handle : NULL,

View File

@ -42,7 +42,7 @@ _public_ int cryptsetup_token_open_pin(
void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) { void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) {
_cleanup_(erase_and_freep) char *base64_encoded = NULL, *pin_string = NULL; _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_(iovec_done_erase) struct iovec decrypted_key = {};
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
uint32_t hash_pcr_mask, pubkey_pcr_mask; uint32_t hash_pcr_mask, pubkey_pcr_mask;
@ -76,6 +76,11 @@ _public_ int cryptsetup_token_open_pin(
if (r < 0) if (r < 0)
return crypt_log_debug_errno(cd, r, "Failed to parse token JSON data: %m"); 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( r = tpm2_parse_luks2_json(
v, v,
/* ret_keyslot= */ NULL, /* ret_keyslot= */ NULL,
@ -84,8 +89,10 @@ _public_ int cryptsetup_token_open_pin(
&pubkey, &pubkey,
&pubkey_pcr_mask, &pubkey_pcr_mask,
&primary_alg, &primary_alg,
&blob, &blobs,
&n_blobs,
&policy_hash, &policy_hash,
&n_policy_hash,
&salt, &salt,
&srk, &srk,
&pcrlock_nv, &pcrlock_nv,
@ -106,8 +113,10 @@ _public_ int cryptsetup_token_open_pin(
pin_string, pin_string,
params.pcrlock_path, params.pcrlock_path,
primary_alg, primary_alg,
&blob, blobs,
&policy_hash, n_blobs,
policy_hash,
n_policy_hash,
&salt, &salt,
&srk, &srk,
&pcrlock_nv, &pcrlock_nv,
@ -167,8 +176,8 @@ _public_ void cryptsetup_token_dump(
struct crypt_device *cd /* is always LUKS2 context */, struct crypt_device *cd /* is always LUKS2 context */,
const char *json /* validated 'systemd-tpm2' token if cryptsetup_token_validate is defined */) { 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_free_ char *hash_pcrs_str = NULL, *pubkey_pcrs_str = NULL, *pubkey_str = 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_(sd_json_variant_unrefp) sd_json_variant *v = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
uint32_t hash_pcr_mask, pubkey_pcr_mask; uint32_t hash_pcr_mask, pubkey_pcr_mask;
uint16_t pcr_bank, primary_alg; uint16_t pcr_bank, primary_alg;
@ -181,6 +190,11 @@ _public_ void cryptsetup_token_dump(
if (r < 0) if (r < 0)
return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " JSON object: %m"); 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( r = tpm2_parse_luks2_json(
v, v,
NULL, NULL,
@ -189,8 +203,10 @@ _public_ void cryptsetup_token_dump(
&pubkey, &pubkey,
&pubkey_pcr_mask, &pubkey_pcr_mask,
&primary_alg, &primary_alg,
&blob, &blobs,
&n_blobs,
&policy_hash, &policy_hash,
&n_policy_hash,
&salt, &salt,
&srk, &srk,
&pcrlock_nv, &pcrlock_nv,
@ -206,30 +222,40 @@ _public_ void cryptsetup_token_dump(
if (!pubkey_pcrs_str) if (!pubkey_pcrs_str)
return (void) crypt_log_debug_errno(cd, ENOMEM, "Cannot format PCR hash mask: %m"); 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); r = crypt_dump_buffer_to_hex_string(pubkey.iov_base, pubkey.iov_len, &pubkey_str);
if (r < 0) if (r < 0)
return (void) crypt_log_debug_errno(cd, r, "Cannot dump " TOKEN_NAME " content: %m"); 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-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-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:" CRYPT_DUMP_LINE_SEP "%s\n", pubkey_str);
crypt_log(cd, "\ttpm2-pubkey-pcrs: %s\n", strna(pubkey_pcrs_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-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-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-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-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-srk: %s\n", true_false(iovec_is_set(&srk)));
crypt_log(cd, "\ttpm2-pcrlock-nv: %s\n", true_false(iovec_is_set(&pcrlock_nv))); 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; return 1;
} }
r = sd_json_variant_unbase64(w, NULL, NULL); if (sd_json_variant_is_array(w)) {
if (r < 0) sd_json_variant *i;
return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'tpm2-blob' field: %m"); 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"); w = sd_json_variant_by_key(v, "tpm2-policy-hash");
if (!w) { if (!w) {
@ -323,9 +358,18 @@ _public_ int cryptsetup_token_validate(
return 1; return 1;
} }
r = sd_json_variant_unhex(w, NULL, NULL); if (sd_json_variant_is_array(w)) {
if (r < 0) sd_json_variant *i;
return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'tpm2-policy-hash' field: %m"); 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"); w = sd_json_variant_by_key(v, "tpm2-pin");
if (w) { if (w) {

View File

@ -24,8 +24,10 @@ int acquire_luks2_key(
const char *pin, const char *pin,
const char *pcrlock_path, const char *pcrlock_path,
uint16_t primary_alg, uint16_t primary_alg,
const struct iovec *blob, const struct iovec blobs[],
const struct iovec *policy_hash, size_t n_blobs,
const struct iovec policy_hash[],
size_t n_policy_hash,
const struct iovec *salt, const struct iovec *salt,
const struct iovec *srk, const struct iovec *srk,
const struct iovec *pcrlock_nv, const struct iovec *pcrlock_nv,
@ -101,8 +103,10 @@ int acquire_luks2_key(
pin, pin,
FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK) ? &pcrlock_policy : NULL, FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK) ? &pcrlock_policy : NULL,
primary_alg, primary_alg,
blob, blobs,
n_blobs,
policy_hash, policy_hash,
n_policy_hash,
srk, srk,
ret_decrypted_key); ret_decrypted_key);
if (r < 0) if (r < 0)

View File

@ -16,8 +16,10 @@ int acquire_luks2_key(
const char *pin, const char *pin,
const char *pcrlock_path, const char *pcrlock_path,
uint16_t primary_alg, uint16_t primary_alg,
const struct iovec *key_data, const struct iovec blobs[],
const struct iovec *policy_hash, size_t n_blobs,
const struct iovec policy_hash[],
size_t n_policy_hash,
const struct iovec *salt, const struct iovec *salt,
const struct iovec *srk, const struct iovec *srk,
const struct iovec *pcrlock_nv, const struct iovec *pcrlock_nv,

View File

@ -1862,8 +1862,9 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
/* pcrlock_path= */ NULL, /* pcrlock_path= */ NULL,
/* primary_alg= */ 0, /* primary_alg= */ 0,
key_file, arg_keyfile_size, arg_keyfile_offset, 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 */ /* policy_hash= */ NULL, /* we don't know the policy hash */
/* n_policy_hash= */ 0,
/* salt= */ NULL, /* salt= */ NULL,
/* srk= */ NULL, /* srk= */ NULL,
/* pcrlock_nv= */ NULL, /* pcrlock_nv= */ NULL,
@ -1911,11 +1912,15 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
for (;;) { for (;;) {
_cleanup_(iovec_done) struct iovec pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {}; _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; uint32_t hash_pcr_mask, pubkey_pcr_mask;
size_t n_blobs = 0, n_policy_hash = 0;
uint16_t pcr_bank, primary_alg; uint16_t pcr_bank, primary_alg;
TPM2Flags tpm2_flags; 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( r = find_tpm2_auto_data(
cd, cd,
arg_tpm2_pcr_mask, /* if != UINT32_MAX we'll only look for tokens with this PCR mask */ 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,
&pubkey_pcr_mask, &pubkey_pcr_mask,
&primary_alg, &primary_alg,
&blob, &blobs,
&n_blobs,
&policy_hash, &policy_hash,
&n_policy_hash,
&salt, &salt,
&srk, &srk,
&pcrlock_nv, &pcrlock_nv,
@ -1960,8 +1967,10 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
arg_tpm2_pcrlock, arg_tpm2_pcrlock,
primary_alg, primary_alg,
/* key_file= */ NULL, /* key_file_size= */ 0, /* key_file_offset= */ 0, /* no key file */ /* key_file= */ NULL, /* key_file_size= */ 0, /* key_file_offset= */ 0, /* no key file */
&blob, blobs,
&policy_hash, n_blobs,
policy_hash,
n_policy_hash,
&salt, &salt,
&srk, &srk,
&pcrlock_nv, &pcrlock_nv,

View File

@ -4236,7 +4236,7 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta
if (IN_SET(p->encrypt, ENCRYPT_TPM2, ENCRYPT_KEY_FILE_TPM2)) { if (IN_SET(p->encrypt, ENCRYPT_TPM2, ENCRYPT_KEY_FILE_TPM2)) {
#if HAVE_TPM2 #if HAVE_TPM2
_cleanup_(iovec_done) struct iovec pubkey = {}, blob = {}, srk = {}; _cleanup_(iovec_done) struct iovec pubkey = {}, srk = {};
_cleanup_(iovec_done_erase) struct iovec secret = {}; _cleanup_(iovec_done_erase) struct iovec secret = {};
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
ssize_t base64_encoded_size; ssize_t base64_encoded_size;
@ -4309,35 +4309,71 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta
return log_error_errno(r, "Could not get hash mask: %m"); return log_error_errno(r, "Could not get hash mask: %m");
} }
TPM2B_DIGEST policy = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); TPM2B_DIGEST policy_hash[2] = {
TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE),
TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE),
};
size_t n_policy_hash = 1;
/* If both PCR public key unlock and pcrlock unlock is selected, then shard the encryption key. */
r = tpm2_calculate_sealing_policy( r = tpm2_calculate_sealing_policy(
arg_tpm2_hash_pcr_values, arg_tpm2_hash_pcr_values,
arg_tpm2_n_hash_pcr_values, arg_tpm2_n_hash_pcr_values,
iovec_is_set(&pubkey) ? &public : NULL, iovec_is_set(&pubkey) ? &public : NULL,
/* use_pin= */ false, /* use_pin= */ false,
arg_tpm2_pcrlock ? &pcrlock_policy : NULL, arg_tpm2_pcrlock && !iovec_is_set(&pubkey) ? &pcrlock_policy : NULL,
&policy); policy_hash + 0);
if (r < 0) if (r < 0)
return log_error_errno(r, "Could not calculate sealing policy digest: %m"); return log_error_errno(r, "Could not calculate sealing policy digest for shard 0: %m");
if (arg_tpm2_pcrlock && iovec_is_set(&pubkey)) {
r = tpm2_calculate_sealing_policy(
arg_tpm2_hash_pcr_values,
arg_tpm2_n_hash_pcr_values,
/* pubkey= */ NULL, /* Turn this one off for the 2nd shard */
/* use_pin= */ false,
&pcrlock_policy, /* But turn this one on */
policy_hash + 1);
if (r < 0)
return log_error_errno(r, "Could not calculate sealing policy digest for shard 1: %m");
n_policy_hash++;
}
struct iovec *blobs = NULL;
size_t n_blobs = 0;
CLEANUP_ARRAY(blobs, n_blobs, iovec_array_free);
if (arg_tpm2_device_key) {
if (n_policy_hash > 1)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"Combined signed PCR policies and pcrlock policies cannot be calculated offline, currently.");
blobs = new0(struct iovec, 1);
if (!blobs)
return log_oom();
n_blobs = 1;
if (arg_tpm2_device_key)
r = tpm2_calculate_seal( r = tpm2_calculate_seal(
arg_tpm2_seal_key_handle, arg_tpm2_seal_key_handle,
&device_key_public, &device_key_public,
/* attributes= */ NULL, /* attributes= */ NULL,
/* secret= */ NULL, /* secret= */ NULL,
&policy, policy_hash + 0,
/* pin= */ NULL, /* pin= */ NULL,
&secret, &secret,
&blob, blobs + 0,
&srk); &srk);
else } else
r = tpm2_seal(tpm2_context, r = tpm2_seal(tpm2_context,
arg_tpm2_seal_key_handle, arg_tpm2_seal_key_handle,
&policy, policy_hash,
n_policy_hash,
/* pin= */ NULL, /* pin= */ NULL,
&secret, &secret,
&blob, &blobs,
&n_blobs,
/* ret_primary_alg= */ NULL, /* ret_primary_alg= */ NULL,
&srk); &srk);
if (r < 0) if (r < 0)
@ -4361,6 +4397,11 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta
if (keyslot < 0) if (keyslot < 0)
return log_error_errno(keyslot, "Failed to add new TPM2 key: %m"); return log_error_errno(keyslot, "Failed to add new TPM2 key: %m");
struct iovec policy_hash_as_iovec[2] = {
IOVEC_MAKE(policy_hash[0].buffer, policy_hash[0].size),
IOVEC_MAKE(policy_hash[1].buffer, policy_hash[1].size),
};
r = tpm2_make_luks2_json( r = tpm2_make_luks2_json(
keyslot, keyslot,
hash_pcr_mask, hash_pcr_mask,
@ -4368,8 +4409,10 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta
&pubkey, &pubkey,
arg_tpm2_public_key_pcr_mask, arg_tpm2_public_key_pcr_mask,
/* primary_alg= */ 0, /* primary_alg= */ 0,
&blob, blobs,
&IOVEC_MAKE(policy.buffer, policy.size), n_blobs,
policy_hash_as_iovec,
n_policy_hash,
/* salt= */ NULL, /* no salt because tpm2_seal has no pin */ /* salt= */ NULL, /* no salt because tpm2_seal has no pin */
&srk, &srk,
&pcrlock_policy.nv_handle, &pcrlock_policy.nv_handle,

View File

@ -957,12 +957,18 @@ int encrypt_credential_and_warn(
if (r < 0) if (r < 0)
return log_error_errno(r, "Could not calculate sealing policy digest: %m"); 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, r = tpm2_seal(tpm2_context,
/* seal_key_handle= */ 0, /* seal_key_handle= */ 0,
&tpm2_policy, &tpm2_policy,
/* n_policy_hash= */ 1,
/* pin= */ NULL, /* pin= */ NULL,
&tpm2_key, &tpm2_key,
&tpm2_blob, &blobs,
&n_blobs,
&tpm2_primary_alg, &tpm2_primary_alg,
/* ret_srk= */ NULL); /* ret_srk= */ NULL);
if (r < 0) { 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)) if (!iovec_memdup(&IOVEC_MAKE(tpm2_policy.buffer, tpm2_policy.size), &tpm2_policy_hash))
return log_oom(); return log_oom();
assert(n_blobs == 1);
tpm2_blob = TAKE_STRUCT(blobs[0]);
assert(tpm2_blob.iov_len <= CREDENTIAL_FIELD_SIZE_MAX); assert(tpm2_blob.iov_len <= CREDENTIAL_FIELD_SIZE_MAX);
assert(tpm2_policy_hash.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, /* pcrlock_policy= */ NULL,
le16toh(t->primary_alg), le16toh(t->primary_alg),
&IOVEC_MAKE(t->policy_hash_and_blob, le32toh(t->blob_size)), &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)), &IOVEC_MAKE(t->policy_hash_and_blob + le32toh(t->blob_size), le32toh(t->policy_hash_size)),
/* n_policy_hash= */ 1,
/* srk= */ NULL, /* srk= */ NULL,
&tpm2_key); &tpm2_key);
if (r < 0) if (r < 0)

View File

@ -70,8 +70,10 @@ int acquire_tpm2_key(
const char *key_file, const char *key_file,
size_t key_file_size, size_t key_file_size,
uint64_t key_file_offset, uint64_t key_file_offset,
const struct iovec *key_data, const struct iovec blobs[],
const struct iovec *policy_hash, size_t n_blobs,
const struct iovec policy_hash[],
size_t n_policy_hash,
const struct iovec *salt, const struct iovec *salt,
const struct iovec *srk, const struct iovec *srk,
const struct iovec *pcrlock_nv, const struct iovec *pcrlock_nv,
@ -82,9 +84,8 @@ int acquire_tpm2_key(
struct iovec *ret_decrypted_key) { struct iovec *ret_decrypted_key) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *signature_json = NULL; _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; _cleanup_free_ char *auto_device = NULL;
struct iovec blob;
int r; int r;
assert(iovec_is_valid(salt)); assert(iovec_is_valid(salt));
@ -99,9 +100,7 @@ int acquire_tpm2_key(
device = auto_device; device = auto_device;
} }
if (iovec_is_set(key_data)) if (n_blobs == 0) {
blob = *key_data;
else {
_cleanup_free_ char *bindname = NULL; _cleanup_free_ char *bindname = NULL;
/* If we read the salt via AF_UNIX, make this client recognizable */ /* 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, key_file_size == 0 ? SIZE_MAX : key_file_size,
READ_FULL_FILE_CONNECT_SOCKET, READ_FULL_FILE_CONNECT_SOCKET,
bindname, bindname,
(char**) &loaded_blob, &blob.iov_len); (char**) &loaded_blob.iov_base, &loaded_blob.iov_len);
if (r < 0) if (r < 0)
return r; return r;
blob.iov_base = loaded_blob; blobs = &loaded_blob;
n_blobs = 1;
} }
if (pubkey_pcr_mask != 0) { if (pubkey_pcr_mask != 0) {
@ -158,8 +158,10 @@ int acquire_tpm2_key(
/* pin= */ NULL, /* pin= */ NULL,
FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK) ? &pcrlock_policy : NULL, FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK) ? &pcrlock_policy : NULL,
primary_alg, primary_alg,
&blob, blobs,
n_blobs,
policy_hash, policy_hash,
n_policy_hash,
srk, srk,
ret_decrypted_key); ret_decrypted_key);
if (r < 0) if (r < 0)
@ -204,8 +206,10 @@ int acquire_tpm2_key(
b64_salted_pin, b64_salted_pin,
FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK) ? &pcrlock_policy : NULL, FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK) ? &pcrlock_policy : NULL,
primary_alg, primary_alg,
&blob, blobs,
n_blobs,
policy_hash, policy_hash,
n_policy_hash,
srk, srk,
ret_decrypted_key); ret_decrypted_key);
if (r < 0) { if (r < 0) {
@ -230,8 +234,10 @@ int find_tpm2_auto_data(
struct iovec *ret_pubkey, struct iovec *ret_pubkey,
uint32_t *ret_pubkey_pcr_mask, uint32_t *ret_pubkey_pcr_mask,
uint16_t *ret_primary_alg, uint16_t *ret_primary_alg,
struct iovec *ret_blob, struct iovec **ret_blobs,
struct iovec *ret_policy_hash, size_t *ret_n_blobs,
struct iovec **ret_policy_hash,
size_t *ret_n_policy_hash,
struct iovec *ret_salt, struct iovec *ret_salt,
struct iovec *ret_srk, struct iovec *ret_srk,
struct iovec *ret_pcrlock_nv, struct iovec *ret_pcrlock_nv,
@ -242,15 +248,35 @@ int find_tpm2_auto_data(
int r, token; int r, token;
assert(cd); 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++) { 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; _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; uint32_t hash_pcr_mask, pubkey_pcr_mask;
uint16_t pcr_bank, primary_alg; uint16_t pcr_bank, primary_alg;
TPM2Flags flags; TPM2Flags flags;
int keyslot; 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); r = cryptsetup_get_token_as_json(cd, token, "systemd-tpm2", &v);
if (IN_SET(r, -ENOENT, -EINVAL, -EMEDIUMTYPE)) if (IN_SET(r, -ENOENT, -EINVAL, -EMEDIUMTYPE))
continue; continue;
@ -265,8 +291,10 @@ int find_tpm2_auto_data(
&pubkey, &pubkey,
&pubkey_pcr_mask, &pubkey_pcr_mask,
&primary_alg, &primary_alg,
&blob, &blobs,
&n_blobs,
&policy_hash, &policy_hash,
&n_policy_hash,
&salt, &salt,
&srk, &srk,
&pcrlock_nv, &pcrlock_nv,
@ -287,8 +315,10 @@ int find_tpm2_auto_data(
*ret_pubkey = TAKE_STRUCT(pubkey); *ret_pubkey = TAKE_STRUCT(pubkey);
*ret_pubkey_pcr_mask = pubkey_pcr_mask; *ret_pubkey_pcr_mask = pubkey_pcr_mask;
*ret_primary_alg = primary_alg; *ret_primary_alg = primary_alg;
*ret_blob = TAKE_STRUCT(blob); *ret_blobs = TAKE_PTR(blobs);
*ret_policy_hash = TAKE_STRUCT(policy_hash); *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_salt = TAKE_STRUCT(salt);
*ret_keyslot = keyslot; *ret_keyslot = keyslot;
*ret_token = token; *ret_token = token;

View File

@ -24,8 +24,10 @@ int acquire_tpm2_key(
const char *key_file, const char *key_file,
size_t key_file_size, size_t key_file_size,
uint64_t key_file_offset, uint64_t key_file_offset,
const struct iovec *key_data, const struct iovec blobs[],
const struct iovec *policy_hash, size_t n_blobs,
const struct iovec policy_hash[],
size_t n_policy_hash,
const struct iovec *salt, const struct iovec *salt,
const struct iovec *srk, const struct iovec *srk,
const struct iovec *pcrlock_nv, const struct iovec *pcrlock_nv,
@ -44,8 +46,10 @@ int find_tpm2_auto_data(
struct iovec *ret_pubkey, struct iovec *ret_pubkey,
uint32_t *ret_pubkey_pcr_mask, uint32_t *ret_pubkey_pcr_mask,
uint16_t *ret_primary_alg, uint16_t *ret_primary_alg,
struct iovec *ret_blob, struct iovec **ret_blobs,
struct iovec *ret_policy_hash, size_t *ret_n_blobs,
struct iovec **ret_policy_hash,
size_t *ret_n_policy_hash,
struct iovec *ret_salt, struct iovec *ret_salt,
struct iovec *ret_srk, struct iovec *ret_srk,
struct iovec *ret_pcrlock_nv, struct iovec *ret_pcrlock_nv,
@ -68,8 +72,10 @@ static inline int acquire_tpm2_key(
const char *key_file, const char *key_file,
size_t key_file_size, size_t key_file_size,
uint64_t key_file_offset, uint64_t key_file_offset,
const struct iovec *key_data, const struct iovec blobs[],
const struct iovec *policy_hash, size_t n_blobs,
const struct iovec policy_hash[],
size_t n_policy_hash,
const struct iovec *salt, const struct iovec *salt,
const struct iovec *srk, const struct iovec *srk,
const struct iovec *pcrlock_nv, const struct iovec *pcrlock_nv,
@ -92,8 +98,10 @@ static inline int find_tpm2_auto_data(
struct iovec *ret_pubkey, struct iovec *ret_pubkey,
uint32_t *ret_pubkey_pcr_mask, uint32_t *ret_pubkey_pcr_mask,
uint16_t *ret_primary_alg, uint16_t *ret_primary_alg,
struct iovec *ret_blob, struct iovec **ret_blobs,
struct iovec *ret_policy_hash, size_t *ret_n_blobs,
struct iovec **ret_policy_hash,
size_t *ret_n_policy_hash,
struct iovec *ret_salt, struct iovec *ret_salt,
struct iovec *ret_srk, struct iovec *ret_srk,
struct iovec *ret_pcrlock_nv, struct iovec *ret_pcrlock_nv,

View File

@ -4179,8 +4179,11 @@ int tpm2_calculate_sealing_policy(
assert(pcr_values || n_pcr_values == 0); assert(pcr_values || n_pcr_values == 0);
assert(digest); 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) 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) { if (public) {
r = tpm2_calculate_policy_authorize(public, NULL, digest); r = tpm2_calculate_policy_authorize(public, NULL, digest);
@ -5375,10 +5378,12 @@ int tpm2_calculate_seal(
int tpm2_seal(Tpm2Context *c, int tpm2_seal(Tpm2Context *c,
uint32_t seal_key_handle, uint32_t seal_key_handle,
const TPM2B_DIGEST *policy, const TPM2B_DIGEST policy[],
size_t n_policy,
const char *pin, const char *pin,
struct iovec *ret_secret, struct iovec *ret_secret,
struct iovec *ret_blob, struct iovec **ret_blobs,
size_t *ret_n_blobs,
uint16_t *ret_primary_alg, uint16_t *ret_primary_alg,
struct iovec *ret_srk) { struct iovec *ret_srk) {
@ -5386,7 +5391,8 @@ int tpm2_seal(Tpm2Context *c,
int r; int r;
assert(ret_secret); 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 /* 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 * 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, .objectAttributes = hmac_attributes,
.parameters.keyedHashDetail.scheme.scheme = TPM2_ALG_NULL, .parameters.keyedHashDetail.scheme.scheme = TPM2_ALG_NULL,
.unique.keyedHash.size = SHA256_DIGEST_SIZE, .unique.keyedHash.size = SHA256_DIGEST_SIZE,
.authPolicy = policy ? *policy : TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE),
}; };
TPMS_SENSITIVE_CREATE hmac_sensitive = { TPMS_SENSITIVE_CREATE hmac_sensitive = {
@ -5443,12 +5448,6 @@ int tpm2_seal(Tpm2Context *c,
(void) tpm2_credit_random(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; _cleanup_(tpm2_handle_freep) Tpm2Handle *primary_handle = NULL;
if (ret_srk) { if (ret_srk) {
_cleanup_(Esys_Freep) TPM2B_PUBLIC *primary_public = NULL; _cleanup_(Esys_Freep) TPM2B_PUBLIC *primary_public = NULL;
@ -5526,24 +5525,51 @@ int tpm2_seal(Tpm2Context *c,
if (r < 0) if (r < 0)
return r; return r;
_cleanup_(Esys_Freep) TPM2B_PUBLIC *public = NULL; log_debug("Generating secret key data.");
_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;
/* 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 = {}; _cleanup_(iovec_done_erase) struct iovec secret = {};
secret.iov_base = memdup(hmac_sensitive.data.buffer, hmac_sensitive.data.size); r = crypto_random_bytes_allocate_iovec(hmac_sensitive.data.size * n_shards, &secret);
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);
if (r < 0) 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) if (DEBUG_LOGGING)
log_debug("Completed TPM2 key sealing in %s.", FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - start, 1)); 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_secret = TAKE_STRUCT(secret);
*ret_blob = TAKE_STRUCT(blob); *ret_blobs = TAKE_PTR(blobs);
*ret_n_blobs = n_blobs;
if (ret_primary_alg) if (ret_primary_alg)
*ret_primary_alg = primary_alg; *ret_primary_alg = primary_alg;
@ -5590,16 +5617,17 @@ int tpm2_unseal(Tpm2Context *c,
const char *pin, const char *pin,
const Tpm2PCRLockPolicy *pcrlock_policy, const Tpm2PCRLockPolicy *pcrlock_policy,
uint16_t primary_alg, uint16_t primary_alg,
const struct iovec *blob, const struct iovec blobs[],
const struct iovec *known_policy_hash, size_t n_blobs,
const struct iovec known_policy_hash[],
size_t n_known_policy_hash,
const struct iovec *srk, const struct iovec *srk,
struct iovec *ret_secret) { struct iovec *ret_secret) {
TSS2_RC rc; TSS2_RC rc;
int r; int r;
assert(iovec_is_set(blob)); assert(n_blobs > 0);
assert(iovec_is_valid(known_policy_hash));
assert(iovec_is_valid(pubkey)); assert(iovec_is_valid(pubkey));
assert(ret_secret); assert(ret_secret);
@ -5616,12 +5644,11 @@ int tpm2_unseal(Tpm2Context *c,
usec_t start = now(CLOCK_MONOTONIC); usec_t start = now(CLOCK_MONOTONIC);
TPM2B_PUBLIC public; size_t n_shards = pcrlock_policy && iovec_is_set(pubkey) ? 2 : 1;
TPM2B_PRIVATE private; if (n_blobs != n_shards)
TPM2B_ENCRYPTED_SECRET seed = {}; return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Number of provided key blobs (%zu) does not match policy requirements (%zu).", n_blobs, n_shards);
r = tpm2_unmarshal_blob(blob->iov_base, blob->iov_len, &public, &private, &seed); if (n_known_policy_hash > 0 && n_known_policy_hash != n_shards)
if (r < 0) 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);
return log_debug_errno(r, "Could not extract parts from blob: %m");
/* Older code did not save the pcr_bank, and unsealing needed to detect the best pcr bank to use, /* 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. */ * so we need to handle that legacy situation. */
@ -5657,37 +5684,6 @@ int tpm2_unseal(Tpm2Context *c,
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"No SRK or primary alg provided."); "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; TPM2B_PUBLIC pubkey_tpm2b;
_cleanup_(iovec_done) struct iovec fp = {}; _cleanup_(iovec_done) struct iovec fp = {};
if (iovec_is_set(pubkey)) { 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"); return log_debug_errno(r, "Could not get key fingerprint: %m");
} }
/* _cleanup_(iovec_done_erase) struct iovec secret = {};
* 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;
for (unsigned i = RETRY_UNSEAL_MAX;; i--) { for (unsigned i = RETRY_UNSEAL_MAX;; i--) {
_cleanup_(tpm2_handle_freep) Tpm2Handle *encryption_session = NULL; bool retry = false;
r = tpm2_make_encryption_session(c, primary_handle, hmac_key, &encryption_session); iovec_done_erase(&secret); /* clear data from previous unseal attempt */
if (r < 0)
return r;
_cleanup_(tpm2_handle_freep) Tpm2Handle *policy_session = NULL; for (size_t shard = 0; shard < n_blobs; shard++) {
_cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL; TPM2B_PUBLIC public;
r = tpm2_make_policy_session( TPM2B_PRIVATE private;
c, TPM2B_ENCRYPTED_SECRET seed = {};
primary_handle, r = tpm2_unmarshal_blob(blobs[shard].iov_base, blobs[shard].iov_len, &public, &private, &seed);
encryption_session, if (r < 0)
&policy_session); return log_debug_errno(r, "Could not extract parts from blob: %m");
if (r < 0)
return r;
r = tpm2_build_sealing_policy( if (seed.size > 0) {
c, /* This is a calculated (or duplicated) sealed object, and must be imported. */
policy_session, _cleanup_free_ TPM2B_PRIVATE *imported_private = NULL;
hash_pcr_mask, r = tpm2_import(c,
pcr_bank, primary_handle,
iovec_is_set(pubkey) ? &pubkey_tpm2b : NULL, /* session= */ NULL,
fp.iov_base, fp.iov_len, &public,
pubkey_pcr_mask, &private,
signature, &seed,
!!pin, /* encryption_key= */ NULL,
pcrlock_policy, /* symmetric= */ NULL,
&policy_digest); &imported_private);
if (r < 0) if (r < 0)
return r; return r;
/* If we know the policy hash to expect, and it doesn't match, we can shortcut things here, and not private = *imported_private;
* wait until the TPM2 tells us to go away. */ }
if (iovec_is_set(known_policy_hash) && memcmp_nn(policy_digest->buffer,
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, policy_digest->size,
known_policy_hash->iov_base, known_policy_hash[shard].iov_base,
known_policy_hash->iov_len) != 0) { known_policy_hash[shard].iov_len) != 0) {
#if HAVE_OPENSSL #if HAVE_OPENSSL
if (iovec_is_set(pubkey) && if (shard == 0 &&
pubkey_tpm2b.publicArea.type == TPM2_ALG_RSA && iovec_is_set(pubkey) &&
pubkey_tpm2b.publicArea.parameters.rsaDetail.exponent == TPM2_RSA_DEFAULT_EXPONENT) { pubkey_tpm2b.publicArea.type == TPM2_ALG_RSA &&
/* Due to bug #30546, if using RSA pubkey with the default exponent, we may pubkey_tpm2b.publicArea.parameters.rsaDetail.exponent == TPM2_RSA_DEFAULT_EXPONENT) {
* need to set the exponent to the TPM special-case value of 0 and retry. */ /* Due to bug #30546, if using RSA pubkey with the default exponent, we may
log_debug("Policy hash mismatch, retrying with RSA pubkey exponent set to 0."); * need to set the exponent to the TPM special-case value of 0 and retry. */
pubkey_tpm2b.publicArea.parameters.rsaDetail.exponent = 0; log_debug("Policy hash mismatch, retrying with RSA pubkey exponent set to 0.");
continue; pubkey_tpm2b.publicArea.parameters.rsaDetail.exponent = 0;
} else retry = true;
break;
}
#endif #endif
return log_debug_errno(SYNTHETIC_ERRNO(EPERM), return log_debug_errno(SYNTHETIC_ERRNO(EPERM),
"Current policy digest does not match stored policy digest, cancelling " "Current policy digest does not match stored policy digest, cancelling "
"TPM2 authentication attempt."); "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."); if (!retry)
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)
break; 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) if (DEBUG_LOGGING)
log_debug("Completed TPM2 key unsealing in %s.", FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - start, 1)); log_debug("Completed TPM2 key unsealing in %s.", FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - start, 1));
*ret_secret = TAKE_STRUCT(secret); *ret_secret = TAKE_STRUCT(secret);
return 0; return 0;
} }
@ -7344,6 +7385,46 @@ int tpm2_parse_pcr_json_array(sd_json_variant *v, uint32_t *ret) {
return 0; 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 tpm2_make_luks2_json(
int keyslot, int keyslot,
uint32_t hash_pcr_mask, uint32_t hash_pcr_mask,
@ -7351,8 +7432,10 @@ int tpm2_make_luks2_json(
const struct iovec *pubkey, const struct iovec *pubkey,
uint32_t pubkey_pcr_mask, uint32_t pubkey_pcr_mask,
uint16_t primary_alg, uint16_t primary_alg,
const struct iovec *blob, const struct iovec blobs[],
const struct iovec *policy_hash, size_t n_blobs,
const struct iovec policy_hash[],
size_t n_policy_hash,
const struct iovec *salt, const struct iovec *salt,
const struct iovec *srk, const struct iovec *srk,
const struct iovec *pcrlock_nv, const struct iovec *pcrlock_nv,
@ -7364,8 +7447,8 @@ int tpm2_make_luks2_json(
int r; int r;
assert(iovec_is_valid(pubkey)); assert(iovec_is_valid(pubkey));
assert(iovec_is_valid(blob)); assert(n_blobs >= 1);
assert(iovec_is_valid(policy_hash)); assert(n_policy_hash >= 1);
if (asprintf(&keyslot_as_string, "%i", keyslot) < 0) if (asprintf(&keyslot_as_string, "%i", keyslot) < 0)
return -ENOMEM; return -ENOMEM;
@ -7380,6 +7463,16 @@ int tpm2_make_luks2_json(
return r; 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 /* 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 * 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. */ * object should use "_" rather than "-" in field names. */
@ -7388,11 +7481,11 @@ int tpm2_make_luks2_json(
&v, &v,
SD_JSON_BUILD_PAIR("type", JSON_BUILD_CONST_STRING("systemd-tpm2")), 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("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("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(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_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_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(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)), 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; 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( int tpm2_parse_luks2_json(
sd_json_variant *v, sd_json_variant *v,
int *ret_keyslot, int *ret_keyslot,
@ -7417,14 +7567,16 @@ int tpm2_parse_luks2_json(
struct iovec *ret_pubkey, struct iovec *ret_pubkey,
uint32_t *ret_pubkey_pcr_mask, uint32_t *ret_pubkey_pcr_mask,
uint16_t *ret_primary_alg, uint16_t *ret_primary_alg,
struct iovec *ret_blob, struct iovec **ret_blobs,
struct iovec *ret_policy_hash, size_t *ret_n_blobs,
struct iovec **ret_policy_hash,
size_t *ret_n_policy_hash,
struct iovec *ret_salt, struct iovec *ret_salt,
struct iovec *ret_srk, struct iovec *ret_srk,
struct iovec *ret_pcrlock_nv, struct iovec *ret_pcrlock_nv,
TPM2Flags *ret_flags) { 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; 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 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 */ uint16_t pcr_bank = UINT16_MAX; /* default: pick automatically */
@ -7489,17 +7641,25 @@ int tpm2_parse_luks2_json(
if (!w) if (!w)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 token data lacks 'tpm2-blob' field."); 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) 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"); w = sd_json_variant_by_key(v, "tpm2-policy-hash");
if (!w) if (!w)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 token data lacks 'tpm2-policy-hash' field."); 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) 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"); w = sd_json_variant_by_key(v, "tpm2-pin");
if (w) { if (w) {
@ -7565,10 +7725,14 @@ int tpm2_parse_luks2_json(
*ret_pubkey_pcr_mask = pubkey_pcr_mask; *ret_pubkey_pcr_mask = pubkey_pcr_mask;
if (ret_primary_alg) if (ret_primary_alg)
*ret_primary_alg = primary_alg; *ret_primary_alg = primary_alg;
if (ret_blob) if (ret_blobs)
*ret_blob = TAKE_STRUCT(blob); *ret_blobs = TAKE_PTR(blobs);
if (ret_n_blobs)
*ret_n_blobs = n_blobs;
if (ret_policy_hash) 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) if (ret_salt)
*ret_salt = TAKE_STRUCT(salt); *ret_salt = TAKE_STRUCT(salt);
if (ret_srk) if (ret_srk)

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_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_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_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 *blob, const struct iovec *policy_hash, const struct iovec *srk, struct iovec *ret_secret); 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 #if HAVE_OPENSSL
int tpm2_tpm2b_public_to_openssl_pkey(const TPM2B_PUBLIC *public, EVP_PKEY **ret); 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_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_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_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_blob, struct iovec *ret_policy_hash, struct iovec *ret_salt, struct iovec *ret_srk, struct iovec *pcrlock_nv, TPM2Flags *ret_flags); 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 */ /* Default to PCR 7 only */
#define TPM2_PCR_INDEX_DEFAULT UINT32_C(7) #define TPM2_PCR_INDEX_DEFAULT UINT32_C(7)

View File

@ -1163,7 +1163,9 @@ static void calculate_seal_and_unseal(
/* pcrlock_policy= */ NULL, /* pcrlock_policy= */ NULL,
/* primary_alg= */ 0, /* primary_alg= */ 0,
&blob, &blob,
/* n_blobs= */ 1,
/* known_policy_hash= */ NULL, /* known_policy_hash= */ NULL,
/* n_known_policy_hash= */ 0,
&serialized_parent, &serialized_parent,
&unsealed_secret) >= 0); &unsealed_secret) >= 0);
@ -1222,14 +1224,20 @@ static void check_seal_unseal_for_handle(Tpm2Context *c, TPM2_HANDLE handle) {
log_debug("Check seal/unseal for handle 0x%" PRIx32, handle); log_debug("Check seal/unseal for handle 0x%" PRIx32, handle);
_cleanup_(iovec_done) struct iovec secret = {}, blob = {}, srk = {}, unsealed_secret = {}; _cleanup_(iovec_done) struct iovec secret = {}, srk = {}, unsealed_secret = {};
struct iovec *blobs = NULL;
size_t n_blobs = 0;
CLEANUP_ARRAY(blobs, n_blobs, iovec_array_free);
assert_se(tpm2_seal( assert_se(tpm2_seal(
c, c,
handle, handle,
&policy, &policy,
1,
/* pin= */ NULL, /* pin= */ NULL,
&secret, &secret,
&blob, &blobs,
&n_blobs,
/* ret_primary_alg= */ NULL, /* ret_primary_alg= */ NULL,
&srk) >= 0); &srk) >= 0);
@ -1243,8 +1251,10 @@ static void check_seal_unseal_for_handle(Tpm2Context *c, TPM2_HANDLE handle) {
/* pin= */ NULL, /* pin= */ NULL,
/* pcrlock_policy= */ NULL, /* pcrlock_policy= */ NULL,
/* primary_alg= */ 0, /* primary_alg= */ 0,
&blob, blobs,
n_blobs,
/* policy_hash= */ NULL, /* policy_hash= */ NULL,
/* n_policy_hash= */ 0,
&srk, &srk,
&unsealed_secret) >= 0); &unsealed_secret) >= 0);

View File

@ -10,9 +10,10 @@ export SYSTEMD_LOG_LEVEL=debug
export PAGER= export PAGER=
SD_PCREXTEND="/usr/lib/systemd/systemd-pcrextend" SD_PCREXTEND="/usr/lib/systemd/systemd-pcrextend"
SD_PCRLOCK="/usr/lib/systemd/systemd-pcrlock" SD_PCRLOCK="/usr/lib/systemd/systemd-pcrlock"
SD_MEASURE="/usr/lib/systemd/systemd-measure"
if [[ ! -x "${SD_PCREXTEND:?}" ]] || [[ ! -x "${SD_PCRLOCK:?}" ]] ; then if [[ ! -x "${SD_PCREXTEND:?}" ]] || [[ ! -x "${SD_PCRLOCK:?}" ]] || [[ ! -x "${SD_MEASURE:?}" ]] ; then
echo "$SD_PCREXTEND or $SD_PCRLOCK not found, skipping pcrlock tests" echo "$SD_PCREXTEND or $SD_PCRLOCK or $SD_MEASURE not found, skipping pcrlock tests"
exit 0 exit 0
fi fi
@ -127,6 +128,17 @@ echo -n test70-take-two | "$SD_PCRLOCK" lock-raw --pcrlock=/var/lib/pcrlock.d/92
systemd-cryptsetup attach pcrlock "$img" - tpm2-device=auto,tpm2-pcrlock=/var/lib/systemd/pcrlock.json,headless systemd-cryptsetup attach pcrlock "$img" - tpm2-device=auto,tpm2-pcrlock=/var/lib/systemd/pcrlock.json,headless
systemd-cryptsetup detach pcrlock systemd-cryptsetup detach pcrlock
# Now combined pcrlock and signed PCR
# Generate key pair
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out "$img".private.pem
openssl rsa -pubout -in "$img".private.pem -out "$img".public.pem
systemd-cryptenroll --unlock-tpm2-device=auto --tpm2-device=auto --tpm2-pcrlock=/var/lib/systemd/pcrlock.json --tpm2-public-key="$img".public.pem --wipe-slot=tpm2 "$img"
"$SD_MEASURE" sign --current --bank=sha256 --private-key="$img".private.pem --public-key="$img".public.pem --phase=: | tee "$img".pcrsign
SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=0 systemd-cryptsetup attach pcrlock "$img" - "tpm2-device=auto,tpm2-pcrlock=/var/lib/systemd/pcrlock.json,tpm2-signature=$img.pcrsign,headless"
systemd-cryptsetup detach pcrlock
systemd-cryptenroll --unlock-key-file=/tmp/pcrlockpwd --tpm2-device=auto --tpm2-pcrlock=/var/lib/systemd/pcrlock.json --wipe-slot=tpm2 "$img"
rm "$img".public.pem "$img".private.pem "$img".pcrsign
# Now use the root fs support, i.e. make the tool write a copy of the pcrlock # Now use the root fs support, i.e. make the tool write a copy of the pcrlock
# file as service credential to some temporary dir and remove the local copy, so that # file as service credential to some temporary dir and remove the local copy, so that
# it has to use the credential version. # it has to use the credential version.