mirror of
https://github.com/systemd/systemd.git
synced 2025-01-10 05:18:17 +03:00
Merge pull request #34177 from poettering/pcrlock-and-signed
cryptenroll/cryptsetup: support combined signed PCR + pcrlock policies
This commit is contained in:
commit
456cd065bf
@ -82,3 +82,15 @@ void iovec_array_free(struct iovec *iovec, size_t n_iovec) {
|
||||
|
||||
free(iovec);
|
||||
}
|
||||
|
||||
struct iovec* iovec_append(struct iovec *iovec, const struct iovec *append) {
|
||||
assert(iovec_is_valid(iovec));
|
||||
|
||||
if (!iovec_is_set(append))
|
||||
return iovec;
|
||||
|
||||
if (!greedy_realloc_append(&iovec->iov_base, &iovec->iov_len, append->iov_base, append->iov_len, 1))
|
||||
return NULL;
|
||||
|
||||
return iovec;
|
||||
}
|
||||
|
@ -70,3 +70,5 @@ static inline struct iovec *iovec_memdup(const struct iovec *source, struct iove
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct iovec* iovec_append(struct iovec *iovec, const struct iovec *append);
|
||||
|
@ -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,
|
||||
|
@ -210,10 +210,22 @@ int dlopen_tpm2(void) {
|
||||
}
|
||||
|
||||
void Esys_Freep(void *p) {
|
||||
assert(p);
|
||||
|
||||
if (*(void**) p)
|
||||
sym_Esys_Free(*(void**) p);
|
||||
}
|
||||
|
||||
static void tpm2b_sensitive_data_erase_and_esys_freep(TPM2B_SENSITIVE_DATA **p) {
|
||||
assert(p);
|
||||
|
||||
if (!*p)
|
||||
return;
|
||||
|
||||
explicit_bzero_safe((*p)->buffer, (*p)->size);
|
||||
sym_Esys_Free(*p);
|
||||
}
|
||||
|
||||
/* Get a specific TPM capability (or capabilities).
|
||||
*
|
||||
* Returns 0 if there are no more capability properties of the requested type, or 1 if there are more, or < 0
|
||||
@ -4179,8 +4191,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 +5390,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 +5403,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 +5442,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 +5460,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 +5537,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 +5609,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 +5629,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 +5656,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 +5696,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 +5708,143 @@ 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_(tpm2b_sensitive_data_erase_and_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();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -6064,7 +6115,7 @@ int tpm2_unseal_data(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_(Esys_Freep) TPM2B_SENSITIVE_DATA* unsealed = NULL;
|
||||
_cleanup_(tpm2b_sensitive_data_erase_and_esys_freep) TPM2B_SENSITIVE_DATA* unsealed = NULL;
|
||||
rc = sym_Esys_Unseal(
|
||||
c->esys_context,
|
||||
what->esys_handle,
|
||||
@ -6079,11 +6130,10 @@ int tpm2_unseal_data(
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
|
||||
"Failed to unseal data: %s", sym_Tss2_RC_Decode(rc));
|
||||
|
||||
_cleanup_(iovec_done) struct iovec d = {};
|
||||
d = IOVEC_MAKE(memdup(unsealed->buffer, unsealed->size), unsealed->size);
|
||||
|
||||
explicit_bzero_safe(unsealed->buffer, unsealed->size);
|
||||
|
||||
_cleanup_(iovec_done) struct iovec d = {
|
||||
.iov_base = memdup(unsealed->buffer, unsealed->size),
|
||||
.iov_len = unsealed->size,
|
||||
};
|
||||
if (!d.iov_base)
|
||||
return log_oom_debug();
|
||||
|
||||
@ -7344,6 +7394,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 +7441,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 +7456,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 +7472,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 +7490,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 +7511,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 +7576,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 +7650,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 +7734,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)
|
||||
|
@ -54,4 +54,17 @@ TEST(iovec_set_and_valid) {
|
||||
assert_se(!iovec_is_valid(&invalid));
|
||||
}
|
||||
|
||||
TEST(iovec_append) {
|
||||
_cleanup_(iovec_done) struct iovec iov = {};
|
||||
|
||||
assert_se(iovec_append(&iov, &IOVEC_MAKE_STRING("")) == &iov);
|
||||
assert_se(iovec_append(&iov, &IOVEC_MAKE_STRING("waldo")) == &iov);
|
||||
assert_se(iovec_append(&iov, &IOVEC_MAKE_STRING("quux")) == &iov);
|
||||
assert_se(iovec_append(&iov, &IOVEC_MAKE_STRING("")) == &iov);
|
||||
assert_se(iovec_append(&iov, &IOVEC_MAKE_STRING("p")) == &iov);
|
||||
assert_se(iovec_append(&iov, &IOVEC_MAKE_STRING("")) == &iov);
|
||||
|
||||
assert_se(iovec_memcmp(&iov, &IOVEC_MAKE_STRING("waldoquuxp")) == 0);
|
||||
}
|
||||
|
||||
DEFINE_TEST_MAIN(LOG_INFO);
|
||||
|
@ -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