You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1152 lines
60 KiB
1152 lines
60 KiB
From 607a98a1f7be2220654cabf9cf679ca0392853fb Mon Sep 17 00:00:00 2001
|
|
From: William Roberts <william.c.roberts@intel.com>
|
|
Date: Fri, 24 Feb 2023 14:11:16 -0600
|
|
Subject: [PATCH] tpm2: add support for a trusted SRK
|
|
|
|
Prevent attackers from spoofing the tpmKey portion of the AuthSession by
|
|
adding a trusted key to the LUKS header metadata. Also, use a persistent
|
|
object rather than a transient object.
|
|
|
|
This provides the following benifits:
|
|
1. No way to MITM the tpmKey portion of the session, see [1] for
|
|
details.
|
|
|
|
2. Strengthens the encrypted sessions, note that the bindKey could be
|
|
dropped now.
|
|
|
|
3. Speed, once it's created we just use it.
|
|
|
|
4. Owner Auth is needed to call create primary, so using the SRK
|
|
creates a scratch space for normal users.
|
|
|
|
This is a "first to set" model, in where the first person to set the key
|
|
in the LUKS header wins. Thus, setup should be done in a known good
|
|
state. If an SRK, which is a primary key at a special persistent
|
|
address, is found, it will use whatever is there. If not, it creates an
|
|
SRK. The SRK follows the convetions used through the tpm2-software
|
|
organization code on GitHub [2], however, a split has occured between
|
|
Windows and Linux with respect to SRK templates. The Linux SRK is
|
|
generated with the unique field size set to 0, in Windows, it properly
|
|
sets the size to key size in bytes and the unique data to all 0's of that
|
|
size. Note the proper templates for SRKs is covered in spec [3].
|
|
However, the most important thing, is that both SRKs are passwordless,
|
|
and thus they should be interchangable. If Windows is the first to make
|
|
the SRK, systemd will gladly accept it and vice-versa.
|
|
|
|
1. Without the bindKey being utilized, an attacker was able to intercept
|
|
this and fake a key, thus being able to decrypt and encrypt traffic as
|
|
needed. Introduction of the bindKey strengthened this, but allows for
|
|
the attacker to brute force AES128CFB using pin guesses. Introduction of
|
|
the salt increases the difficulty of this attack as well as DA attacks
|
|
on the TPM objects itself.
|
|
|
|
2. https://github.com/tpm2-software
|
|
|
|
3. https://trustedcomputinggroup.org/wp-content/uploads/TCG-TPM-v2.0-Provisioning-Guidance-Published-v1r1.pdf
|
|
|
|
Fixes: #20668
|
|
Fixes: #22637
|
|
|
|
Signed-off-by: William Roberts <william.c.roberts@intel.com>
|
|
(cherry picked from commit acbb504eaf1be51572b1c0d0d490ac478bc41c64)
|
|
|
|
Related: RHEL-16182
|
|
---
|
|
TODO | 2 +
|
|
src/cryptenroll/cryptenroll-tpm2.c | 9 +-
|
|
.../cryptsetup-token-systemd-tpm2.c | 15 +-
|
|
src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c | 3 +
|
|
src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h | 2 +
|
|
src/cryptsetup/cryptsetup-tpm2.c | 15 +-
|
|
src/cryptsetup/cryptsetup-tpm2.h | 4 +
|
|
src/cryptsetup/cryptsetup.c | 7 +-
|
|
src/partition/repart.c | 9 +-
|
|
src/shared/creds-util.c | 10 +-
|
|
src/shared/tpm2-util.c | 372 +++++++++++++++---
|
|
src/shared/tpm2-util.h | 17 +-
|
|
src/test/test-tpm2.c | 91 +++++
|
|
13 files changed, 488 insertions(+), 68 deletions(-)
|
|
|
|
diff --git a/TODO b/TODO
|
|
index aa3f1c596c..c512bedb92 100644
|
|
--- a/TODO
|
|
+++ b/TODO
|
|
@@ -455,6 +455,8 @@ Features:
|
|
random seed EFI variable is already set. That way, the variable set will be
|
|
set in all cases: if you just use sd-stub, or just sd-boot, or both.
|
|
|
|
+* Add and pickup tpm2 metadata for creds structure.
|
|
+
|
|
* sd-boot: we probably should include all BootXY EFI variable defined boot
|
|
entries in our menu, and then suppress ourselves. Benefit: instant
|
|
compatibility with all other OSes which register things there, in particular
|
|
diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c
|
|
index 3098b2e7ac..ab43135dc7 100644
|
|
--- a/src/cryptenroll/cryptenroll-tpm2.c
|
|
+++ b/src/cryptenroll/cryptenroll-tpm2.c
|
|
@@ -142,7 +142,8 @@ int enroll_tpm2(struct crypt_device *cd,
|
|
_cleanup_(erase_and_freep) void *secret = NULL;
|
|
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *signature_json = NULL;
|
|
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
|
|
- size_t secret_size, blob_size, hash_size, pubkey_size = 0;
|
|
+ _cleanup_(freep) void *srk_buf = NULL;
|
|
+ size_t secret_size, blob_size, hash_size, pubkey_size = 0, srk_buf_size = 0;
|
|
_cleanup_free_ void *blob = NULL, *hash = NULL, *pubkey = NULL;
|
|
uint16_t pcr_bank, primary_alg;
|
|
const char *node;
|
|
@@ -217,7 +218,9 @@ int enroll_tpm2(struct crypt_device *cd,
|
|
&blob, &blob_size,
|
|
&hash, &hash_size,
|
|
&pcr_bank,
|
|
- &primary_alg);
|
|
+ &primary_alg,
|
|
+ &srk_buf,
|
|
+ &srk_buf_size);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
@@ -248,6 +251,7 @@ int enroll_tpm2(struct crypt_device *cd,
|
|
primary_alg,
|
|
blob, blob_size,
|
|
hash, hash_size,
|
|
+ srk_buf, srk_buf_size,
|
|
&secret2, &secret2_size);
|
|
if (r < 0)
|
|
return r;
|
|
@@ -286,6 +290,7 @@ int enroll_tpm2(struct crypt_device *cd,
|
|
hash, hash_size,
|
|
use_pin ? binary_salt : NULL,
|
|
use_pin ? sizeof(binary_salt) : 0,
|
|
+ srk_buf, srk_buf_size,
|
|
flags,
|
|
&v);
|
|
if (r < 0)
|
|
diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c
|
|
index b5d66e389d..aab3a4b4c0 100644
|
|
--- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c
|
|
+++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c
|
|
@@ -42,8 +42,8 @@ _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_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL, *salt = NULL;
|
|
- size_t blob_size, policy_hash_size, decrypted_key_size, pubkey_size, salt_size = 0;
|
|
+ _cleanup_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL, *salt = NULL, *srk_buf = NULL;
|
|
+ size_t blob_size, policy_hash_size, decrypted_key_size, pubkey_size, salt_size = 0, srk_buf_size = 0;
|
|
_cleanup_(erase_and_freep) void *decrypted_key = NULL;
|
|
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
|
|
uint32_t hash_pcr_mask, pubkey_pcr_mask;
|
|
@@ -92,6 +92,8 @@ _public_ int cryptsetup_token_open_pin(
|
|
&policy_hash_size,
|
|
&salt,
|
|
&salt_size,
|
|
+ &srk_buf,
|
|
+ &srk_buf_size,
|
|
&flags);
|
|
if (r < 0)
|
|
return log_debug_open_error(cd, r);
|
|
@@ -114,6 +116,8 @@ _public_ int cryptsetup_token_open_pin(
|
|
policy_hash_size,
|
|
salt,
|
|
salt_size,
|
|
+ srk_buf,
|
|
+ srk_buf_size,
|
|
flags,
|
|
&decrypted_key,
|
|
&decrypted_key_size);
|
|
@@ -172,9 +176,9 @@ _public_ void cryptsetup_token_dump(
|
|
const char *json /* validated 'systemd-tpm2' token if cryptsetup_token_validate is defined */) {
|
|
|
|
_cleanup_free_ char *hash_pcrs_str = NULL, *pubkey_pcrs_str = NULL, *blob_str = NULL, *policy_hash_str = NULL, *pubkey_str = NULL;
|
|
- _cleanup_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL, *salt = NULL;
|
|
+ _cleanup_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL, *salt = NULL, *srk_buf = NULL;
|
|
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
|
|
- size_t blob_size, policy_hash_size, pubkey_size, salt_size = 0;
|
|
+ size_t blob_size, policy_hash_size, pubkey_size, salt_size = 0, srk_buf_size = 0;
|
|
uint32_t hash_pcr_mask, pubkey_pcr_mask;
|
|
uint16_t pcr_bank, primary_alg;
|
|
TPM2Flags flags = 0;
|
|
@@ -201,6 +205,8 @@ _public_ void cryptsetup_token_dump(
|
|
&policy_hash_size,
|
|
&salt,
|
|
&salt_size,
|
|
+ &srk_buf,
|
|
+ &srk_buf_size,
|
|
&flags);
|
|
if (r < 0)
|
|
return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " JSON fields: %m");
|
|
@@ -234,6 +240,7 @@ _public_ void cryptsetup_token_dump(
|
|
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-salt: %s\n", true_false(salt));
|
|
+ crypt_log(cd, "\ttpm2-srk: %s\n", true_false(srk_buf));
|
|
}
|
|
|
|
/*
|
|
diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c
|
|
index 80a2c0d316..af747af613 100644
|
|
--- a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c
|
|
+++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c
|
|
@@ -29,6 +29,8 @@ int acquire_luks2_key(
|
|
size_t policy_hash_size,
|
|
const void *salt,
|
|
size_t salt_size,
|
|
+ const void *srk_buf,
|
|
+ size_t srk_buf_size,
|
|
TPM2Flags flags,
|
|
void **ret_decrypted_key,
|
|
size_t *ret_decrypted_key_size) {
|
|
@@ -88,5 +90,6 @@ int acquire_luks2_key(
|
|
primary_alg,
|
|
key_data, key_data_size,
|
|
policy_hash, policy_hash_size,
|
|
+ srk_buf, srk_buf_size,
|
|
ret_decrypted_key, ret_decrypted_key_size);
|
|
}
|
|
diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h
|
|
index 36d514caa0..1143f5fd9f 100644
|
|
--- a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h
|
|
+++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h
|
|
@@ -22,6 +22,8 @@ int acquire_luks2_key(
|
|
size_t policy_hash_size,
|
|
const void *salt,
|
|
size_t salt_size,
|
|
+ const void *srk_buf,
|
|
+ size_t srk_buf_size,
|
|
TPM2Flags flags,
|
|
void **ret_decrypted_key,
|
|
size_t *ret_decrypted_key_size);
|
|
diff --git a/src/cryptsetup/cryptsetup-tpm2.c b/src/cryptsetup/cryptsetup-tpm2.c
|
|
index 2a8a38c593..6b650b0d26 100644
|
|
--- a/src/cryptsetup/cryptsetup-tpm2.c
|
|
+++ b/src/cryptsetup/cryptsetup-tpm2.c
|
|
@@ -72,6 +72,8 @@ int acquire_tpm2_key(
|
|
size_t policy_hash_size,
|
|
const void *salt,
|
|
size_t salt_size,
|
|
+ const void *srk_buf,
|
|
+ size_t srk_buf_size,
|
|
TPM2Flags flags,
|
|
usec_t until,
|
|
bool headless,
|
|
@@ -139,6 +141,8 @@ int acquire_tpm2_key(
|
|
blob_size,
|
|
policy_hash,
|
|
policy_hash_size,
|
|
+ srk_buf,
|
|
+ srk_buf_size,
|
|
ret_decrypted_key,
|
|
ret_decrypted_key_size);
|
|
|
|
@@ -179,6 +183,8 @@ int acquire_tpm2_key(
|
|
blob_size,
|
|
policy_hash,
|
|
policy_hash_size,
|
|
+ srk_buf,
|
|
+ srk_buf_size,
|
|
ret_decrypted_key,
|
|
ret_decrypted_key_size);
|
|
/* We get this error in case there is an authentication policy mismatch. This should
|
|
@@ -208,6 +214,8 @@ int find_tpm2_auto_data(
|
|
size_t *ret_policy_hash_size,
|
|
void **ret_salt,
|
|
size_t *ret_salt_size,
|
|
+ void **ret_srk_buf,
|
|
+ size_t *ret_srk_buf_size,
|
|
TPM2Flags *ret_flags,
|
|
int *ret_keyslot,
|
|
int *ret_token) {
|
|
@@ -217,9 +225,9 @@ int find_tpm2_auto_data(
|
|
assert(cd);
|
|
|
|
for (token = start_token; token < sym_crypt_token_max(CRYPT_LUKS2); token++) {
|
|
- _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL, *salt = NULL;
|
|
+ _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL, *salt = NULL, *srk_buf = NULL;
|
|
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
|
|
- size_t blob_size, policy_hash_size, pubkey_size, salt_size = 0;
|
|
+ size_t blob_size, policy_hash_size, pubkey_size, salt_size = 0, srk_buf_size = 0;
|
|
uint32_t hash_pcr_mask, pubkey_pcr_mask;
|
|
uint16_t pcr_bank, primary_alg;
|
|
TPM2Flags flags;
|
|
@@ -242,6 +250,7 @@ int find_tpm2_auto_data(
|
|
&blob, &blob_size,
|
|
&policy_hash, &policy_hash_size,
|
|
&salt, &salt_size,
|
|
+ &srk_buf, &srk_buf_size,
|
|
&flags);
|
|
if (r == -EUCLEAN) /* Gracefully handle issues in JSON fields not owned by us */
|
|
continue;
|
|
@@ -268,6 +277,8 @@ int find_tpm2_auto_data(
|
|
*ret_salt_size = salt_size;
|
|
*ret_keyslot = keyslot;
|
|
*ret_token = token;
|
|
+ *ret_srk_buf = TAKE_PTR(srk_buf);
|
|
+ *ret_srk_buf_size = srk_buf_size;
|
|
*ret_flags = flags;
|
|
return 0;
|
|
}
|
|
diff --git a/src/cryptsetup/cryptsetup-tpm2.h b/src/cryptsetup/cryptsetup-tpm2.h
|
|
index f6549b7d1d..c3d56ac979 100644
|
|
--- a/src/cryptsetup/cryptsetup-tpm2.h
|
|
+++ b/src/cryptsetup/cryptsetup-tpm2.h
|
|
@@ -30,6 +30,8 @@ int acquire_tpm2_key(
|
|
size_t policy_hash_size,
|
|
const void *salt,
|
|
size_t salt_size,
|
|
+ const void *srk_buf,
|
|
+ size_t salt_srk_buf_size,
|
|
TPM2Flags flags,
|
|
usec_t until,
|
|
bool headless,
|
|
@@ -53,6 +55,8 @@ int find_tpm2_auto_data(
|
|
size_t *ret_policy_hash_size,
|
|
void **ret_salt,
|
|
size_t *ret_salt_size,
|
|
+ void **ret_srk_buf,
|
|
+ size_t *ret_srk_size,
|
|
TPM2Flags *ret_flags,
|
|
int *ret_keyslot,
|
|
int *ret_token);
|
|
diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c
|
|
index d46a88c9fb..caef45637c 100644
|
|
--- a/src/cryptsetup/cryptsetup.c
|
|
+++ b/src/cryptsetup/cryptsetup.c
|
|
@@ -1675,6 +1675,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
|
|
key_data, key_data_size,
|
|
/* policy_hash= */ NULL, /* policy_hash_size= */ 0, /* we don't know the policy hash */
|
|
/* salt= */ NULL, /* salt_size= */ 0,
|
|
+ /* srk_buf= */ NULL, /* srk_buf_size= */ 0,
|
|
arg_tpm2_pin ? TPM2_FLAGS_USE_PIN : 0,
|
|
until,
|
|
arg_headless,
|
|
@@ -1720,8 +1721,8 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
|
|
* works. */
|
|
|
|
for (;;) {
|
|
- _cleanup_free_ void *pubkey = NULL, *salt = NULL;
|
|
- size_t pubkey_size = 0, salt_size = 0;
|
|
+ _cleanup_free_ void *pubkey = NULL, *salt = NULL, *srk_buf = NULL;
|
|
+ size_t pubkey_size = 0, salt_size = 0, srk_buf_size = 0;
|
|
uint32_t hash_pcr_mask, pubkey_pcr_mask;
|
|
uint16_t pcr_bank, primary_alg;
|
|
TPM2Flags tpm2_flags;
|
|
@@ -1738,6 +1739,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
|
|
&blob, &blob_size,
|
|
&policy_hash, &policy_hash_size,
|
|
&salt, &salt_size,
|
|
+ &srk_buf, &srk_buf_size,
|
|
&tpm2_flags,
|
|
&keyslot,
|
|
&token);
|
|
@@ -1768,6 +1770,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
|
|
blob, blob_size,
|
|
policy_hash, policy_hash_size,
|
|
salt, salt_size,
|
|
+ srk_buf, srk_buf_size,
|
|
tpm2_flags,
|
|
until,
|
|
arg_headless,
|
|
diff --git a/src/partition/repart.c b/src/partition/repart.c
|
|
index 0075932c09..57e1a8052a 100644
|
|
--- a/src/partition/repart.c
|
|
+++ b/src/partition/repart.c
|
|
@@ -3020,8 +3020,8 @@ static int partition_encrypt(
|
|
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
|
|
_cleanup_(erase_and_freep) void *secret = NULL;
|
|
_cleanup_free_ void *pubkey = NULL;
|
|
- _cleanup_free_ void *blob = NULL, *hash = NULL;
|
|
- size_t secret_size, blob_size, hash_size, pubkey_size = 0;
|
|
+ _cleanup_free_ void *blob = NULL, *hash = NULL, *srk_buf = NULL;
|
|
+ size_t secret_size, blob_size, hash_size, pubkey_size = 0, srk_buf_size = 0;
|
|
ssize_t base64_encoded_size;
|
|
uint16_t pcr_bank, primary_alg;
|
|
int keyslot;
|
|
@@ -3046,7 +3046,9 @@ static int partition_encrypt(
|
|
&blob, &blob_size,
|
|
&hash, &hash_size,
|
|
&pcr_bank,
|
|
- &primary_alg);
|
|
+ &primary_alg,
|
|
+ &srk_buf,
|
|
+ &srk_buf_size);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to seal to TPM2: %m");
|
|
|
|
@@ -3078,6 +3080,7 @@ static int partition_encrypt(
|
|
blob, blob_size,
|
|
hash, hash_size,
|
|
NULL, 0, /* no salt because tpm2_seal has no pin */
|
|
+ srk_buf, srk_buf_size,
|
|
0,
|
|
&v);
|
|
if (r < 0)
|
|
diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c
|
|
index e9cafb8097..f55c4ac96e 100644
|
|
--- a/src/shared/creds-util.c
|
|
+++ b/src/shared/creds-util.c
|
|
@@ -657,7 +657,9 @@ int encrypt_credential_and_warn(
|
|
&tpm2_blob, &tpm2_blob_size,
|
|
&tpm2_policy_hash, &tpm2_policy_hash_size,
|
|
&tpm2_pcr_bank,
|
|
- &tpm2_primary_alg);
|
|
+ &tpm2_primary_alg,
|
|
+ /* ret_srk_buf= */ NULL,
|
|
+ /* ret_srk_buf_size= */ 0);
|
|
if (r < 0) {
|
|
if (sd_id128_equal(with_key, _CRED_AUTO_INITRD))
|
|
log_warning("Firmware reported a TPM2 being present and used, but we didn't manage to talk to it. Credential will be refused if SecureBoot is enabled.");
|
|
@@ -987,6 +989,10 @@ int decrypt_credential_and_warn(
|
|
le32toh(z->size));
|
|
}
|
|
|
|
+ /*
|
|
+ * TODO: Add the SRK data to the credential structure so it can be plumbed
|
|
+ * through and used to verify the TPM session.
|
|
+ */
|
|
r = tpm2_unseal(tpm2_device,
|
|
le64toh(t->pcr_mask),
|
|
le16toh(t->pcr_bank),
|
|
@@ -1000,6 +1006,8 @@ int decrypt_credential_and_warn(
|
|
le32toh(t->blob_size),
|
|
t->policy_hash_and_blob + le32toh(t->blob_size),
|
|
le32toh(t->policy_hash_size),
|
|
+ /* srk_buf= */ NULL,
|
|
+ /* srk_buf_size= */ 0,
|
|
&tpm2_key,
|
|
&tpm2_key_size);
|
|
if (r < 0)
|
|
diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c
|
|
index 3960c0aed7..55153e79f4 100644
|
|
--- a/src/shared/tpm2-util.c
|
|
+++ b/src/shared/tpm2-util.c
|
|
@@ -13,6 +13,7 @@
|
|
#include "fs-util.h"
|
|
#include "hexdecoct.h"
|
|
#include "hmac.h"
|
|
+#include "lockfile-util.h"
|
|
#include "memory-util.h"
|
|
#include "openssl-util.h"
|
|
#include "parse-util.h"
|
|
@@ -30,6 +31,7 @@ static void *libtss2_mu_dl = NULL;
|
|
|
|
TSS2_RC (*sym_Esys_Create)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE_CREATE *inSensitive, const TPM2B_PUBLIC *inPublic, const TPM2B_DATA *outsideInfo, const TPML_PCR_SELECTION *creationPCR, TPM2B_PRIVATE **outPrivate, TPM2B_PUBLIC **outPublic, TPM2B_CREATION_DATA **creationData, TPM2B_DIGEST **creationHash, TPMT_TK_CREATION **creationTicket) = NULL;
|
|
TSS2_RC (*sym_Esys_CreatePrimary)(ESYS_CONTEXT *esysContext, ESYS_TR primaryHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE_CREATE *inSensitive, const TPM2B_PUBLIC *inPublic, const TPM2B_DATA *outsideInfo, const TPML_PCR_SELECTION *creationPCR, ESYS_TR *objectHandle, TPM2B_PUBLIC **outPublic, TPM2B_CREATION_DATA **creationData, TPM2B_DIGEST **creationHash, TPMT_TK_CREATION **creationTicket) = NULL;
|
|
+TSS2_RC (*sym_Esys_EvictControl)(ESYS_CONTEXT *esysContext, ESYS_TR auth, ESYS_TR objectHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPMI_DH_PERSISTENT persistentHandle, ESYS_TR *newObjectHandle);
|
|
void (*sym_Esys_Finalize)(ESYS_CONTEXT **context) = NULL;
|
|
TSS2_RC (*sym_Esys_FlushContext)(ESYS_CONTEXT *esysContext, ESYS_TR flushHandle) = NULL;
|
|
void (*sym_Esys_Free)(void *ptr) = NULL;
|
|
@@ -44,11 +46,15 @@ TSS2_RC (*sym_Esys_PolicyAuthorize)(ESYS_CONTEXT *esysContext, ESYS_TR policySes
|
|
TSS2_RC (*sym_Esys_PolicyAuthValue)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3) = NULL;
|
|
TSS2_RC (*sym_Esys_PolicyGetDigest)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_DIGEST **policyDigest) = NULL;
|
|
TSS2_RC (*sym_Esys_PolicyPCR)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *pcrDigest, const TPML_PCR_SELECTION *pcrs) = NULL;
|
|
+TSS2_RC (*sym_Esys_ReadPublic)(ESYS_CONTEXT *esysContext, ESYS_TR objectHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_PUBLIC **outPublic, TPM2B_NAME **name, TPM2B_NAME **qualifiedName);
|
|
TSS2_RC (*sym_Esys_StartAuthSession)(ESYS_CONTEXT *esysContext, ESYS_TR tpmKey, ESYS_TR bind, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_NONCE *nonceCaller, TPM2_SE sessionType, const TPMT_SYM_DEF *symmetric, TPMI_ALG_HASH authHash, ESYS_TR *sessionHandle) = NULL;
|
|
TSS2_RC (*sym_Esys_Startup)(ESYS_CONTEXT *esysContext, TPM2_SU startupType) = NULL;
|
|
TSS2_RC (*sym_Esys_TRSess_GetAttributes)(ESYS_CONTEXT *esysContext, ESYS_TR session, TPMA_SESSION *flags);
|
|
TSS2_RC (*sym_Esys_TRSess_SetAttributes)(ESYS_CONTEXT *esysContext, ESYS_TR session, TPMA_SESSION flags, TPMA_SESSION mask);
|
|
TSS2_RC (*sym_Esys_TR_GetName)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_NAME **name);
|
|
+TSS2_RC (*sym_Esys_TR_Deserialize)(ESYS_CONTEXT *esys_context, uint8_t const *buffer, size_t buffer_size, ESYS_TR *esys_handle);
|
|
+TSS2_RC (*sym_Esys_TR_FromTPMPublic)(ESYS_CONTEXT *esysContext, TPM2_HANDLE tpm_handle, ESYS_TR optionalSession1, ESYS_TR optionalSession2, ESYS_TR optionalSession3, ESYS_TR *object);
|
|
+TSS2_RC (*sym_Esys_TR_Serialize)(ESYS_CONTEXT *esys_context, ESYS_TR object, uint8_t **buffer, size_t *buffer_size);
|
|
TSS2_RC (*sym_Esys_TR_SetAuth)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_AUTH const *authValue) = NULL;
|
|
TSS2_RC (*sym_Esys_Unseal)(ESYS_CONTEXT *esysContext, ESYS_TR itemHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_SENSITIVE_DATA **outData) = NULL;
|
|
TSS2_RC (*sym_Esys_VerifySignature)(ESYS_CONTEXT *esysContext, ESYS_TR keyHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *digest, const TPMT_SIGNATURE *signature, TPMT_TK_VERIFIED **validation);
|
|
@@ -67,6 +73,7 @@ int dlopen_tpm2(void) {
|
|
&libtss2_esys_dl, "libtss2-esys.so.0", LOG_DEBUG,
|
|
DLSYM_ARG(Esys_Create),
|
|
DLSYM_ARG(Esys_CreatePrimary),
|
|
+ DLSYM_ARG(Esys_EvictControl),
|
|
DLSYM_ARG(Esys_Finalize),
|
|
DLSYM_ARG(Esys_FlushContext),
|
|
DLSYM_ARG(Esys_Free),
|
|
@@ -81,11 +88,15 @@ int dlopen_tpm2(void) {
|
|
DLSYM_ARG(Esys_PolicyAuthValue),
|
|
DLSYM_ARG(Esys_PolicyGetDigest),
|
|
DLSYM_ARG(Esys_PolicyPCR),
|
|
+ DLSYM_ARG(Esys_ReadPublic),
|
|
DLSYM_ARG(Esys_StartAuthSession),
|
|
DLSYM_ARG(Esys_Startup),
|
|
DLSYM_ARG(Esys_TRSess_GetAttributes),
|
|
DLSYM_ARG(Esys_TRSess_SetAttributes),
|
|
+ DLSYM_ARG(Esys_TR_FromTPMPublic),
|
|
DLSYM_ARG(Esys_TR_GetName),
|
|
+ DLSYM_ARG(Esys_TR_Deserialize),
|
|
+ DLSYM_ARG(Esys_TR_Serialize),
|
|
DLSYM_ARG(Esys_TR_SetAuth),
|
|
DLSYM_ARG(Esys_Unseal),
|
|
DLSYM_ARG(Esys_VerifySignature));
|
|
@@ -249,7 +260,7 @@ Tpm2Handle *tpm2_handle_free(Tpm2Handle *handle) {
|
|
return NULL;
|
|
|
|
_cleanup_tpm2_context_ Tpm2Context *context = (Tpm2Context*)handle->tpm2_context;
|
|
- if (context)
|
|
+ if (context && !handle->keep)
|
|
tpm2_handle_flush(context->esys_context, handle->esys_handle);
|
|
|
|
return mfree(handle);
|
|
@@ -332,55 +343,200 @@ static int tpm2_credit_random(Tpm2Context *c) {
|
|
return 0;
|
|
}
|
|
|
|
-static int tpm2_make_primary(
|
|
- Tpm2Context *c,
|
|
- Tpm2Handle **ret_primary,
|
|
- TPMI_ALG_PUBLIC alg,
|
|
- TPMI_ALG_PUBLIC *ret_alg) {
|
|
+const TPM2B_PUBLIC *tpm2_get_primary_template(Tpm2SRKTemplateFlags flags) {
|
|
|
|
- static const TPM2B_SENSITIVE_CREATE primary_sensitive = {};
|
|
- static const TPM2B_PUBLIC primary_template_ecc = {
|
|
- .size = sizeof(TPMT_PUBLIC),
|
|
- .publicArea = {
|
|
- .type = TPM2_ALG_ECC,
|
|
- .nameAlg = TPM2_ALG_SHA256,
|
|
- .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH,
|
|
- .parameters.eccDetail = {
|
|
- .symmetric = {
|
|
- .algorithm = TPM2_ALG_AES,
|
|
- .keyBits.aes = 128,
|
|
- .mode.aes = TPM2_ALG_CFB,
|
|
+ /*
|
|
+ * Set up array so flags can be used directly as an input.
|
|
+ *
|
|
+ * Templates for SRK come from the spec:
|
|
+ * - https://trustedcomputinggroup.org/wp-content/uploads/TCG-TPM-v2.0-Provisioning-Guidance-Published-v1r1.pdf
|
|
+ *
|
|
+ * However, note their is some lore here. On Linux, the SRK has it's unique field set to size 0 and
|
|
+ * on Windows the SRK has their unique data set to keyLen in bytes of zeros.
|
|
+ */
|
|
+ assert(flags >= 0);
|
|
+ assert(flags <= _TPM2_SRK_TEMPLATE_MAX);
|
|
+
|
|
+ static const TPM2B_PUBLIC templ[_TPM2_SRK_TEMPLATE_MAX + 1] = {
|
|
+ /* index 0 RSA old */
|
|
+ [0] = {
|
|
+ .publicArea = {
|
|
+ .type = TPM2_ALG_RSA,
|
|
+ .nameAlg = TPM2_ALG_SHA256,
|
|
+ .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH,
|
|
+ .parameters.rsaDetail = {
|
|
+ .symmetric = {
|
|
+ .algorithm = TPM2_ALG_AES,
|
|
+ .keyBits.aes = 128,
|
|
+ .mode.aes = TPM2_ALG_CFB,
|
|
+ },
|
|
+ .scheme.scheme = TPM2_ALG_NULL,
|
|
+ .keyBits = 2048,
|
|
},
|
|
- .scheme.scheme = TPM2_ALG_NULL,
|
|
- .curveID = TPM2_ECC_NIST_P256,
|
|
- .kdf.scheme = TPM2_ALG_NULL,
|
|
},
|
|
},
|
|
- };
|
|
- static const TPM2B_PUBLIC primary_template_rsa = {
|
|
- .size = sizeof(TPMT_PUBLIC),
|
|
- .publicArea = {
|
|
- .type = TPM2_ALG_RSA,
|
|
- .nameAlg = TPM2_ALG_SHA256,
|
|
- .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH,
|
|
- .parameters.rsaDetail = {
|
|
- .symmetric = {
|
|
- .algorithm = TPM2_ALG_AES,
|
|
- .keyBits.aes = 128,
|
|
- .mode.aes = TPM2_ALG_CFB,
|
|
+ [TPM2_SRK_TEMPLATE_ECC] = {
|
|
+ .publicArea = {
|
|
+ .type = TPM2_ALG_ECC,
|
|
+ .nameAlg = TPM2_ALG_SHA256,
|
|
+ .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH,
|
|
+ .parameters.eccDetail = {
|
|
+ .symmetric = {
|
|
+ .algorithm = TPM2_ALG_AES,
|
|
+ .keyBits.aes = 128,
|
|
+ .mode.aes = TPM2_ALG_CFB,
|
|
+ },
|
|
+ .scheme.scheme = TPM2_ALG_NULL,
|
|
+ .curveID = TPM2_ECC_NIST_P256,
|
|
+ .kdf.scheme = TPM2_ALG_NULL,
|
|
+ },
|
|
+ },
|
|
+ },
|
|
+ [TPM2_SRK_TEMPLATE_NEW_STYLE] = {
|
|
+ .publicArea = {
|
|
+ .type = TPM2_ALG_RSA,
|
|
+ .nameAlg = TPM2_ALG_SHA256,
|
|
+ .objectAttributes = TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_USERWITHAUTH|TPMA_OBJECT_NODA,
|
|
+ .parameters.rsaDetail = {
|
|
+ .symmetric = {
|
|
+ .algorithm = TPM2_ALG_AES,
|
|
+ .keyBits.aes = 128,
|
|
+ .mode.aes = TPM2_ALG_CFB,
|
|
+ },
|
|
+ .scheme.scheme = TPM2_ALG_NULL,
|
|
+ .keyBits = 2048,
|
|
+ },
|
|
+ },
|
|
+ },
|
|
+ [TPM2_SRK_TEMPLATE_NEW_STYLE|TPM2_SRK_TEMPLATE_ECC] = {
|
|
+ .publicArea = {
|
|
+ .type = TPM2_ALG_ECC,
|
|
+ .nameAlg = TPM2_ALG_SHA256,
|
|
+ .objectAttributes = TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_USERWITHAUTH|TPMA_OBJECT_NODA,
|
|
+ .parameters.eccDetail = {
|
|
+ .symmetric = {
|
|
+ .algorithm = TPM2_ALG_AES,
|
|
+ .keyBits.aes = 128,
|
|
+ .mode.aes = TPM2_ALG_CFB,
|
|
+ },
|
|
+ .scheme.scheme = TPM2_ALG_NULL,
|
|
+ .curveID = TPM2_ECC_NIST_P256,
|
|
+ .kdf.scheme = TPM2_ALG_NULL,
|
|
},
|
|
- .scheme.scheme = TPM2_ALG_NULL,
|
|
- .keyBits = 2048,
|
|
},
|
|
},
|
|
};
|
|
|
|
+ return &templ[flags];
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Why and what is an SRK?
|
|
+ * TL;DR provides a working space for those without owner auth. The user enrolling
|
|
+ * the disk may not have access to the TPMs owner hierarchy auth, so they need a
|
|
+ * working space. This working space is at the defined address of 0x81000001.
|
|
+ * Details can be found here:
|
|
+ * - https://trustedcomputinggroup.org/wp-content/uploads/TCG-TPM-v2.0-Provisioning-Guidance-Published-v1r1.pdf
|
|
+ */
|
|
+#define SRK_HANDLE UINT32_C(0x81000001)
|
|
+
|
|
+/*
|
|
+ * Retrieves the SRK handle if present. Returns 0 if SRK not present, 1 if present
|
|
+ * and < 0 on error
|
|
+ */
|
|
+static int tpm2_get_srk(
|
|
+ Tpm2Context *c,
|
|
+ TPMI_ALG_PUBLIC *ret_alg,
|
|
+ Tpm2Handle *ret_primary) {
|
|
+
|
|
+ TPMI_YES_NO more_data;
|
|
+ ESYS_TR primary_tr = ESYS_TR_NONE;
|
|
+ _cleanup_(Esys_Freep) TPMS_CAPABILITY_DATA *cap_data = NULL;
|
|
+
|
|
+ assert(c);
|
|
+ assert(ret_primary);
|
|
+
|
|
+ TSS2_RC rc = sym_Esys_GetCapability(c->esys_context,
|
|
+ ESYS_TR_NONE,
|
|
+ ESYS_TR_NONE,
|
|
+ ESYS_TR_NONE,
|
|
+ TPM2_CAP_HANDLES,
|
|
+ SRK_HANDLE,
|
|
+ 1,
|
|
+ &more_data,
|
|
+ &cap_data);
|
|
+ if (rc != TSS2_RC_SUCCESS)
|
|
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
|
|
+ "Failed to enumerate handles searching for SRK: %s",
|
|
+ sym_Tss2_RC_Decode(rc));
|
|
+
|
|
+ /* Did Not find SRK, indicate this by returning 0 */
|
|
+ if (cap_data->data.handles.count == 0 || cap_data->data.handles.handle[0] != SRK_HANDLE) {
|
|
+ ret_primary->esys_handle = ESYS_TR_NONE;
|
|
+
|
|
+ if (ret_alg)
|
|
+ *ret_alg = 0;
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ log_debug("Found SRK on TPM.");
|
|
+
|
|
+ /* convert the raw handle to an ESYS_TR */
|
|
+ TPM2_HANDLE handle = cap_data->data.handles.handle[0];
|
|
+ rc = sym_Esys_TR_FromTPMPublic(c->esys_context,
|
|
+ handle,
|
|
+ ESYS_TR_NONE,
|
|
+ ESYS_TR_NONE,
|
|
+ ESYS_TR_NONE,
|
|
+ &primary_tr);
|
|
+ if (rc != TSS2_RC_SUCCESS)
|
|
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
|
|
+ "Failed to convert ray handle to ESYS_TR for SRK: %s",
|
|
+ sym_Tss2_RC_Decode(rc));
|
|
+
|
|
+ /* Get the algorithm if the caller wants it */
|
|
+ _cleanup_(Esys_Freep) TPM2B_PUBLIC *out_public = NULL;
|
|
+ if (ret_alg) {
|
|
+ rc = sym_Esys_ReadPublic(
|
|
+ c->esys_context,
|
|
+ primary_tr,
|
|
+ ESYS_TR_NONE,
|
|
+ ESYS_TR_NONE,
|
|
+ ESYS_TR_NONE,
|
|
+ &out_public,
|
|
+ NULL,
|
|
+ NULL);
|
|
+ if (rc != TSS2_RC_SUCCESS)
|
|
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
|
|
+ "Failed to convert ray handle to ESYS_TR for SRK: %s",
|
|
+ sym_Tss2_RC_Decode(rc));
|
|
+ }
|
|
+
|
|
+ ret_primary->esys_handle = primary_tr;
|
|
+
|
|
+ if (ret_alg)
|
|
+ *ret_alg = out_public->publicArea.type;
|
|
+
|
|
+ return 1;
|
|
+}
|
|
+
|
|
+static int tpm2_make_primary(
|
|
+ Tpm2Context *c,
|
|
+ TPMI_ALG_PUBLIC alg,
|
|
+ bool use_srk_model,
|
|
+ TPMI_ALG_PUBLIC *ret_alg,
|
|
+ Tpm2Handle **ret_primary) {
|
|
+
|
|
+ static const TPM2B_SENSITIVE_CREATE primary_sensitive = {};
|
|
static const TPML_PCR_SELECTION creation_pcr = {};
|
|
+ const TPM2B_PUBLIC *primary_template = NULL;
|
|
+ Tpm2SRKTemplateFlags base_flags = use_srk_model ? TPM2_SRK_TEMPLATE_NEW_STYLE : 0;
|
|
+ _cleanup_(release_lock_file) LockFile srk_lock = LOCK_FILE_INIT;
|
|
TSS2_RC rc;
|
|
usec_t ts;
|
|
int r;
|
|
|
|
- log_debug("Creating primary key on TPM.");
|
|
+ log_debug("Creating %s on TPM.", use_srk_model ? "SRK" : "Transient Primary Key");
|
|
|
|
/* So apparently not all TPM2 devices support ECC. ECC is generally preferably, because it's so much
|
|
* faster, noticeably so (~10s vs. ~240ms on my system). Hence, unless explicitly configured let's
|
|
@@ -393,7 +549,42 @@ static int tpm2_make_primary(
|
|
if (r < 0)
|
|
return r;
|
|
|
|
+ /* we only need the SRK lock when making the SRK since its not atomic, transient
|
|
+ * primary creations don't even matter if they stomp on each other, the TPM will
|
|
+ * keep kicking back the same key.
|
|
+ */
|
|
+ if (use_srk_model) {
|
|
+ r = make_lock_file("/run/systemd/tpm2-srk-init", LOCK_EX, &srk_lock);
|
|
+ if (r < 0)
|
|
+ return log_error_errno(r, "Failed to take network zone lock: %m");
|
|
+ }
|
|
+
|
|
+ /* Find existing SRK and use it if present */
|
|
+ if (use_srk_model) {
|
|
+ TPMI_ALG_PUBLIC got_alg = TPM2_ALG_NULL;
|
|
+ r = tpm2_get_srk(c, &got_alg, primary);
|
|
+ if (r < 0)
|
|
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
|
|
+ "Failed to establish if SRK is present");
|
|
+ if (r == 1) {
|
|
+ log_debug("Discovered existing SRK");
|
|
+
|
|
+ if (alg != 0 && alg != got_alg)
|
|
+ log_warning("Caller asked for specific algorithm %u, but existing SRK is %u, ignoring",
|
|
+ alg, got_alg);
|
|
+
|
|
+ if (ret_alg)
|
|
+ *ret_alg = alg;
|
|
+ if (ret_primary)
|
|
+ *ret_primary = TAKE_PTR(primary);
|
|
+ return 0;
|
|
+ }
|
|
+ log_debug("Did not find SRK, generating...");
|
|
+ }
|
|
+
|
|
if (IN_SET(alg, 0, TPM2_ALG_ECC)) {
|
|
+ primary_template = tpm2_get_primary_template(base_flags | TPM2_SRK_TEMPLATE_ECC);
|
|
+
|
|
rc = sym_Esys_CreatePrimary(
|
|
c->esys_context,
|
|
ESYS_TR_RH_OWNER,
|
|
@@ -401,7 +592,7 @@ static int tpm2_make_primary(
|
|
ESYS_TR_NONE,
|
|
ESYS_TR_NONE,
|
|
&primary_sensitive,
|
|
- &primary_template_ecc,
|
|
+ primary_template,
|
|
NULL,
|
|
&creation_pcr,
|
|
&primary->esys_handle,
|
|
@@ -423,6 +614,8 @@ static int tpm2_make_primary(
|
|
}
|
|
|
|
if (IN_SET(alg, 0, TPM2_ALG_RSA)) {
|
|
+ primary_template = tpm2_get_primary_template(base_flags);
|
|
+
|
|
rc = sym_Esys_CreatePrimary(
|
|
c->esys_context,
|
|
ESYS_TR_RH_OWNER,
|
|
@@ -430,7 +623,7 @@ static int tpm2_make_primary(
|
|
ESYS_TR_NONE,
|
|
ESYS_TR_NONE,
|
|
&primary_sensitive,
|
|
- &primary_template_rsa,
|
|
+ primary_template,
|
|
NULL,
|
|
&creation_pcr,
|
|
&primary->esys_handle,
|
|
@@ -450,7 +643,17 @@ static int tpm2_make_primary(
|
|
log_debug("Successfully created RSA primary key on TPM.");
|
|
}
|
|
|
|
- log_debug("Generating primary key on TPM2 took %s.", FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - ts, USEC_PER_MSEC));
|
|
+ log_debug("Generating %s on the TPM2 took %s.", use_srk_model ? "SRK" : "Transient Primary Key",
|
|
+ FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - ts, USEC_PER_MSEC));
|
|
+
|
|
+ if (use_srk_model) {
|
|
+ rc = sym_Esys_EvictControl(c->esys_context, ESYS_TR_RH_OWNER, primary->esys_handle,
|
|
+ ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, SRK_HANDLE, &primary->esys_handle);
|
|
+ if (rc != TSS2_RC_SUCCESS)
|
|
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
|
|
+ "Failed to persist SRK within TPM: %s", sym_Tss2_RC_Decode(rc));
|
|
+ primary->keep = true;
|
|
+ }
|
|
|
|
if (ret_primary)
|
|
*ret_primary = TAKE_PTR(primary);
|
|
@@ -1774,10 +1977,13 @@ int tpm2_seal(const char *device,
|
|
void **ret_pcr_hash,
|
|
size_t *ret_pcr_hash_size,
|
|
uint16_t *ret_pcr_bank,
|
|
- uint16_t *ret_primary_alg) {
|
|
+ uint16_t *ret_primary_alg,
|
|
+ void **ret_srk_buf,
|
|
+ size_t *ret_srk_buf_size) {
|
|
|
|
_cleanup_(Esys_Freep) TPM2B_PRIVATE *private = NULL;
|
|
_cleanup_(Esys_Freep) TPM2B_PUBLIC *public = NULL;
|
|
+ _cleanup_(Esys_Freep) uint8_t *srk_buf = NULL;
|
|
static const TPML_PCR_SELECTION creation_pcr = {};
|
|
_cleanup_(erase_and_freep) void *secret = NULL;
|
|
_cleanup_free_ void *hash = NULL;
|
|
@@ -1786,6 +1992,7 @@ int tpm2_seal(const char *device,
|
|
TPM2B_PUBLIC hmac_template;
|
|
usec_t start;
|
|
TSS2_RC rc;
|
|
+ size_t srk_buf_size;
|
|
int r;
|
|
|
|
assert(pubkey || pubkey_size == 0);
|
|
@@ -1835,7 +2042,7 @@ int tpm2_seal(const char *device,
|
|
}
|
|
|
|
_cleanup_tpm2_handle_ Tpm2Handle *primary = NULL;
|
|
- r = tpm2_make_primary(c, &primary, 0, &primary_alg);
|
|
+ r = tpm2_make_primary(c, /* alg = */0, !!ret_srk_buf, &primary_alg, &primary);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
@@ -1960,9 +2167,35 @@ int tpm2_seal(const char *device,
|
|
if (!hash)
|
|
return log_oom();
|
|
|
|
+ /* serialize the key for storage in the LUKS header. A deserialized ESYS_TR provides both
|
|
+ * the raw TPM handle as well as the object name. The object name is used to verify that
|
|
+ * the key we use later is the key we expect to establish the session with.
|
|
+ */
|
|
+ if (ret_srk_buf) {
|
|
+ log_debug("Serializing SRK ESYS_TR reference");
|
|
+ rc = sym_Esys_TR_Serialize(c->esys_context, primary->esys_handle, &srk_buf, &srk_buf_size);
|
|
+ if (rc != TSS2_RC_SUCCESS)
|
|
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
|
|
+ "Failed to serialize primary key: %s", sym_Tss2_RC_Decode(rc));
|
|
+ }
|
|
+
|
|
if (DEBUG_LOGGING)
|
|
log_debug("Completed TPM2 key sealing in %s.", FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - start, 1));
|
|
|
|
+ if (ret_srk_buf) {
|
|
+ /*
|
|
+ * make a copy since we don't want the caller to understand that
|
|
+ * ESYS allocated the pointer. It would make tracking what deallocator
|
|
+ * to use for srk_buf in which context a PITA.
|
|
+ */
|
|
+ void *tmp = memdup(srk_buf, srk_buf_size);
|
|
+ if (!tmp)
|
|
+ return log_oom();
|
|
+
|
|
+ *ret_srk_buf = TAKE_PTR(tmp);
|
|
+ *ret_srk_buf_size = srk_buf_size;
|
|
+ }
|
|
+
|
|
*ret_secret = TAKE_PTR(secret);
|
|
*ret_secret_size = hmac_sensitive.sensitive.data.size;
|
|
*ret_blob = TAKE_PTR(blob);
|
|
@@ -1990,6 +2223,8 @@ int tpm2_unseal(const char *device,
|
|
size_t blob_size,
|
|
const void *known_policy_hash,
|
|
size_t known_policy_hash_size,
|
|
+ const void *srk_buf,
|
|
+ size_t srk_buf_size,
|
|
void **ret_secret,
|
|
size_t *ret_secret_size) {
|
|
|
|
@@ -2045,18 +2280,39 @@ int tpm2_unseal(const char *device,
|
|
if (r < 0)
|
|
return r;
|
|
|
|
+ /* If their is a primary key we trust, like an SRK, use it */
|
|
_cleanup_tpm2_handle_ Tpm2Handle *primary = NULL;
|
|
- r = tpm2_make_primary(c, &primary, primary_alg, NULL);
|
|
- if (r < 0)
|
|
- return r;
|
|
+ if (srk_buf) {
|
|
+
|
|
+ r = tpm2_handle_new(c, &primary);
|
|
+ if (r < 0)
|
|
+ return r;
|
|
+
|
|
+ primary->keep = true;
|
|
+
|
|
+ log_debug("Found existing SRK key to use, deserializing ESYS_TR");
|
|
+ rc = sym_Esys_TR_Deserialize(
|
|
+ c->esys_context,
|
|
+ srk_buf,
|
|
+ srk_buf_size,
|
|
+ &primary->esys_handle);
|
|
+ if (rc != TSS2_RC_SUCCESS)
|
|
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
|
|
+ "Failed to deserialize primary key: %s", sym_Tss2_RC_Decode(rc));
|
|
+ /* old callers without an SRK still need to create a key */
|
|
+ } else {
|
|
+ r = tpm2_make_primary(c, primary_alg, false, NULL, &primary);
|
|
+ if (r < 0)
|
|
+ return r;
|
|
+ }
|
|
|
|
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 if a pin
|
|
- * is provided. If an attacker gives back a bad key, we already lost since
|
|
- * primary key is not verified and they could attack there as well.
|
|
+ * 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_ Tpm2Handle *hmac_key = NULL;
|
|
r = tpm2_handle_new(c, &hmac_key);
|
|
@@ -2491,6 +2747,8 @@ int tpm2_make_luks2_json(
|
|
size_t policy_hash_size,
|
|
const void *salt,
|
|
size_t salt_size,
|
|
+ const void *srk_buf,
|
|
+ size_t srk_buf_size,
|
|
TPM2Flags flags,
|
|
JsonVariant **ret) {
|
|
|
|
@@ -2531,7 +2789,8 @@ int tpm2_make_luks2_json(
|
|
JSON_BUILD_PAIR("tpm2-pin", JSON_BUILD_BOOLEAN(flags & TPM2_FLAGS_USE_PIN)),
|
|
JSON_BUILD_PAIR_CONDITION(pubkey_pcr_mask != 0, "tpm2_pubkey_pcrs", JSON_BUILD_VARIANT(pkmj)),
|
|
JSON_BUILD_PAIR_CONDITION(pubkey_pcr_mask != 0, "tpm2_pubkey", JSON_BUILD_BASE64(pubkey, pubkey_size)),
|
|
- JSON_BUILD_PAIR_CONDITION(salt, "tpm2_salt", JSON_BUILD_BASE64(salt, salt_size))));
|
|
+ JSON_BUILD_PAIR_CONDITION(salt, "tpm2_salt", JSON_BUILD_BASE64(salt, salt_size)),
|
|
+ JSON_BUILD_PAIR_CONDITION(srk_buf, "tpm2_srk", JSON_BUILD_BASE64(srk_buf, srk_buf_size))));
|
|
if (r < 0)
|
|
return r;
|
|
|
|
@@ -2556,10 +2815,12 @@ int tpm2_parse_luks2_json(
|
|
size_t *ret_policy_hash_size,
|
|
void **ret_salt,
|
|
size_t *ret_salt_size,
|
|
+ void **ret_srk_buf,
|
|
+ size_t *ret_srk_buf_size,
|
|
TPM2Flags *ret_flags) {
|
|
|
|
- _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL, *salt = NULL;
|
|
- size_t blob_size = 0, policy_hash_size = 0, pubkey_size = 0, salt_size = 0;
|
|
+ _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL, *salt = NULL, *srk_buf = NULL;
|
|
+ size_t blob_size = 0, policy_hash_size = 0, pubkey_size = 0, salt_size = 0, srk_buf_size = 0;
|
|
uint32_t hash_pcr_mask = 0, pubkey_pcr_mask = 0;
|
|
uint16_t primary_alg = TPM2_ALG_ECC; /* ECC was the only supported algorithm in systemd < 250, use that as implied default, for compatibility */
|
|
uint16_t pcr_bank = UINT16_MAX; /* default: pick automatically */
|
|
@@ -2666,6 +2927,13 @@ int tpm2_parse_luks2_json(
|
|
} else if (pubkey_pcr_mask != 0)
|
|
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Public key PCR mask set, but not public key included in JSON data, refusing.");
|
|
|
|
+ w = json_variant_by_key(v, "tpm2_srk");
|
|
+ if (w) {
|
|
+ r = json_variant_unbase64(w, &srk_buf, &srk_buf_size);
|
|
+ if (r < 0)
|
|
+ return log_debug_errno(r, "Invalid base64 data in 'tpm2_srk' field.");
|
|
+ }
|
|
+
|
|
if (ret_keyslot)
|
|
*ret_keyslot = keyslot;
|
|
if (ret_hash_pcr_mask)
|
|
@@ -2694,6 +2962,10 @@ int tpm2_parse_luks2_json(
|
|
*ret_salt_size = salt_size;
|
|
if (ret_flags)
|
|
*ret_flags = flags;
|
|
+ if (ret_srk_buf)
|
|
+ *ret_srk_buf = TAKE_PTR(srk_buf);
|
|
+ if (ret_srk_buf_size)
|
|
+ *ret_srk_buf_size = srk_buf_size;
|
|
|
|
return 0;
|
|
}
|
|
diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h
|
|
index cc43bbfbfb..3c4d045197 100644
|
|
--- a/src/shared/tpm2-util.h
|
|
+++ b/src/shared/tpm2-util.h
|
|
@@ -13,6 +13,12 @@ typedef enum TPM2Flags {
|
|
} TPM2Flags;
|
|
|
|
|
|
+typedef enum Tpm2SRKTemplateFlags {
|
|
+ TPM2_SRK_TEMPLATE_ECC = 1 << 0,
|
|
+ TPM2_SRK_TEMPLATE_NEW_STYLE = 1 << 1,
|
|
+ _TPM2_SRK_TEMPLATE_MAX = TPM2_SRK_TEMPLATE_NEW_STYLE|TPM2_SRK_TEMPLATE_ECC,
|
|
+} Tpm2SRKTemplateFlags;
|
|
+
|
|
/* As per https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_PFP_r1p05_v23_pub.pdf a
|
|
* TPM2 on a Client PC must have at least 24 PCRs. This hardcodes our expectation of 24. */
|
|
#define TPM2_PCRS_MAX 24U
|
|
@@ -66,8 +72,8 @@ extern TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal)(uint8_t const buffer[], siz
|
|
|
|
int dlopen_tpm2(void);
|
|
|
|
-int tpm2_seal(const char *device, uint32_t hash_pcr_mask, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_pcr_hash, size_t *ret_pcr_hash_size, uint16_t *ret_pcr_bank, uint16_t *ret_primary_alg);
|
|
-int tpm2_unseal(const char *device, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, void **ret_secret, size_t *ret_secret_size);
|
|
+int tpm2_seal(const char *device, uint32_t hash_pcr_mask, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_pcr_hash, size_t *ret_pcr_hash_size, uint16_t *ret_pcr_bank, uint16_t *ret_primary_alg, void **ret_srk_buf, size_t *ret_srk_buf_size);
|
|
+int tpm2_unseal(const char *device, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *srk_buf, size_t srk_buf_size, void **ret_secret, size_t *ret_secret_size);
|
|
|
|
typedef struct {
|
|
unsigned n_ref;
|
|
@@ -86,6 +92,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(Tpm2Context*, tpm2_context_unref);
|
|
typedef struct {
|
|
Tpm2Context *tpm2_context;
|
|
ESYS_TR esys_handle;
|
|
+ bool keep;
|
|
} Tpm2Handle;
|
|
|
|
#define _tpm2_handle(c, h) { .tpm2_context = (c), .esys_handle = (h), }
|
|
@@ -125,6 +132,8 @@ char *tpm2_tpml_pcr_selection_to_string(const TPML_PCR_SELECTION *l);
|
|
size_t tpm2_tpml_pcr_selection_weight(const TPML_PCR_SELECTION *l);
|
|
#define tpm2_tpml_pcr_selection_is_empty(l) (tpm2_tpml_pcr_selection_weight(l) == 0)
|
|
|
|
+const TPM2B_PUBLIC *tpm2_get_primary_template(Tpm2SRKTemplateFlags flags);
|
|
+
|
|
#else /* HAVE_TPM2 */
|
|
typedef struct {} Tpm2Context;
|
|
typedef struct {} Tpm2Handle;
|
|
@@ -136,8 +145,8 @@ int tpm2_find_device_auto(int log_level, char **ret);
|
|
int tpm2_make_pcr_json_array(uint32_t pcr_mask, JsonVariant **ret);
|
|
int tpm2_parse_pcr_json_array(JsonVariant *v, uint32_t *ret);
|
|
|
|
-int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *salt, size_t salt_size, TPM2Flags flags, JsonVariant **ret);
|
|
-int tpm2_parse_luks2_json(JsonVariant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, void **ret_pubkey, size_t *ret_pubkey_size, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, void **ret_blob, size_t *ret_blob_size, void **ret_policy_hash, size_t *ret_policy_hash_size, void **ret_salt, size_t *ret_salt_size, TPM2Flags *ret_flags);
|
|
+int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *salt, size_t salt_size, const void *srk_buf, size_t srk_buf_size, TPM2Flags flags, JsonVariant **ret);
|
|
+int tpm2_parse_luks2_json(JsonVariant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, void **ret_pubkey, size_t *ret_pubkey_size, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, void **ret_blob, size_t *ret_blob_size, void **ret_policy_hash, size_t *ret_policy_hash_size, void **ret_salt, size_t *ret_salt_size, void **ret_srk_buf, size_t *ret_srk_buf_size, TPM2Flags *ret_flags);
|
|
|
|
/* Default to PCR 7 only */
|
|
#define TPM2_PCR_MASK_DEFAULT (UINT32_C(1) << 7)
|
|
diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c
|
|
index 20baa0f261..2c696e443d 100644
|
|
--- a/src/test/test-tpm2.c
|
|
+++ b/src/test/test-tpm2.c
|
|
@@ -409,6 +409,97 @@ TEST(tpml_pcr_selection_add_sub) {
|
|
expected2, expected2_count);
|
|
}
|
|
|
|
+
|
|
+/* this test includes TPM2 specific data structures */
|
|
+TEST(tpm2_get_primary_template) {
|
|
+
|
|
+ /*
|
|
+ * Verify that if someone changes the template code, they know they're breaking things.
|
|
+ * Templates MUST be changed in a backwards compatible way.
|
|
+ *
|
|
+ */
|
|
+ static const TPM2B_PUBLIC templ[] = {
|
|
+ /* index 0 RSA old */
|
|
+ [0] = {
|
|
+ .publicArea = {
|
|
+ .type = TPM2_ALG_RSA,
|
|
+ .nameAlg = TPM2_ALG_SHA256,
|
|
+ .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH,
|
|
+ .parameters.rsaDetail = {
|
|
+ .symmetric = {
|
|
+ .algorithm = TPM2_ALG_AES,
|
|
+ .keyBits.aes = 128,
|
|
+ .mode.aes = TPM2_ALG_CFB,
|
|
+ },
|
|
+ .scheme.scheme = TPM2_ALG_NULL,
|
|
+ .keyBits = 2048,
|
|
+ },
|
|
+ },
|
|
+ },
|
|
+ /* Index 1 ECC old */
|
|
+ [TPM2_SRK_TEMPLATE_ECC] = {
|
|
+ .publicArea = {
|
|
+ .type = TPM2_ALG_ECC,
|
|
+ .nameAlg = TPM2_ALG_SHA256,
|
|
+ .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH,
|
|
+ .parameters.eccDetail = {
|
|
+ .symmetric = {
|
|
+ .algorithm = TPM2_ALG_AES,
|
|
+ .keyBits.aes = 128,
|
|
+ .mode.aes = TPM2_ALG_CFB,
|
|
+ },
|
|
+ .scheme.scheme = TPM2_ALG_NULL,
|
|
+ .curveID = TPM2_ECC_NIST_P256,
|
|
+ .kdf.scheme = TPM2_ALG_NULL,
|
|
+ },
|
|
+ },
|
|
+ },
|
|
+ /* index 2 RSA SRK */
|
|
+ [TPM2_SRK_TEMPLATE_NEW_STYLE] = {
|
|
+ .publicArea = {
|
|
+ .type = TPM2_ALG_RSA,
|
|
+ .nameAlg = TPM2_ALG_SHA256,
|
|
+ .objectAttributes = TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_USERWITHAUTH|TPMA_OBJECT_NODA,
|
|
+ .parameters.rsaDetail = {
|
|
+ .symmetric = {
|
|
+ .algorithm = TPM2_ALG_AES,
|
|
+ .keyBits.aes = 128,
|
|
+ .mode.aes = TPM2_ALG_CFB,
|
|
+ },
|
|
+ .scheme.scheme = TPM2_ALG_NULL,
|
|
+ .keyBits = 2048,
|
|
+ },
|
|
+ },
|
|
+ },
|
|
+ /* Index 3 ECC SRK */
|
|
+ [TPM2_SRK_TEMPLATE_NEW_STYLE | TPM2_SRK_TEMPLATE_ECC] = {
|
|
+ .publicArea = {
|
|
+ .type = TPM2_ALG_ECC,
|
|
+ .nameAlg = TPM2_ALG_SHA256,
|
|
+ .objectAttributes = TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_USERWITHAUTH|TPMA_OBJECT_NODA,
|
|
+ .parameters.eccDetail = {
|
|
+ .symmetric = {
|
|
+ .algorithm = TPM2_ALG_AES,
|
|
+ .keyBits.aes = 128,
|
|
+ .mode.aes = TPM2_ALG_CFB,
|
|
+ },
|
|
+ .scheme.scheme = TPM2_ALG_NULL,
|
|
+ .curveID = TPM2_ECC_NIST_P256,
|
|
+ .kdf.scheme = TPM2_ALG_NULL,
|
|
+ },
|
|
+ },
|
|
+ },
|
|
+ };
|
|
+
|
|
+ assert_cc(ELEMENTSOF(templ) == _TPM2_SRK_TEMPLATE_MAX + 1);
|
|
+
|
|
+ for (size_t i = 0; i < ELEMENTSOF(templ); i++) {
|
|
+ /* the index counter lines up with the flags and the expected template received */
|
|
+ const TPM2B_PUBLIC *got = tpm2_get_primary_template((Tpm2SRKTemplateFlags)i);
|
|
+ assert_se(memcmp(&templ[i], got, sizeof(*got)) == 0);
|
|
+ }
|
|
+}
|
|
+
|
|
#endif /* HAVE_TPM2 */
|
|
|
|
DEFINE_TEST_MAIN(LOG_DEBUG);
|