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:
parent
664570f531
commit
8e6587679b
@ -10,6 +10,7 @@
|
||||
#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"
|
||||
@ -18,20 +19,20 @@
|
||||
|
||||
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;
|
||||
|
||||
@ -54,12 +55,45 @@ static int search_policy_hash(
|
||||
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 (memcmp_nn(hash->iov_base, hash->iov_len, thash, thash_size) == 0)
|
||||
return keyslot; /* Found entry with same hash. */
|
||||
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 (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 */
|
||||
@ -154,12 +188,16 @@ int load_volume_key_tpm2(
|
||||
|
||||
for (;;) {
|
||||
_cleanup_(iovec_done) struct iovec pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {};
|
||||
_cleanup_(iovec_done) struct iovec blob = {}, policy_hash = {};
|
||||
struct iovec *blobs = NULL, *policy_hash = NULL;
|
||||
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,
|
||||
@ -169,8 +207,10 @@ int load_volume_key_tpm2(
|
||||
&pubkey,
|
||||
&pubkey_pcr_mask,
|
||||
&primary_alg,
|
||||
&blob,
|
||||
&blobs,
|
||||
&n_blobs,
|
||||
&policy_hash,
|
||||
&n_policy_hash,
|
||||
&salt,
|
||||
&srk,
|
||||
&pcrlock_nv,
|
||||
@ -202,8 +242,10 @@ int load_volume_key_tpm2(
|
||||
/* 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,
|
||||
@ -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_(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;
|
||||
@ -304,10 +346,7 @@ int enroll_tpm2(struct crypt_device *cd,
|
||||
}
|
||||
|
||||
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)
|
||||
@ -398,42 +437,88 @@ int enroll_tpm2(struct crypt_device *cd,
|
||||
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)
|
||||
@ -461,8 +546,10 @@ int enroll_tpm2(struct crypt_device *cd,
|
||||
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)
|
||||
@ -498,8 +585,10 @@ int enroll_tpm2(struct crypt_device *cd,
|
||||
&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,
|
||||
|
@ -42,7 +42,7 @@ _public_ int cryptsetup_token_open_pin(
|
||||
void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) {
|
||||
|
||||
_cleanup_(erase_and_freep) char *base64_encoded = NULL, *pin_string = NULL;
|
||||
_cleanup_(iovec_done) struct iovec blob = {}, pubkey = {}, policy_hash = {}, salt = {}, srk = {}, pcrlock_nv = {};
|
||||
_cleanup_(iovec_done) struct iovec pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {};
|
||||
_cleanup_(iovec_done_erase) struct iovec decrypted_key = {};
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
|
||||
uint32_t hash_pcr_mask, pubkey_pcr_mask;
|
||||
@ -76,6 +76,11 @@ _public_ int cryptsetup_token_open_pin(
|
||||
if (r < 0)
|
||||
return crypt_log_debug_errno(cd, r, "Failed to parse token JSON data: %m");
|
||||
|
||||
struct iovec *blobs = NULL, *policy_hash = NULL;
|
||||
size_t n_blobs = 0, n_policy_hash = 0;
|
||||
CLEANUP_ARRAY(blobs, n_blobs, iovec_array_free);
|
||||
CLEANUP_ARRAY(policy_hash, n_policy_hash, iovec_array_free);
|
||||
|
||||
r = tpm2_parse_luks2_json(
|
||||
v,
|
||||
/* ret_keyslot= */ NULL,
|
||||
@ -84,8 +89,10 @@ _public_ int cryptsetup_token_open_pin(
|
||||
&pubkey,
|
||||
&pubkey_pcr_mask,
|
||||
&primary_alg,
|
||||
&blob,
|
||||
&blobs,
|
||||
&n_blobs,
|
||||
&policy_hash,
|
||||
&n_policy_hash,
|
||||
&salt,
|
||||
&srk,
|
||||
&pcrlock_nv,
|
||||
@ -106,8 +113,10 @@ _public_ int cryptsetup_token_open_pin(
|
||||
pin_string,
|
||||
params.pcrlock_path,
|
||||
primary_alg,
|
||||
&blob,
|
||||
&policy_hash,
|
||||
blobs,
|
||||
n_blobs,
|
||||
policy_hash,
|
||||
n_policy_hash,
|
||||
&salt,
|
||||
&srk,
|
||||
&pcrlock_nv,
|
||||
@ -167,8 +176,8 @@ _public_ void cryptsetup_token_dump(
|
||||
struct crypt_device *cd /* is always LUKS2 context */,
|
||||
const char *json /* validated 'systemd-tpm2' token if cryptsetup_token_validate is defined */) {
|
||||
|
||||
_cleanup_free_ char *hash_pcrs_str = NULL, *pubkey_pcrs_str = NULL, *blob_str = NULL, *policy_hash_str = NULL, *pubkey_str = NULL;
|
||||
_cleanup_(iovec_done) struct iovec blob = {}, pubkey = {}, policy_hash = {}, salt = {}, srk = {}, pcrlock_nv = {};
|
||||
_cleanup_free_ char *hash_pcrs_str = NULL, *pubkey_pcrs_str = NULL, *pubkey_str = NULL;
|
||||
_cleanup_(iovec_done) struct iovec pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {};
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
|
||||
uint32_t hash_pcr_mask, pubkey_pcr_mask;
|
||||
uint16_t pcr_bank, primary_alg;
|
||||
@ -181,6 +190,11 @@ _public_ void cryptsetup_token_dump(
|
||||
if (r < 0)
|
||||
return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " JSON object: %m");
|
||||
|
||||
struct iovec *blobs = NULL, *policy_hash = NULL;
|
||||
size_t n_blobs = 0, n_policy_hash = 0;
|
||||
CLEANUP_ARRAY(blobs, n_blobs, iovec_array_free);
|
||||
CLEANUP_ARRAY(policy_hash, n_policy_hash, iovec_array_free);
|
||||
|
||||
r = tpm2_parse_luks2_json(
|
||||
v,
|
||||
NULL,
|
||||
@ -189,8 +203,10 @@ _public_ void cryptsetup_token_dump(
|
||||
&pubkey,
|
||||
&pubkey_pcr_mask,
|
||||
&primary_alg,
|
||||
&blob,
|
||||
&blobs,
|
||||
&n_blobs,
|
||||
&policy_hash,
|
||||
&n_policy_hash,
|
||||
&salt,
|
||||
&srk,
|
||||
&pcrlock_nv,
|
||||
@ -206,30 +222,40 @@ _public_ void cryptsetup_token_dump(
|
||||
if (!pubkey_pcrs_str)
|
||||
return (void) crypt_log_debug_errno(cd, ENOMEM, "Cannot format PCR hash mask: %m");
|
||||
|
||||
r = crypt_dump_buffer_to_hex_string(blob.iov_base, blob.iov_len, &blob_str);
|
||||
if (r < 0)
|
||||
return (void) crypt_log_debug_errno(cd, r, "Cannot dump " TOKEN_NAME " content: %m");
|
||||
|
||||
r = crypt_dump_buffer_to_hex_string(pubkey.iov_base, pubkey.iov_len, &pubkey_str);
|
||||
if (r < 0)
|
||||
return (void) crypt_log_debug_errno(cd, r, "Cannot dump " TOKEN_NAME " content: %m");
|
||||
|
||||
r = crypt_dump_buffer_to_hex_string(policy_hash.iov_base, policy_hash.iov_len, &policy_hash_str);
|
||||
if (r < 0)
|
||||
return (void) crypt_log_debug_errno(cd, r, "Cannot dump " TOKEN_NAME " content: %m");
|
||||
|
||||
crypt_log(cd, "\ttpm2-hash-pcrs: %s\n", strna(hash_pcrs_str));
|
||||
crypt_log(cd, "\ttpm2-pcr-bank: %s\n", strna(tpm2_hash_alg_to_string(pcr_bank)));
|
||||
crypt_log(cd, "\ttpm2-pubkey:" CRYPT_DUMP_LINE_SEP "%s\n", pubkey_str);
|
||||
crypt_log(cd, "\ttpm2-pubkey-pcrs: %s\n", strna(pubkey_pcrs_str));
|
||||
crypt_log(cd, "\ttpm2-primary-alg: %s\n", strna(tpm2_asym_alg_to_string(primary_alg)));
|
||||
crypt_log(cd, "\ttpm2-blob: %s\n", blob_str);
|
||||
crypt_log(cd, "\ttpm2-policy-hash:" CRYPT_DUMP_LINE_SEP "%s\n", policy_hash_str);
|
||||
crypt_log(cd, "\ttpm2-pin: %s\n", true_false(flags & TPM2_FLAGS_USE_PIN));
|
||||
crypt_log(cd, "\ttpm2-pcrlock: %s\n", true_false(flags & TPM2_FLAGS_USE_PCRLOCK));
|
||||
crypt_log(cd, "\ttpm2-salt: %s\n", true_false(iovec_is_set(&salt)));
|
||||
crypt_log(cd, "\ttpm2-srk: %s\n", true_false(iovec_is_set(&srk)));
|
||||
crypt_log(cd, "\ttpm2-pcrlock-nv: %s\n", true_false(iovec_is_set(&pcrlock_nv)));
|
||||
|
||||
FOREACH_ARRAY(p, policy_hash, n_policy_hash) {
|
||||
_cleanup_free_ char *policy_hash_str = NULL;
|
||||
|
||||
r = crypt_dump_buffer_to_hex_string(p->iov_base, p->iov_len, &policy_hash_str);
|
||||
if (r < 0)
|
||||
return (void) crypt_log_debug_errno(cd, r, "Cannot dump " TOKEN_NAME " content: %m");
|
||||
|
||||
crypt_log(cd, "\ttpm2-policy-hash:" CRYPT_DUMP_LINE_SEP "%s\n", policy_hash_str);
|
||||
}
|
||||
|
||||
FOREACH_ARRAY(b, blobs, n_blobs) {
|
||||
_cleanup_free_ char *blob_str = NULL;
|
||||
|
||||
r = crypt_dump_buffer_to_hex_string(b->iov_base, b->iov_len, &blob_str);
|
||||
if (r < 0)
|
||||
return (void) crypt_log_debug_errno(cd, r, "Cannot dump " TOKEN_NAME " content: %m");
|
||||
|
||||
crypt_log(cd, "\ttpm2-blob: %s\n", blob_str);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@ -313,9 +339,18 @@ _public_ int cryptsetup_token_validate(
|
||||
return 1;
|
||||
}
|
||||
|
||||
r = sd_json_variant_unbase64(w, NULL, NULL);
|
||||
if (r < 0)
|
||||
return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'tpm2-blob' field: %m");
|
||||
if (sd_json_variant_is_array(w)) {
|
||||
sd_json_variant *i;
|
||||
JSON_VARIANT_ARRAY_FOREACH(i, w) {
|
||||
r = sd_json_variant_unbase64(i, /* ret= */ NULL, /* ret_size= */ NULL);
|
||||
if (r < 0)
|
||||
return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'tpm2-blob' field: %m");
|
||||
}
|
||||
} else {
|
||||
r = sd_json_variant_unbase64(w, /* ret= */ NULL, /* ret_size= */ NULL);
|
||||
if (r < 0)
|
||||
return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'tpm2-blob' field: %m");
|
||||
}
|
||||
|
||||
w = sd_json_variant_by_key(v, "tpm2-policy-hash");
|
||||
if (!w) {
|
||||
@ -323,9 +358,18 @@ _public_ int cryptsetup_token_validate(
|
||||
return 1;
|
||||
}
|
||||
|
||||
r = sd_json_variant_unhex(w, NULL, NULL);
|
||||
if (r < 0)
|
||||
return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'tpm2-policy-hash' field: %m");
|
||||
if (sd_json_variant_is_array(w)) {
|
||||
sd_json_variant *i;
|
||||
JSON_VARIANT_ARRAY_FOREACH(i, w) {
|
||||
r = sd_json_variant_unhex(i, /* ret= */ NULL, /* ret_size= */ NULL);
|
||||
if (r < 0)
|
||||
return crypt_log_debug_errno(cd, r, "Invalid hex data in 'tpm2-policy-hash' field: %m");
|
||||
}
|
||||
} else {
|
||||
r = sd_json_variant_unhex(w, /* ret= */ NULL, /* ret_size= */ NULL);
|
||||
if (r < 0)
|
||||
return crypt_log_debug_errno(cd, r, "Invalid hex data in 'tpm2-policy-hash' field: %m");
|
||||
}
|
||||
|
||||
w = sd_json_variant_by_key(v, "tpm2-pin");
|
||||
if (w) {
|
||||
|
@ -24,8 +24,10 @@ int acquire_luks2_key(
|
||||
const char *pin,
|
||||
const char *pcrlock_path,
|
||||
uint16_t primary_alg,
|
||||
const struct iovec *blob,
|
||||
const struct iovec *policy_hash,
|
||||
const struct iovec blobs[],
|
||||
size_t n_blobs,
|
||||
const struct iovec policy_hash[],
|
||||
size_t n_policy_hash,
|
||||
const struct iovec *salt,
|
||||
const struct iovec *srk,
|
||||
const struct iovec *pcrlock_nv,
|
||||
@ -101,8 +103,10 @@ int acquire_luks2_key(
|
||||
pin,
|
||||
FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK) ? &pcrlock_policy : NULL,
|
||||
primary_alg,
|
||||
blob,
|
||||
blobs,
|
||||
n_blobs,
|
||||
policy_hash,
|
||||
n_policy_hash,
|
||||
srk,
|
||||
ret_decrypted_key);
|
||||
if (r < 0)
|
||||
|
@ -16,8 +16,10 @@ int acquire_luks2_key(
|
||||
const char *pin,
|
||||
const char *pcrlock_path,
|
||||
uint16_t primary_alg,
|
||||
const struct iovec *key_data,
|
||||
const struct iovec *policy_hash,
|
||||
const struct iovec blobs[],
|
||||
size_t n_blobs,
|
||||
const struct iovec policy_hash[],
|
||||
size_t n_policy_hash,
|
||||
const struct iovec *salt,
|
||||
const struct iovec *srk,
|
||||
const struct iovec *pcrlock_nv,
|
||||
|
@ -1862,8 +1862,9 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
|
||||
/* pcrlock_path= */ NULL,
|
||||
/* primary_alg= */ 0,
|
||||
key_file, arg_keyfile_size, arg_keyfile_offset,
|
||||
key_data,
|
||||
key_data, /* n_blobs= */ 1,
|
||||
/* policy_hash= */ NULL, /* we don't know the policy hash */
|
||||
/* n_policy_hash= */ 0,
|
||||
/* salt= */ NULL,
|
||||
/* srk= */ NULL,
|
||||
/* pcrlock_nv= */ NULL,
|
||||
@ -1911,11 +1912,15 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
|
||||
|
||||
for (;;) {
|
||||
_cleanup_(iovec_done) struct iovec pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {};
|
||||
_cleanup_(iovec_done) struct iovec blob = {}, policy_hash = {};
|
||||
struct iovec *blobs = NULL, *policy_hash = NULL;
|
||||
uint32_t hash_pcr_mask, pubkey_pcr_mask;
|
||||
size_t n_blobs = 0, n_policy_hash = 0;
|
||||
uint16_t pcr_bank, primary_alg;
|
||||
TPM2Flags tpm2_flags;
|
||||
|
||||
CLEANUP_ARRAY(blobs, n_blobs, iovec_array_free);
|
||||
CLEANUP_ARRAY(policy_hash, n_policy_hash, iovec_array_free);
|
||||
|
||||
r = find_tpm2_auto_data(
|
||||
cd,
|
||||
arg_tpm2_pcr_mask, /* if != UINT32_MAX we'll only look for tokens with this PCR mask */
|
||||
@ -1925,8 +1930,10 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
|
||||
&pubkey,
|
||||
&pubkey_pcr_mask,
|
||||
&primary_alg,
|
||||
&blob,
|
||||
&blobs,
|
||||
&n_blobs,
|
||||
&policy_hash,
|
||||
&n_policy_hash,
|
||||
&salt,
|
||||
&srk,
|
||||
&pcrlock_nv,
|
||||
@ -1960,8 +1967,10 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
|
||||
arg_tpm2_pcrlock,
|
||||
primary_alg,
|
||||
/* key_file= */ NULL, /* key_file_size= */ 0, /* key_file_offset= */ 0, /* no key file */
|
||||
&blob,
|
||||
&policy_hash,
|
||||
blobs,
|
||||
n_blobs,
|
||||
policy_hash,
|
||||
n_policy_hash,
|
||||
&salt,
|
||||
&srk,
|
||||
&pcrlock_nv,
|
||||
|
@ -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 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;
|
||||
@ -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");
|
||||
}
|
||||
|
||||
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)
|
||||
@ -4361,6 +4397,11 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta
|
||||
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,
|
||||
@ -4368,8 +4409,10 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta
|
||||
&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,
|
||||
|
@ -957,12 +957,18 @@ int encrypt_credential_and_warn(
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Could not calculate sealing policy digest: %m");
|
||||
|
||||
struct iovec *blobs = NULL;
|
||||
size_t n_blobs = 0;
|
||||
CLEANUP_ARRAY(blobs, n_blobs, iovec_array_free);
|
||||
|
||||
r = tpm2_seal(tpm2_context,
|
||||
/* seal_key_handle= */ 0,
|
||||
&tpm2_policy,
|
||||
/* n_policy_hash= */ 1,
|
||||
/* pin= */ NULL,
|
||||
&tpm2_key,
|
||||
&tpm2_blob,
|
||||
&blobs,
|
||||
&n_blobs,
|
||||
&tpm2_primary_alg,
|
||||
/* ret_srk= */ NULL);
|
||||
if (r < 0) {
|
||||
@ -977,6 +983,9 @@ int encrypt_credential_and_warn(
|
||||
if (!iovec_memdup(&IOVEC_MAKE(tpm2_policy.buffer, tpm2_policy.size), &tpm2_policy_hash))
|
||||
return log_oom();
|
||||
|
||||
assert(n_blobs == 1);
|
||||
tpm2_blob = TAKE_STRUCT(blobs[0]);
|
||||
|
||||
assert(tpm2_blob.iov_len <= CREDENTIAL_FIELD_SIZE_MAX);
|
||||
assert(tpm2_policy_hash.iov_len <= CREDENTIAL_FIELD_SIZE_MAX);
|
||||
}
|
||||
@ -1341,7 +1350,9 @@ int decrypt_credential_and_warn(
|
||||
/* pcrlock_policy= */ NULL,
|
||||
le16toh(t->primary_alg),
|
||||
&IOVEC_MAKE(t->policy_hash_and_blob, le32toh(t->blob_size)),
|
||||
/* n_blobs= */ 1,
|
||||
&IOVEC_MAKE(t->policy_hash_and_blob + le32toh(t->blob_size), le32toh(t->policy_hash_size)),
|
||||
/* n_policy_hash= */ 1,
|
||||
/* srk= */ NULL,
|
||||
&tpm2_key);
|
||||
if (r < 0)
|
||||
|
@ -70,8 +70,10 @@ int acquire_tpm2_key(
|
||||
const char *key_file,
|
||||
size_t key_file_size,
|
||||
uint64_t key_file_offset,
|
||||
const struct iovec *key_data,
|
||||
const struct iovec *policy_hash,
|
||||
const struct iovec blobs[],
|
||||
size_t n_blobs,
|
||||
const struct iovec policy_hash[],
|
||||
size_t n_policy_hash,
|
||||
const struct iovec *salt,
|
||||
const struct iovec *srk,
|
||||
const struct iovec *pcrlock_nv,
|
||||
@ -82,9 +84,8 @@ int acquire_tpm2_key(
|
||||
struct iovec *ret_decrypted_key) {
|
||||
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *signature_json = NULL;
|
||||
_cleanup_free_ void *loaded_blob = NULL;
|
||||
_cleanup_(iovec_done) struct iovec loaded_blob = {};
|
||||
_cleanup_free_ char *auto_device = NULL;
|
||||
struct iovec blob;
|
||||
int r;
|
||||
|
||||
assert(iovec_is_valid(salt));
|
||||
@ -99,9 +100,7 @@ int acquire_tpm2_key(
|
||||
device = auto_device;
|
||||
}
|
||||
|
||||
if (iovec_is_set(key_data))
|
||||
blob = *key_data;
|
||||
else {
|
||||
if (n_blobs == 0) {
|
||||
_cleanup_free_ char *bindname = NULL;
|
||||
|
||||
/* If we read the salt via AF_UNIX, make this client recognizable */
|
||||
@ -114,11 +113,12 @@ int acquire_tpm2_key(
|
||||
key_file_size == 0 ? SIZE_MAX : key_file_size,
|
||||
READ_FULL_FILE_CONNECT_SOCKET,
|
||||
bindname,
|
||||
(char**) &loaded_blob, &blob.iov_len);
|
||||
(char**) &loaded_blob.iov_base, &loaded_blob.iov_len);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
blob.iov_base = loaded_blob;
|
||||
blobs = &loaded_blob;
|
||||
n_blobs = 1;
|
||||
}
|
||||
|
||||
if (pubkey_pcr_mask != 0) {
|
||||
@ -158,8 +158,10 @@ int acquire_tpm2_key(
|
||||
/* pin= */ NULL,
|
||||
FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK) ? &pcrlock_policy : NULL,
|
||||
primary_alg,
|
||||
&blob,
|
||||
blobs,
|
||||
n_blobs,
|
||||
policy_hash,
|
||||
n_policy_hash,
|
||||
srk,
|
||||
ret_decrypted_key);
|
||||
if (r < 0)
|
||||
@ -204,8 +206,10 @@ int acquire_tpm2_key(
|
||||
b64_salted_pin,
|
||||
FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK) ? &pcrlock_policy : NULL,
|
||||
primary_alg,
|
||||
&blob,
|
||||
blobs,
|
||||
n_blobs,
|
||||
policy_hash,
|
||||
n_policy_hash,
|
||||
srk,
|
||||
ret_decrypted_key);
|
||||
if (r < 0) {
|
||||
@ -230,8 +234,10 @@ int find_tpm2_auto_data(
|
||||
struct iovec *ret_pubkey,
|
||||
uint32_t *ret_pubkey_pcr_mask,
|
||||
uint16_t *ret_primary_alg,
|
||||
struct iovec *ret_blob,
|
||||
struct iovec *ret_policy_hash,
|
||||
struct iovec **ret_blobs,
|
||||
size_t *ret_n_blobs,
|
||||
struct iovec **ret_policy_hash,
|
||||
size_t *ret_n_policy_hash,
|
||||
struct iovec *ret_salt,
|
||||
struct iovec *ret_srk,
|
||||
struct iovec *ret_pcrlock_nv,
|
||||
@ -242,15 +248,35 @@ int find_tpm2_auto_data(
|
||||
int r, token;
|
||||
|
||||
assert(cd);
|
||||
assert(ret_hash_pcr_mask);
|
||||
assert(ret_pcrlock_nv);
|
||||
assert(ret_pubkey);
|
||||
assert(ret_pubkey_pcr_mask);
|
||||
assert(ret_primary_alg);
|
||||
assert(ret_blobs);
|
||||
assert(ret_n_blobs);
|
||||
assert(ret_policy_hash);
|
||||
assert(ret_n_policy_hash);
|
||||
assert(ret_salt);
|
||||
assert(ret_srk);
|
||||
assert(ret_pcrlock_nv);
|
||||
assert(ret_flags);
|
||||
assert(ret_keyslot);
|
||||
assert(ret_token);
|
||||
|
||||
for (token = start_token; token < sym_crypt_token_max(CRYPT_LUKS2); token++) {
|
||||
_cleanup_(iovec_done) struct iovec blob = {}, policy_hash = {}, pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {};
|
||||
_cleanup_(iovec_done) struct iovec pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {};
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
|
||||
struct iovec *blobs = NULL, *policy_hash = NULL;
|
||||
size_t n_blobs = 0, n_policy_hash = 0;
|
||||
uint32_t hash_pcr_mask, pubkey_pcr_mask;
|
||||
uint16_t pcr_bank, primary_alg;
|
||||
TPM2Flags flags;
|
||||
int keyslot;
|
||||
|
||||
CLEANUP_ARRAY(blobs, n_blobs, iovec_array_free);
|
||||
CLEANUP_ARRAY(policy_hash, n_policy_hash, iovec_array_free);
|
||||
|
||||
r = cryptsetup_get_token_as_json(cd, token, "systemd-tpm2", &v);
|
||||
if (IN_SET(r, -ENOENT, -EINVAL, -EMEDIUMTYPE))
|
||||
continue;
|
||||
@ -265,8 +291,10 @@ int find_tpm2_auto_data(
|
||||
&pubkey,
|
||||
&pubkey_pcr_mask,
|
||||
&primary_alg,
|
||||
&blob,
|
||||
&blobs,
|
||||
&n_blobs,
|
||||
&policy_hash,
|
||||
&n_policy_hash,
|
||||
&salt,
|
||||
&srk,
|
||||
&pcrlock_nv,
|
||||
@ -287,8 +315,10 @@ int find_tpm2_auto_data(
|
||||
*ret_pubkey = TAKE_STRUCT(pubkey);
|
||||
*ret_pubkey_pcr_mask = pubkey_pcr_mask;
|
||||
*ret_primary_alg = primary_alg;
|
||||
*ret_blob = TAKE_STRUCT(blob);
|
||||
*ret_policy_hash = TAKE_STRUCT(policy_hash);
|
||||
*ret_blobs = TAKE_PTR(blobs);
|
||||
*ret_n_blobs = n_blobs;
|
||||
*ret_policy_hash = TAKE_PTR(policy_hash);
|
||||
*ret_n_policy_hash = n_policy_hash;
|
||||
*ret_salt = TAKE_STRUCT(salt);
|
||||
*ret_keyslot = keyslot;
|
||||
*ret_token = token;
|
||||
|
@ -24,8 +24,10 @@ int acquire_tpm2_key(
|
||||
const char *key_file,
|
||||
size_t key_file_size,
|
||||
uint64_t key_file_offset,
|
||||
const struct iovec *key_data,
|
||||
const struct iovec *policy_hash,
|
||||
const struct iovec blobs[],
|
||||
size_t n_blobs,
|
||||
const struct iovec policy_hash[],
|
||||
size_t n_policy_hash,
|
||||
const struct iovec *salt,
|
||||
const struct iovec *srk,
|
||||
const struct iovec *pcrlock_nv,
|
||||
@ -44,8 +46,10 @@ int find_tpm2_auto_data(
|
||||
struct iovec *ret_pubkey,
|
||||
uint32_t *ret_pubkey_pcr_mask,
|
||||
uint16_t *ret_primary_alg,
|
||||
struct iovec *ret_blob,
|
||||
struct iovec *ret_policy_hash,
|
||||
struct iovec **ret_blobs,
|
||||
size_t *ret_n_blobs,
|
||||
struct iovec **ret_policy_hash,
|
||||
size_t *ret_n_policy_hash,
|
||||
struct iovec *ret_salt,
|
||||
struct iovec *ret_srk,
|
||||
struct iovec *ret_pcrlock_nv,
|
||||
@ -68,8 +72,10 @@ static inline int acquire_tpm2_key(
|
||||
const char *key_file,
|
||||
size_t key_file_size,
|
||||
uint64_t key_file_offset,
|
||||
const struct iovec *key_data,
|
||||
const struct iovec *policy_hash,
|
||||
const struct iovec blobs[],
|
||||
size_t n_blobs,
|
||||
const struct iovec policy_hash[],
|
||||
size_t n_policy_hash,
|
||||
const struct iovec *salt,
|
||||
const struct iovec *srk,
|
||||
const struct iovec *pcrlock_nv,
|
||||
@ -92,8 +98,10 @@ static inline int find_tpm2_auto_data(
|
||||
struct iovec *ret_pubkey,
|
||||
uint32_t *ret_pubkey_pcr_mask,
|
||||
uint16_t *ret_primary_alg,
|
||||
struct iovec *ret_blob,
|
||||
struct iovec *ret_policy_hash,
|
||||
struct iovec **ret_blobs,
|
||||
size_t *ret_n_blobs,
|
||||
struct iovec **ret_policy_hash,
|
||||
size_t *ret_n_policy_hash,
|
||||
struct iovec *ret_salt,
|
||||
struct iovec *ret_srk,
|
||||
struct iovec *ret_pcrlock_nv,
|
||||
|
@ -4179,8 +4179,11 @@ int tpm2_calculate_sealing_policy(
|
||||
assert(pcr_values || n_pcr_values == 0);
|
||||
assert(digest);
|
||||
|
||||
/* The combination of signed PCR policies and pcrlock is not supported (because we cannot combine
|
||||
* PolicyAuthorize and PolicyAuthorizeNV in one policy). Callers need to use "sharding" of the
|
||||
* symmetric FDE unlock key to make policies like that work. */
|
||||
if (public && pcrlock_policy)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Policies with both signed PCR and pcrlock are currently not supported.");
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Policies that combined signed PCR and pcrlock are not supported.");
|
||||
|
||||
if (public) {
|
||||
r = tpm2_calculate_policy_authorize(public, NULL, digest);
|
||||
@ -5375,10 +5378,12 @@ int tpm2_calculate_seal(
|
||||
|
||||
int tpm2_seal(Tpm2Context *c,
|
||||
uint32_t seal_key_handle,
|
||||
const TPM2B_DIGEST *policy,
|
||||
const TPM2B_DIGEST policy[],
|
||||
size_t n_policy,
|
||||
const char *pin,
|
||||
struct iovec *ret_secret,
|
||||
struct iovec *ret_blob,
|
||||
struct iovec **ret_blobs,
|
||||
size_t *ret_n_blobs,
|
||||
uint16_t *ret_primary_alg,
|
||||
struct iovec *ret_srk) {
|
||||
|
||||
@ -5386,7 +5391,8 @@ int tpm2_seal(Tpm2Context *c,
|
||||
int r;
|
||||
|
||||
assert(ret_secret);
|
||||
assert(ret_blob);
|
||||
assert(ret_blobs);
|
||||
assert(ret_n_blobs);
|
||||
|
||||
/* So here's what we do here: we connect to the TPM2 chip. It persistently contains a "seed" key that
|
||||
* is randomized when the TPM2 is first initialized or reset and remains stable across boots. We
|
||||
@ -5424,7 +5430,6 @@ int tpm2_seal(Tpm2Context *c,
|
||||
.objectAttributes = hmac_attributes,
|
||||
.parameters.keyedHashDetail.scheme.scheme = TPM2_ALG_NULL,
|
||||
.unique.keyedHash.size = SHA256_DIGEST_SIZE,
|
||||
.authPolicy = policy ? *policy : TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE),
|
||||
};
|
||||
|
||||
TPMS_SENSITIVE_CREATE hmac_sensitive = {
|
||||
@ -5443,12 +5448,6 @@ int tpm2_seal(Tpm2Context *c,
|
||||
|
||||
(void) tpm2_credit_random(c);
|
||||
|
||||
log_debug("Generating secret key data.");
|
||||
|
||||
r = crypto_random_bytes(hmac_sensitive.data.buffer, hmac_sensitive.data.size);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to generate secret key: %m");
|
||||
|
||||
_cleanup_(tpm2_handle_freep) Tpm2Handle *primary_handle = NULL;
|
||||
if (ret_srk) {
|
||||
_cleanup_(Esys_Freep) TPM2B_PUBLIC *primary_public = NULL;
|
||||
@ -5526,24 +5525,51 @@ int tpm2_seal(Tpm2Context *c,
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_(Esys_Freep) TPM2B_PUBLIC *public = NULL;
|
||||
_cleanup_(Esys_Freep) TPM2B_PRIVATE *private = NULL;
|
||||
r = tpm2_create(c, primary_handle, encryption_session, &hmac_template, &hmac_sensitive, &public, &private);
|
||||
if (r < 0)
|
||||
return r;
|
||||
log_debug("Generating secret key data.");
|
||||
|
||||
/* At least one shard, and if we have multiple policies, then we need one shard for each */
|
||||
size_t n_shards = MAX(n_policy, 1U);
|
||||
|
||||
/* Create a large secret which covers all shards we need */
|
||||
_cleanup_(iovec_done_erase) struct iovec secret = {};
|
||||
secret.iov_base = memdup(hmac_sensitive.data.buffer, hmac_sensitive.data.size);
|
||||
if (!secret.iov_base)
|
||||
return log_oom_debug();
|
||||
secret.iov_len = hmac_sensitive.data.size;
|
||||
|
||||
log_debug("Marshalling private and public part of HMAC key.");
|
||||
|
||||
_cleanup_(iovec_done) struct iovec blob = {};
|
||||
r = tpm2_marshal_blob(public, private, /* seed= */ NULL, &blob.iov_base, &blob.iov_len);
|
||||
r = crypto_random_bytes_allocate_iovec(hmac_sensitive.data.size * n_shards, &secret);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Could not create sealed blob: %m");
|
||||
return log_debug_errno(r, "Failed to generate secret key: %m");
|
||||
|
||||
struct iovec *blobs = new0(struct iovec, n_shards);
|
||||
size_t n_blobs = 0;
|
||||
|
||||
CLEANUP_ARRAY(blobs, n_blobs, iovec_array_free);
|
||||
|
||||
for (size_t shard = 0; shard < n_shards; shard++) {
|
||||
|
||||
/* Patch this shard's policy into the template */
|
||||
if (shard < n_policy)
|
||||
hmac_template.authPolicy = policy[shard];
|
||||
else
|
||||
hmac_template.authPolicy = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE);
|
||||
|
||||
/* Copy in this shard's secret key */
|
||||
memcpy(hmac_sensitive.data.buffer,
|
||||
(const uint8_t*) secret.iov_base + (hmac_sensitive.data.size * shard),
|
||||
hmac_sensitive.data.size);
|
||||
|
||||
log_debug("Creating HMAC key on TPM for shard %zu.", shard);
|
||||
|
||||
_cleanup_(Esys_Freep) TPM2B_PUBLIC *public = NULL;
|
||||
_cleanup_(Esys_Freep) TPM2B_PRIVATE *private = NULL;
|
||||
r = tpm2_create(c, primary_handle, encryption_session, &hmac_template, &hmac_sensitive, &public, &private);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
log_debug("Marshalling private and public part of HMAC key for shard %zu.", shard);
|
||||
|
||||
r = tpm2_marshal_blob(public, private, /* seed= */ NULL, &blobs[n_blobs].iov_base, &blobs[n_blobs].iov_len);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Could not create sealed blob: %m");
|
||||
|
||||
n_blobs++;
|
||||
}
|
||||
|
||||
if (DEBUG_LOGGING)
|
||||
log_debug("Completed TPM2 key sealing in %s.", FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - start, 1));
|
||||
@ -5571,7 +5597,8 @@ int tpm2_seal(Tpm2Context *c,
|
||||
}
|
||||
|
||||
*ret_secret = TAKE_STRUCT(secret);
|
||||
*ret_blob = TAKE_STRUCT(blob);
|
||||
*ret_blobs = TAKE_PTR(blobs);
|
||||
*ret_n_blobs = n_blobs;
|
||||
|
||||
if (ret_primary_alg)
|
||||
*ret_primary_alg = primary_alg;
|
||||
@ -5590,16 +5617,17 @@ int tpm2_unseal(Tpm2Context *c,
|
||||
const char *pin,
|
||||
const Tpm2PCRLockPolicy *pcrlock_policy,
|
||||
uint16_t primary_alg,
|
||||
const struct iovec *blob,
|
||||
const struct iovec *known_policy_hash,
|
||||
const struct iovec blobs[],
|
||||
size_t n_blobs,
|
||||
const struct iovec known_policy_hash[],
|
||||
size_t n_known_policy_hash,
|
||||
const struct iovec *srk,
|
||||
struct iovec *ret_secret) {
|
||||
|
||||
TSS2_RC rc;
|
||||
int r;
|
||||
|
||||
assert(iovec_is_set(blob));
|
||||
assert(iovec_is_valid(known_policy_hash));
|
||||
assert(n_blobs > 0);
|
||||
assert(iovec_is_valid(pubkey));
|
||||
assert(ret_secret);
|
||||
|
||||
@ -5616,12 +5644,11 @@ int tpm2_unseal(Tpm2Context *c,
|
||||
|
||||
usec_t start = now(CLOCK_MONOTONIC);
|
||||
|
||||
TPM2B_PUBLIC public;
|
||||
TPM2B_PRIVATE private;
|
||||
TPM2B_ENCRYPTED_SECRET seed = {};
|
||||
r = tpm2_unmarshal_blob(blob->iov_base, blob->iov_len, &public, &private, &seed);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Could not extract parts from blob: %m");
|
||||
size_t n_shards = pcrlock_policy && iovec_is_set(pubkey) ? 2 : 1;
|
||||
if (n_blobs != n_shards)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Number of provided key blobs (%zu) does not match policy requirements (%zu).", n_blobs, n_shards);
|
||||
if (n_known_policy_hash > 0 && n_known_policy_hash != n_shards)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Number of provided known policy hashes (%zu) does not match policy requirements (%zu or 0).", n_known_policy_hash, n_shards);
|
||||
|
||||
/* Older code did not save the pcr_bank, and unsealing needed to detect the best pcr bank to use,
|
||||
* so we need to handle that legacy situation. */
|
||||
@ -5657,37 +5684,6 @@ int tpm2_unseal(Tpm2Context *c,
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"No SRK or primary alg provided.");
|
||||
|
||||
if (seed.size > 0) {
|
||||
/* This is a calculated (or duplicated) sealed object, and must be imported. */
|
||||
_cleanup_free_ TPM2B_PRIVATE *imported_private = NULL;
|
||||
r = tpm2_import(c,
|
||||
primary_handle,
|
||||
/* session= */ NULL,
|
||||
&public,
|
||||
&private,
|
||||
&seed,
|
||||
/* encryption_key= */ NULL,
|
||||
/* symmetric= */ NULL,
|
||||
&imported_private);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
private = *imported_private;
|
||||
}
|
||||
|
||||
log_debug("Loading HMAC key into TPM.");
|
||||
|
||||
/*
|
||||
* Nothing sensitive on the bus, no need for encryption. Even if an attacker
|
||||
* gives you back a different key, the session initiation will fail. In the
|
||||
* SRK model, the tpmKey is verified. In the non-srk model, with pin, the bindKey
|
||||
* provides protections.
|
||||
*/
|
||||
_cleanup_(tpm2_handle_freep) Tpm2Handle *hmac_key = NULL;
|
||||
r = tpm2_load(c, primary_handle, NULL, &public, &private, &hmac_key);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
TPM2B_PUBLIC pubkey_tpm2b;
|
||||
_cleanup_(iovec_done) struct iovec fp = {};
|
||||
if (iovec_is_set(pubkey)) {
|
||||
@ -5700,100 +5696,145 @@ int tpm2_unseal(Tpm2Context *c,
|
||||
return log_debug_errno(r, "Could not get key fingerprint: %m");
|
||||
}
|
||||
|
||||
/*
|
||||
* if a pin is set for the seal object, use it to bind the session
|
||||
* key to that object. This prevents active bus interposers from
|
||||
* faking a TPM and seeing the unsealed value. An active interposer
|
||||
* could fake a TPM, satisfying the encrypted session, and just
|
||||
* forward everything to the *real* TPM.
|
||||
*/
|
||||
r = tpm2_set_auth(c, hmac_key, pin);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_(Esys_Freep) TPM2B_SENSITIVE_DATA* unsealed = NULL;
|
||||
_cleanup_(iovec_done_erase) struct iovec secret = {};
|
||||
for (unsigned i = RETRY_UNSEAL_MAX;; i--) {
|
||||
_cleanup_(tpm2_handle_freep) Tpm2Handle *encryption_session = NULL;
|
||||
r = tpm2_make_encryption_session(c, primary_handle, hmac_key, &encryption_session);
|
||||
if (r < 0)
|
||||
return r;
|
||||
bool retry = false;
|
||||
iovec_done_erase(&secret); /* clear data from previous unseal attempt */
|
||||
|
||||
_cleanup_(tpm2_handle_freep) Tpm2Handle *policy_session = NULL;
|
||||
_cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL;
|
||||
r = tpm2_make_policy_session(
|
||||
c,
|
||||
primary_handle,
|
||||
encryption_session,
|
||||
&policy_session);
|
||||
if (r < 0)
|
||||
return r;
|
||||
for (size_t shard = 0; shard < n_blobs; shard++) {
|
||||
TPM2B_PUBLIC public;
|
||||
TPM2B_PRIVATE private;
|
||||
TPM2B_ENCRYPTED_SECRET seed = {};
|
||||
r = tpm2_unmarshal_blob(blobs[shard].iov_base, blobs[shard].iov_len, &public, &private, &seed);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Could not extract parts from blob: %m");
|
||||
|
||||
r = tpm2_build_sealing_policy(
|
||||
c,
|
||||
policy_session,
|
||||
hash_pcr_mask,
|
||||
pcr_bank,
|
||||
iovec_is_set(pubkey) ? &pubkey_tpm2b : NULL,
|
||||
fp.iov_base, fp.iov_len,
|
||||
pubkey_pcr_mask,
|
||||
signature,
|
||||
!!pin,
|
||||
pcrlock_policy,
|
||||
&policy_digest);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (seed.size > 0) {
|
||||
/* This is a calculated (or duplicated) sealed object, and must be imported. */
|
||||
_cleanup_free_ TPM2B_PRIVATE *imported_private = NULL;
|
||||
r = tpm2_import(c,
|
||||
primary_handle,
|
||||
/* session= */ NULL,
|
||||
&public,
|
||||
&private,
|
||||
&seed,
|
||||
/* encryption_key= */ NULL,
|
||||
/* symmetric= */ NULL,
|
||||
&imported_private);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* If we know the policy hash to expect, and it doesn't match, we can shortcut things here, and not
|
||||
* wait until the TPM2 tells us to go away. */
|
||||
if (iovec_is_set(known_policy_hash) && memcmp_nn(policy_digest->buffer,
|
||||
private = *imported_private;
|
||||
}
|
||||
|
||||
log_debug("Loading HMAC key into TPM for shard %zu.", shard);
|
||||
|
||||
/* Nothing sensitive on the bus, no need for encryption. Even if an attacker gives
|
||||
* you back a different key, the session initiation will fail. In the SRK model, the
|
||||
* tpmKey is verified. In the non-srk model, with pin, the bindKey provides
|
||||
* protections. */
|
||||
_cleanup_(tpm2_handle_freep) Tpm2Handle *hmac_key = NULL;
|
||||
r = tpm2_load(c, primary_handle, NULL, &public, &private, &hmac_key);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* If a PIN is set for the seal object, use it to bind the session key to that
|
||||
* object. This prevents active bus interposers from faking a TPM and seeing the
|
||||
* unsealed value. An active interposer could fake a TPM, satisfying the encrypted
|
||||
* session, and just forward everything to the *real* TPM. */
|
||||
r = tpm2_set_auth(c, hmac_key, pin);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_(tpm2_handle_freep) Tpm2Handle *encryption_session = NULL;
|
||||
r = tpm2_make_encryption_session(c, primary_handle, hmac_key, &encryption_session);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_(tpm2_handle_freep) Tpm2Handle *policy_session = NULL;
|
||||
_cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL;
|
||||
r = tpm2_make_policy_session(
|
||||
c,
|
||||
primary_handle,
|
||||
encryption_session,
|
||||
&policy_session);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* If both public PCR key and pcrlock policies are requested, then generate the
|
||||
* public PCR policy for the first shared, and the pcrlock policy for the 2nd */
|
||||
r = tpm2_build_sealing_policy(
|
||||
c,
|
||||
policy_session,
|
||||
hash_pcr_mask,
|
||||
pcr_bank,
|
||||
shard == 0 && iovec_is_set(pubkey) ? &pubkey_tpm2b : NULL,
|
||||
fp.iov_base, fp.iov_len,
|
||||
shard == 0 ? pubkey_pcr_mask : 0,
|
||||
signature,
|
||||
!!pin,
|
||||
(shard == 1 || !iovec_is_set(pubkey)) ? pcrlock_policy : NULL,
|
||||
&policy_digest);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* If we know the policy hash to expect, and it doesn't match, we can shortcut things here, and not
|
||||
* wait until the TPM2 tells us to go away. */
|
||||
if (n_known_policy_hash > 0 && memcmp_nn(policy_digest->buffer,
|
||||
policy_digest->size,
|
||||
known_policy_hash->iov_base,
|
||||
known_policy_hash->iov_len) != 0) {
|
||||
known_policy_hash[shard].iov_base,
|
||||
known_policy_hash[shard].iov_len) != 0) {
|
||||
#if HAVE_OPENSSL
|
||||
if (iovec_is_set(pubkey) &&
|
||||
pubkey_tpm2b.publicArea.type == TPM2_ALG_RSA &&
|
||||
pubkey_tpm2b.publicArea.parameters.rsaDetail.exponent == TPM2_RSA_DEFAULT_EXPONENT) {
|
||||
/* Due to bug #30546, if using RSA pubkey with the default exponent, we may
|
||||
* need to set the exponent to the TPM special-case value of 0 and retry. */
|
||||
log_debug("Policy hash mismatch, retrying with RSA pubkey exponent set to 0.");
|
||||
pubkey_tpm2b.publicArea.parameters.rsaDetail.exponent = 0;
|
||||
continue;
|
||||
} else
|
||||
if (shard == 0 &&
|
||||
iovec_is_set(pubkey) &&
|
||||
pubkey_tpm2b.publicArea.type == TPM2_ALG_RSA &&
|
||||
pubkey_tpm2b.publicArea.parameters.rsaDetail.exponent == TPM2_RSA_DEFAULT_EXPONENT) {
|
||||
/* Due to bug #30546, if using RSA pubkey with the default exponent, we may
|
||||
* need to set the exponent to the TPM special-case value of 0 and retry. */
|
||||
log_debug("Policy hash mismatch, retrying with RSA pubkey exponent set to 0.");
|
||||
pubkey_tpm2b.publicArea.parameters.rsaDetail.exponent = 0;
|
||||
retry = true;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EPERM),
|
||||
"Current policy digest does not match stored policy digest, cancelling "
|
||||
"TPM2 authentication attempt.");
|
||||
}
|
||||
|
||||
log_debug("Unsealing HMAC key for shard %zu.", shard);
|
||||
|
||||
_cleanup_(Esys_Freep) TPM2B_SENSITIVE_DATA* unsealed = NULL;
|
||||
rc = sym_Esys_Unseal(
|
||||
c->esys_context,
|
||||
hmac_key->esys_handle,
|
||||
policy_session->esys_handle,
|
||||
encryption_session->esys_handle, /* use HMAC session to enable parameter encryption */
|
||||
ESYS_TR_NONE,
|
||||
&unsealed);
|
||||
if (rc == TPM2_RC_PCR_CHANGED && i > 0) {
|
||||
log_debug("A PCR value changed during the TPM2 policy session, restarting HMAC key unsealing (%u tries left).", i);
|
||||
retry = true;
|
||||
break;
|
||||
}
|
||||
if (rc != TPM2_RC_SUCCESS)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
|
||||
"Failed to unseal HMAC key in TPM: %s", sym_Tss2_RC_Decode(rc));
|
||||
|
||||
if (!iovec_append(&secret, &IOVEC_MAKE(unsealed->buffer, unsealed->size)))
|
||||
return log_oom_debug();
|
||||
|
||||
explicit_bzero_safe(unsealed->buffer, unsealed->size);
|
||||
}
|
||||
|
||||
log_debug("Unsealing HMAC key.");
|
||||
|
||||
rc = sym_Esys_Unseal(
|
||||
c->esys_context,
|
||||
hmac_key->esys_handle,
|
||||
policy_session->esys_handle,
|
||||
encryption_session->esys_handle, /* use HMAC session to enable parameter encryption */
|
||||
ESYS_TR_NONE,
|
||||
&unsealed);
|
||||
if (rc == TSS2_RC_SUCCESS)
|
||||
if (!retry)
|
||||
break;
|
||||
if (rc != TPM2_RC_PCR_CHANGED || i == 0)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
|
||||
"Failed to unseal HMAC key in TPM: %s", sym_Tss2_RC_Decode(rc));
|
||||
log_debug("A PCR value changed during the TPM2 policy session, restarting HMAC key unsealing (%u tries left).", i);
|
||||
}
|
||||
|
||||
_cleanup_(iovec_done_erase) struct iovec secret = {};
|
||||
secret.iov_base = memdup(unsealed->buffer, unsealed->size);
|
||||
explicit_bzero_safe(unsealed->buffer, unsealed->size);
|
||||
if (!secret.iov_base)
|
||||
return log_oom_debug();
|
||||
secret.iov_len = unsealed->size;
|
||||
|
||||
if (DEBUG_LOGGING)
|
||||
log_debug("Completed TPM2 key unsealing in %s.", FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - start, 1));
|
||||
|
||||
*ret_secret = TAKE_STRUCT(secret);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -7344,6 +7385,46 @@ int tpm2_parse_pcr_json_array(sd_json_variant *v, uint32_t *ret) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tpm2_make_shard_array(
|
||||
const struct iovec data[],
|
||||
size_t n_data,
|
||||
int (*encode_iovec)(sd_json_variant **, const void*, size_t n), /* pass sd_json_variant_new_base64() or sd_json_variant_new_hex() */
|
||||
sd_json_variant **ret) {
|
||||
|
||||
int r;
|
||||
|
||||
/* Turns a series of struct iovec into either an array of base64/hex strings, or a single string
|
||||
* thereof. Used for generated "tpm2-blob" or "tpm2-policy-hash" fields. */
|
||||
|
||||
assert(data);
|
||||
assert(n_data > 0);
|
||||
assert(encode_iovec);
|
||||
assert(ret);
|
||||
|
||||
/* Only one item? Then create only one encoded string, for compatibility with older versions which
|
||||
* didn't support the "sharding" scheme */
|
||||
if (n_data == 1)
|
||||
return encode_iovec(ret, data[0].iov_base, data[0].iov_len);
|
||||
|
||||
/* Multiple items? Then generate an array of encoded strings */
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL;
|
||||
|
||||
FOREACH_ARRAY(d, data, n_data) {
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *item = NULL;
|
||||
|
||||
r = encode_iovec(&item, d->iov_base, d->iov_len);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_json_variant_append_array(&j, item);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
*ret = TAKE_PTR(j);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tpm2_make_luks2_json(
|
||||
int keyslot,
|
||||
uint32_t hash_pcr_mask,
|
||||
@ -7351,8 +7432,10 @@ int tpm2_make_luks2_json(
|
||||
const struct iovec *pubkey,
|
||||
uint32_t pubkey_pcr_mask,
|
||||
uint16_t primary_alg,
|
||||
const struct iovec *blob,
|
||||
const struct iovec *policy_hash,
|
||||
const struct iovec blobs[],
|
||||
size_t n_blobs,
|
||||
const struct iovec policy_hash[],
|
||||
size_t n_policy_hash,
|
||||
const struct iovec *salt,
|
||||
const struct iovec *srk,
|
||||
const struct iovec *pcrlock_nv,
|
||||
@ -7364,8 +7447,8 @@ int tpm2_make_luks2_json(
|
||||
int r;
|
||||
|
||||
assert(iovec_is_valid(pubkey));
|
||||
assert(iovec_is_valid(blob));
|
||||
assert(iovec_is_valid(policy_hash));
|
||||
assert(n_blobs >= 1);
|
||||
assert(n_policy_hash >= 1);
|
||||
|
||||
if (asprintf(&keyslot_as_string, "%i", keyslot) < 0)
|
||||
return -ENOMEM;
|
||||
@ -7380,6 +7463,16 @@ int tpm2_make_luks2_json(
|
||||
return r;
|
||||
}
|
||||
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *phj = NULL;
|
||||
r = tpm2_make_shard_array(policy_hash, n_policy_hash, sd_json_variant_new_hex, &phj);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *bj = NULL;
|
||||
r = tpm2_make_shard_array(blobs, n_blobs, sd_json_variant_new_base64, &bj);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Note: We made the mistake of using "-" in the field names, which isn't particular compatible with
|
||||
* other programming languages. Let's not make things worse though, i.e. future additions to the JSON
|
||||
* object should use "_" rather than "-" in field names. */
|
||||
@ -7388,11 +7481,11 @@ int tpm2_make_luks2_json(
|
||||
&v,
|
||||
SD_JSON_BUILD_PAIR("type", JSON_BUILD_CONST_STRING("systemd-tpm2")),
|
||||
SD_JSON_BUILD_PAIR("keyslots", SD_JSON_BUILD_ARRAY(SD_JSON_BUILD_STRING(keyslot_as_string))),
|
||||
SD_JSON_BUILD_PAIR("tpm2-blob", JSON_BUILD_IOVEC_BASE64(blob)),
|
||||
SD_JSON_BUILD_PAIR("tpm2-blob", SD_JSON_BUILD_VARIANT(bj)),
|
||||
SD_JSON_BUILD_PAIR("tpm2-pcrs", SD_JSON_BUILD_VARIANT(hmj)),
|
||||
SD_JSON_BUILD_PAIR_CONDITION(pcr_bank != 0 && tpm2_hash_alg_to_string(pcr_bank), "tpm2-pcr-bank", SD_JSON_BUILD_STRING(tpm2_hash_alg_to_string(pcr_bank))),
|
||||
SD_JSON_BUILD_PAIR_CONDITION(primary_alg != 0 && tpm2_asym_alg_to_string(primary_alg), "tpm2-primary-alg", SD_JSON_BUILD_STRING(tpm2_asym_alg_to_string(primary_alg))),
|
||||
SD_JSON_BUILD_PAIR("tpm2-policy-hash", JSON_BUILD_IOVEC_HEX(policy_hash)),
|
||||
SD_JSON_BUILD_PAIR("tpm2-policy-hash", SD_JSON_BUILD_VARIANT(phj)),
|
||||
SD_JSON_BUILD_PAIR_CONDITION(FLAGS_SET(flags, TPM2_FLAGS_USE_PIN), "tpm2-pin", SD_JSON_BUILD_BOOLEAN(true)),
|
||||
SD_JSON_BUILD_PAIR_CONDITION(FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK), "tpm2_pcrlock", SD_JSON_BUILD_BOOLEAN(true)),
|
||||
SD_JSON_BUILD_PAIR_CONDITION(pubkey_pcr_mask != 0, "tpm2_pubkey_pcrs", SD_JSON_BUILD_VARIANT(pkmj)),
|
||||
@ -7409,6 +7502,63 @@ int tpm2_make_luks2_json(
|
||||
return keyslot;
|
||||
}
|
||||
|
||||
static int tpm2_parse_shard_array(
|
||||
sd_json_variant *v,
|
||||
const char *name,
|
||||
int (*decode_iovec)(sd_json_variant*, struct iovec *ret), /* pass json_variant_unbase64_iovec() or json_variant_unhex_iovec() */
|
||||
struct iovec **ret_data,
|
||||
size_t *ret_n_data) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(v);
|
||||
assert(name);
|
||||
assert(decode_iovec);
|
||||
assert(ret_data);
|
||||
assert(ret_n_data);
|
||||
|
||||
/* Parses the "tpm2-blob" or "tpm2-policy-hash" fields of our LUKS JSON serialization. This can
|
||||
* either be an array of base64/hex strings, or a single such string. The former to allow for sharded
|
||||
* keys. The latter mostly for compatibility with older versions where we didn't support sharded
|
||||
* keys. */
|
||||
|
||||
struct iovec *data = NULL;
|
||||
size_t n_data = 0;
|
||||
CLEANUP_ARRAY(data, n_data, iovec_array_free);
|
||||
|
||||
if (sd_json_variant_is_array(v)) {
|
||||
if (sd_json_variant_elements(v) == 0)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 token data contains empty '%s' array.", name);
|
||||
|
||||
data = new0(struct iovec, sd_json_variant_elements(v));
|
||||
if (!data)
|
||||
return log_oom_debug();
|
||||
|
||||
sd_json_variant *i;
|
||||
JSON_VARIANT_ARRAY_FOREACH(i, v) {
|
||||
r = decode_iovec(i, data + n_data);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Invalid data in '%s' field.", name);
|
||||
|
||||
n_data++;
|
||||
}
|
||||
} else {
|
||||
data = new0(struct iovec, 1);
|
||||
if (!data)
|
||||
return log_oom_debug();
|
||||
|
||||
r = decode_iovec(v, data + 0);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Invalid data in '%s' field.", name);
|
||||
|
||||
n_data = 1;
|
||||
}
|
||||
|
||||
*ret_data = TAKE_PTR(data);
|
||||
*ret_n_data = n_data;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tpm2_parse_luks2_json(
|
||||
sd_json_variant *v,
|
||||
int *ret_keyslot,
|
||||
@ -7417,14 +7567,16 @@ int tpm2_parse_luks2_json(
|
||||
struct iovec *ret_pubkey,
|
||||
uint32_t *ret_pubkey_pcr_mask,
|
||||
uint16_t *ret_primary_alg,
|
||||
struct iovec *ret_blob,
|
||||
struct iovec *ret_policy_hash,
|
||||
struct iovec **ret_blobs,
|
||||
size_t *ret_n_blobs,
|
||||
struct iovec **ret_policy_hash,
|
||||
size_t *ret_n_policy_hash,
|
||||
struct iovec *ret_salt,
|
||||
struct iovec *ret_srk,
|
||||
struct iovec *ret_pcrlock_nv,
|
||||
TPM2Flags *ret_flags) {
|
||||
|
||||
_cleanup_(iovec_done) struct iovec blob = {}, policy_hash = {}, pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {};
|
||||
_cleanup_(iovec_done) struct iovec pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {};
|
||||
uint32_t hash_pcr_mask = 0, pubkey_pcr_mask = 0;
|
||||
uint16_t primary_alg = TPM2_ALG_ECC; /* ECC was the only supported algorithm in systemd < 250, use that as implied default, for compatibility */
|
||||
uint16_t pcr_bank = UINT16_MAX; /* default: pick automatically */
|
||||
@ -7489,17 +7641,25 @@ int tpm2_parse_luks2_json(
|
||||
if (!w)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 token data lacks 'tpm2-blob' field.");
|
||||
|
||||
r = json_variant_unbase64_iovec(w, &blob);
|
||||
struct iovec *blobs = NULL;
|
||||
size_t n_blobs = 0;
|
||||
CLEANUP_ARRAY(blobs, n_blobs, iovec_array_free);
|
||||
|
||||
r = tpm2_parse_shard_array(w, "tpm2-blob", json_variant_unbase64_iovec, &blobs, &n_blobs);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Invalid base64 data in 'tpm2-blob' field.");
|
||||
return r;
|
||||
|
||||
w = sd_json_variant_by_key(v, "tpm2-policy-hash");
|
||||
if (!w)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 token data lacks 'tpm2-policy-hash' field.");
|
||||
|
||||
r = json_variant_unhex_iovec(w, &policy_hash);
|
||||
struct iovec *policy_hash = NULL;
|
||||
size_t n_policy_hash = 0;
|
||||
CLEANUP_ARRAY(policy_hash, n_policy_hash, iovec_array_free);
|
||||
|
||||
r = tpm2_parse_shard_array(w, "tpm2-policy-hash", json_variant_unhex_iovec, &policy_hash, &n_policy_hash);
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Invalid base64 data in 'tpm2-policy-hash' field.");
|
||||
return r;
|
||||
|
||||
w = sd_json_variant_by_key(v, "tpm2-pin");
|
||||
if (w) {
|
||||
@ -7565,10 +7725,14 @@ int tpm2_parse_luks2_json(
|
||||
*ret_pubkey_pcr_mask = pubkey_pcr_mask;
|
||||
if (ret_primary_alg)
|
||||
*ret_primary_alg = primary_alg;
|
||||
if (ret_blob)
|
||||
*ret_blob = TAKE_STRUCT(blob);
|
||||
if (ret_blobs)
|
||||
*ret_blobs = TAKE_PTR(blobs);
|
||||
if (ret_n_blobs)
|
||||
*ret_n_blobs = n_blobs;
|
||||
if (ret_policy_hash)
|
||||
*ret_policy_hash = TAKE_STRUCT(policy_hash);
|
||||
*ret_policy_hash = TAKE_PTR(policy_hash);
|
||||
if (ret_n_policy_hash)
|
||||
*ret_n_policy_hash = n_policy_hash;
|
||||
if (ret_salt)
|
||||
*ret_salt = TAKE_STRUCT(salt);
|
||||
if (ret_srk)
|
||||
|
@ -291,8 +291,8 @@ int tpm2_get_best_srk_template(Tpm2Context *c, TPMT_PUBLIC *ret_template);
|
||||
int tpm2_get_srk(Tpm2Context *c, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle);
|
||||
int tpm2_get_or_create_srk(Tpm2Context *c, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle);
|
||||
|
||||
int tpm2_seal(Tpm2Context *c, uint32_t seal_key_handle, const TPM2B_DIGEST *policy, const char *pin, struct iovec *ret_secret, struct iovec *ret_blob, uint16_t *ret_primary_alg, struct iovec *ret_srk);
|
||||
int tpm2_unseal(Tpm2Context *c, uint32_t hash_pcr_mask, uint16_t pcr_bank, const struct iovec *pubkey, uint32_t pubkey_pcr_mask, sd_json_variant *signature, const char *pin, const Tpm2PCRLockPolicy *pcrlock_policy, uint16_t primary_alg, const struct iovec *blob, const struct iovec *policy_hash, const struct iovec *srk, struct iovec *ret_secret);
|
||||
int tpm2_seal(Tpm2Context *c, uint32_t seal_key_handle, const TPM2B_DIGEST policy_hash[], size_t n_policy, const char *pin, struct iovec *ret_secret, struct iovec **ret_blobs, size_t *ret_n_blobs, uint16_t *ret_primary_alg, struct iovec *ret_srk);
|
||||
int tpm2_unseal(Tpm2Context *c, uint32_t hash_pcr_mask, uint16_t pcr_bank, const struct iovec *pubkey, uint32_t pubkey_pcr_mask, sd_json_variant *signature, const char *pin, const Tpm2PCRLockPolicy *pcrlock_policy, uint16_t primary_alg, const struct iovec blobs[], size_t n_blobs, const struct iovec policy_hash[], size_t n_policy_hash, const struct iovec *srk, struct iovec *ret_secret);
|
||||
|
||||
#if HAVE_OPENSSL
|
||||
int tpm2_tpm2b_public_to_openssl_pkey(const TPM2B_PUBLIC *public, EVP_PKEY **ret);
|
||||
@ -391,8 +391,8 @@ int tpm2_find_device_auto(char **ret);
|
||||
int tpm2_make_pcr_json_array(uint32_t pcr_mask, sd_json_variant **ret);
|
||||
int tpm2_parse_pcr_json_array(sd_json_variant *v, uint32_t *ret);
|
||||
|
||||
int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, const struct iovec *pubkey, uint32_t pubkey_pcr_mask, uint16_t primary_alg, const struct iovec *blob, const struct iovec *policy_hash, const struct iovec *salt, const struct iovec *srk, const struct iovec *pcrlock_nv, TPM2Flags flags, sd_json_variant **ret);
|
||||
int tpm2_parse_luks2_json(sd_json_variant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, struct iovec *ret_pubkey, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, struct iovec *ret_blob, struct iovec *ret_policy_hash, struct iovec *ret_salt, struct iovec *ret_srk, struct iovec *pcrlock_nv, TPM2Flags *ret_flags);
|
||||
int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, const struct iovec *pubkey, uint32_t pubkey_pcr_mask, uint16_t primary_alg, const struct iovec blobs[], size_t n_blobs, const struct iovec policy_hash[], size_t n_policy_hash, const struct iovec *salt, const struct iovec *srk, const struct iovec *pcrlock_nv, TPM2Flags flags, sd_json_variant **ret);
|
||||
int tpm2_parse_luks2_json(sd_json_variant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, struct iovec *ret_pubkey, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, struct iovec **ret_blobs, size_t *ret_n_blobs, struct iovec **ret_policy_hash, size_t *ret_n_policy_hash, struct iovec *ret_salt, struct iovec *ret_srk, struct iovec *pcrlock_nv, TPM2Flags *ret_flags);
|
||||
|
||||
/* Default to PCR 7 only */
|
||||
#define TPM2_PCR_INDEX_DEFAULT UINT32_C(7)
|
||||
|
@ -1163,7 +1163,9 @@ static void calculate_seal_and_unseal(
|
||||
/* pcrlock_policy= */ NULL,
|
||||
/* primary_alg= */ 0,
|
||||
&blob,
|
||||
/* n_blobs= */ 1,
|
||||
/* known_policy_hash= */ NULL,
|
||||
/* n_known_policy_hash= */ 0,
|
||||
&serialized_parent,
|
||||
&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);
|
||||
|
||||
_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);
|
||||
|
||||
@ -1243,8 +1251,10 @@ static void check_seal_unseal_for_handle(Tpm2Context *c, TPM2_HANDLE handle) {
|
||||
/* pin= */ NULL,
|
||||
/* pcrlock_policy= */ NULL,
|
||||
/* primary_alg= */ 0,
|
||||
&blob,
|
||||
blobs,
|
||||
n_blobs,
|
||||
/* policy_hash= */ NULL,
|
||||
/* n_policy_hash= */ 0,
|
||||
&srk,
|
||||
&unsealed_secret) >= 0);
|
||||
|
||||
|
@ -10,9 +10,10 @@ export SYSTEMD_LOG_LEVEL=debug
|
||||
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
|
||||
|
||||
@ -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 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.
|
||||
|
Loading…
Reference in New Issue
Block a user