systemd/0516-tpm2-add-support-for-a...

1152 lines
60 KiB
Diff

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);