#include "errno-util.h"
#include "fileio.h"
#include "hexdecoct.h"
+#include "json-util.h"
#include "log.h"
#include "memory-util.h"
#include "random-util.h"
static int search_policy_hash(
struct crypt_device *cd,
- const struct iovec *hash) {
+ const struct iovec policy_hash[],
+ size_t n_policy_hash) {
int r;
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;
for (int token = 0; token < sym_crypt_token_max(CRYPT_LUKS2); token++) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
- _cleanup_free_ void *thash = NULL;
- size_t thash_size = 0;
int keyslot;
sd_json_variant *w;
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"TPM2 token data lacks 'tpm2-policy-hash' field.");
- r = sd_json_variant_unhex(w, &thash, &thash_size);
- if (r < 0)
- return log_error_errno(r, "Invalid base64 data in 'tpm2-policy-hash' field: %m");
+ /* This is either an array of strings (for sharded enrollments), or a single string */
+ if (sd_json_variant_is_array(w)) {
+
+ if (sd_json_variant_elements(w) == n_policy_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 (memcmp_nn(hash->iov_base, hash->iov_len, thash, thash_size) == 0)
- return keyslot; /* Found entry with same 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 */
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;
+ size_t n_blobs = 0, n_policy_hash = 0;
uint32_t hash_pcr_mask, pubkey_pcr_mask;
uint16_t pcr_bank, primary_alg;
TPM2Flags tpm2_flags;
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(
cd,
UINT32_MAX,
&pubkey,
&pubkey_pcr_mask,
&primary_alg,
- &blob,
+ &blobs,
+ &n_blobs,
&policy_hash,
+ &n_policy_hash,
&salt,
&srk,
&pcrlock_nv,
/* pcrlock_path= */ NULL,
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,
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL, *signature_json = 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 = {};
const char *node;
_cleanup_(erase_and_freep) char *pin_str = NULL;
}
TPM2B_PUBLIC public = {};
- /* Load the PCR public key if specified explicitly, or if no pcrlock policy was specified and
- * 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)) {
+ if (pcr_pubkey_path || load_pcr_pubkey) {
r = tpm2_load_pcr_public_key(pcr_pubkey_path, &pubkey.iov_base, &pubkey.iov_len);
if (r < 0) {
if (pcr_pubkey_path || signature_path || r != -ENOENT)
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(
hash_pcr_values,
n_hash_pcr_values,
iovec_is_set(&pubkey) ? &public : NULL,
use_pin,
- pcrlock_path ? &pcrlock_policy : NULL,
- &policy);
+ pcrlock_path && !iovec_is_set(&pubkey) ? &pcrlock_policy : NULL,
+ policy_hash + 0);
if (r < 0)
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(
seal_key_handle,
&device_key_public,
/* attributes= */ NULL,
/* secret= */ NULL,
- &policy,
+ policy_hash + 0,
pin_str,
&secret,
- &blob,
+ blobs + 0,
&srk);
- else
+ } else
r = tpm2_seal(tpm2_context,
seal_key_handle,
- &policy,
+ policy_hash,
+ n_policy_hash,
pin_str,
&secret,
- &blob,
+ &blobs,
+ &n_blobs,
/* ret_primary_alg= */ NULL,
&srk);
if (r < 0)
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. */
- 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)
log_debug_errno(r, "PCR policy hash not yet enrolled, enrolling now.");
else if (r < 0)
pin_str,
pcrlock_path ? &pcrlock_policy : NULL,
/* primary_alg= */ 0,
- &blob,
- &IOVEC_MAKE(policy.buffer, policy.size),
+ blobs,
+ n_blobs,
+ policy_hash_as_iovec,
+ n_policy_hash,
&srk,
&secret2);
if (r < 0)
&pubkey,
pubkey_pcr_mask,
/* primary_alg= */ 0,
- &blob,
- &IOVEC_MAKE(policy.buffer, policy.size),
+ blobs,
+ n_blobs,
+ policy_hash_as_iovec,
+ n_policy_hash,
use_pin ? &IOVEC_MAKE(binary_salt, sizeof(binary_salt)) : NULL,
&srk,
pcrlock_path ? &pcrlock_policy.nv_handle : NULL,
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;
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,
&pubkey,
&pubkey_pcr_mask,
&primary_alg,
- &blob,
+ &blobs,
+ &n_blobs,
&policy_hash,
+ &n_policy_hash,
&salt,
&srk,
&pcrlock_nv,
pin_string,
params.pcrlock_path,
primary_alg,
- &blob,
- &policy_hash,
+ blobs,
+ n_blobs,
+ policy_hash,
+ n_policy_hash,
&salt,
&srk,
&pcrlock_nv,
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;
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,
&pubkey,
&pubkey_pcr_mask,
&primary_alg,
- &blob,
+ &blobs,
+ &n_blobs,
&policy_hash,
+ &n_policy_hash,
&salt,
&srk,
&pcrlock_nv,
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);
+ }
}
/*
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) {
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) {
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,
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)
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,
/* 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,
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 */
&pubkey,
&pubkey_pcr_mask,
&primary_alg,
- &blob,
+ &blobs,
+ &n_blobs,
&policy_hash,
+ &n_policy_hash,
&salt,
&srk,
&pcrlock_nv,
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,
if (IN_SET(p->encrypt, ENCRYPT_TPM2, ENCRYPT_KEY_FILE_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_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
ssize_t base64_encoded_size;
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(
arg_tpm2_hash_pcr_values,
arg_tpm2_n_hash_pcr_values,
iovec_is_set(&pubkey) ? &public : NULL,
/* use_pin= */ false,
- arg_tpm2_pcrlock ? &pcrlock_policy : NULL,
- &policy);
+ arg_tpm2_pcrlock && !iovec_is_set(&pubkey) ? &pcrlock_policy : NULL,
+ policy_hash + 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(
arg_tpm2_seal_key_handle,
&device_key_public,
/* attributes= */ NULL,
/* secret= */ NULL,
- &policy,
+ policy_hash + 0,
/* pin= */ NULL,
&secret,
- &blob,
+ blobs + 0,
&srk);
- else
+ } else
r = tpm2_seal(tpm2_context,
arg_tpm2_seal_key_handle,
- &policy,
+ policy_hash,
+ n_policy_hash,
/* pin= */ NULL,
&secret,
- &blob,
+ &blobs,
+ &n_blobs,
/* ret_primary_alg= */ NULL,
&srk);
if (r < 0)
if (keyslot < 0)
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(
keyslot,
hash_pcr_mask,
&pubkey,
arg_tpm2_public_key_pcr_mask,
/* primary_alg= */ 0,
- &blob,
- &IOVEC_MAKE(policy.buffer, policy.size),
+ blobs,
+ n_blobs,
+ policy_hash_as_iovec,
+ n_policy_hash,
/* salt= */ NULL, /* no salt because tpm2_seal has no pin */
&srk,
&pcrlock_policy.nv_handle,
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) {
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);
}
/* 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)
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,
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));
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 */
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) {
/* 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)
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) {
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,
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;
&pubkey,
&pubkey_pcr_mask,
&primary_alg,
- &blob,
+ &blobs,
+ &n_blobs,
&policy_hash,
+ &n_policy_hash,
&salt,
&srk,
&pcrlock_nv,
*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;
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,
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,
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,
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,
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);
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) {
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
.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 = {
(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;
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;
+ r = crypto_random_bytes_allocate_iovec(hmac_sensitive.data.size * n_shards, &secret);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to generate secret key: %m");
- log_debug("Marshalling private and public part of HMAC key.");
+ struct iovec *blobs = new0(struct iovec, n_shards);
+ size_t n_blobs = 0;
- _cleanup_(iovec_done) struct iovec blob = {};
- r = tpm2_marshal_blob(public, private, /* seed= */ NULL, &blob.iov_base, &blob.iov_len);
- if (r < 0)
- return log_debug_errno(r, "Could not create sealed blob: %m");
+ 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));
}
*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;
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);
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. */
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)) {
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 */
+
+ 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");
+
+ 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;
- _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;
+ private = *imported_private;
+ }
- 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;
+ 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 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,
+ /* 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.");
+ 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));
- 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 (!iovec_append(&secret, &IOVEC_MAKE(unsealed->buffer, unsealed->size)))
+ return log_oom_debug();
+
+ explicit_bzero_safe(unsealed->buffer, unsealed->size);
+ }
+
+ 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;
}
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,
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,
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;
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. */
&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)),
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,
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 */
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) {
*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)
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);
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)
/* pcrlock_policy= */ NULL,
/* primary_alg= */ 0,
&blob,
+ /* n_blobs= */ 1,
/* known_policy_hash= */ NULL,
+ /* n_known_policy_hash= */ 0,
&serialized_parent,
&unsealed_secret) >= 0);
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(
c,
handle,
&policy,
+ 1,
/* pin= */ NULL,
&secret,
- &blob,
+ &blobs,
+ &n_blobs,
/* ret_primary_alg= */ NULL,
&srk) >= 0);
/* pin= */ NULL,
/* pcrlock_policy= */ NULL,
/* primary_alg= */ 0,
- &blob,
+ blobs,
+ n_blobs,
/* policy_hash= */ NULL,
+ /* n_policy_hash= */ 0,
&srk,
&unsealed_secret) >= 0);
export PAGER=
SD_PCREXTEND="/usr/lib/systemd/systemd-pcrextend"
SD_PCRLOCK="/usr/lib/systemd/systemd-pcrlock"
+SD_MEASURE="/usr/lib/systemd/systemd-measure"
-if [[ ! -x "${SD_PCREXTEND:?}" ]] || [[ ! -x "${SD_PCRLOCK:?}" ]] ; then
- echo "$SD_PCREXTEND or $SD_PCRLOCK not found, skipping pcrlock tests"
+if [[ ! -x "${SD_PCREXTEND:?}" ]] || [[ ! -x "${SD_PCRLOCK:?}" ]] || [[ ! -x "${SD_MEASURE:?}" ]] ; then
+ echo "$SD_PCREXTEND or $SD_PCRLOCK or $SD_MEASURE not found, skipping pcrlock tests"
exit 0
fi
systemd-cryptsetup attach pcrlock "$img" - tpm2-device=auto,tpm2-pcrlock=/var/lib/systemd/pcrlock.json,headless
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
# file as service credential to some temporary dir and remove the local copy, so that
# it has to use the credential version.