linux-integrity.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/8] openssl_tpm2_engine: Add attestation functions for primary keys
@ 2024-08-02 20:25 James Bottomley
  2024-08-02 20:25 ` [PATCH 1/8] tss: Fix handling of TPM_RH_NULL in intel-tss James Bottomley
                   ` (7 more replies)
  0 siblings, 8 replies; 20+ messages in thread
From: James Bottomley @ 2024-08-02 20:25 UTC (permalink / raw)
  To: openssl-tpm2-engine; +Cc: linux-integrity, Jarkko Sakkinen

The first 5 patches add supporting infrastructure and the next three
add the actually attestation command, its man page and its tests.

The design is to be able to store a stable copy of the signing EK
(done by name) in /etc/eksign.name, which can then be used to verify
any on the fly creation of the signing key.  The reason for using a
signing EK not an AK as the specs usually require is to have the
simplicity of a stable key that never changes as the attesting key and
because there are no privacy issues in the machine owner knowing it.
The command then provides a way to attest this key against the EK
certificate (if the TPM has one).  If there is no EK certificate, the
signing EK name is used on a trust on first use (TOFU) basis.

attest_tpm2_primary --eksign > /etc/eksign.name

Will create this file once.  If an EK certificate exists, the
eksign.name file can be attested to that certificate with

attest_tpm2_primary --attest tpm-cert.crt --name /etc/eksign.name

The above commands should only need to be done once per TPM.

Once the Signing EK is known, it can be used on every boot to create
and certify the NULL key, which is what the kernel uses and exports
from version 6.10 onwards and thus proves to the user that the
kernel's reliance on the NULL key during boot was cryptographically
justifed.  This can be done from a boot script as:

attest_tpm2_primary --certify null --name /etc/eksign.name /sys/class/tpm/tpm0/null_name

Which will return true if the certification succeeds.  If the
certification fails, all TPM functions should be considered
compromised.  Whether boot should continue even with a compromised TPM
is a user policy decision.

James

---

James Bottomley (8):
  tss: Fix handling of TPM_RH_NULL in intel-tss
  libcommon: add ability to create a signing primary key
  libcommon: add bin2hex and tmp2_get_hexname
  libcommon: add primary creation from template
  tss: add tpm2_Certify, tpm2_ActivateCredential and tpm2_PolicyOR
  tools: add new attest_tpm2_primary command
  attest_tpm2_primary: add man page
  tests: add tests for attest_tpm2_primary

 src/include/ibm-tss.h              |  84 +++
 src/include/intel-tss.h            |  95 +++-
 src/include/tpm2-common.h          |   9 +
 src/libcommon/tpm2-common.c        |  93 +++-
 src/tools/Makefile.am              |  11 +-
 src/tools/attest_tpm2_primary.1.in | 103 ++++
 src/tools/attest_tpm2_primary.c    | 842 +++++++++++++++++++++++++++++
 tests/attestation.sh               |  30 +
 tests/check_importable.sh          |   3 +-
 tests/engine/Makefile.am           |   3 +-
 tests/provider/Makefile.am         |   3 +-
 tests/seal_unseal.sh               |   3 +-
 tests/start_sw_tpm.sh              |   2 +
 13 files changed, 1230 insertions(+), 51 deletions(-)
 create mode 100644 src/tools/attest_tpm2_primary.1.in
 create mode 100644 src/tools/attest_tpm2_primary.c
 create mode 100755 tests/attestation.sh

-- 
2.35.3


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH 1/8] tss: Fix handling of TPM_RH_NULL in intel-tss
  2024-08-02 20:25 [PATCH 0/8] openssl_tpm2_engine: Add attestation functions for primary keys James Bottomley
@ 2024-08-02 20:25 ` James Bottomley
  2024-08-03 17:08   ` Jarkko Sakkinen
  2024-08-02 20:26 ` [PATCH 2/8] libcommon: add ability to create a signing primary key James Bottomley
                   ` (6 subsequent siblings)
  7 siblings, 1 reply; 20+ messages in thread
From: James Bottomley @ 2024-08-02 20:25 UTC (permalink / raw)
  To: openssl-tpm2-engine; +Cc: linux-integrity, Jarkko Sakkinen

Now that we're going to be using the NULL primary to salt sessions,
the Intel TSS shim needs fixing to cope with this.  In the Intel TSS,
there are two internal handles representing NULL: ESYS_TR_NONE and
ESYS_TR_RH_NULL.  We translate TPM_RH_NULL to ESYS_TR_NONE because
most of the time it does mean no value.  However, for the NULL primary
handle we must use ESYS_TR_RH_NULL, so check for that specific case
and fix it.  Additionally remove the intel_handle() code which was
supposed to do this: it's unused because 0 is never passed in as a
handle number.

Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
---
 src/include/intel-tss.h | 18 +++++-------------
 1 file changed, 5 insertions(+), 13 deletions(-)

diff --git a/src/include/intel-tss.h b/src/include/intel-tss.h
index 1870b4e..5b8db20 100644
--- a/src/include/intel-tss.h
+++ b/src/include/intel-tss.h
@@ -251,14 +251,6 @@ intel_sess_helper(TSS_CONTEXT *tssContext, TPM_HANDLE auth, TPMA_SESSION flags)
 				  TPMA_SESSION_CONTINUESESSION | flags);
 }
 
-static inline TPM_HANDLE
-intel_handle(TPM_HANDLE h)
-{
-	if (h == 0)
-		return ESYS_TR_NONE;
-	return h;
-}
-
 static inline void
 TSS_Delete(TSS_CONTEXT *tssContext)
 {
@@ -937,8 +929,10 @@ tpm2_CreatePrimary(TSS_CONTEXT *tssContext, TPM_HANDLE primaryHandle,
 	TPM2B_PUBLIC *opub;
 	TPM_RC rc;
 
-	/* FIXME will generate wrong value for NULL hierarchy */
-	primaryHandle = intel_handle(primaryHandle);
+
+	/* TPM_RH_NULL is mapped to ESYS_TR_NONE, which won't work here */
+	if (primaryHandle == TPM_RH_NULL)
+		primaryHandle = INT_TPM_RH_NULL;
 
 	outsideInfo.size = 0;
 	creationPcr.count = 0;
@@ -993,9 +987,7 @@ tpm2_StartAuthSession(TSS_CONTEXT *tssContext, TPM_HANDLE tpmKey,
 		      TPM_HANDLE *sessionHandle,
 		      const char *bindPassword)
 {
-	bind = intel_handle(bind);
-	tpmKey = intel_handle(tpmKey);
-	if (bind != ESYS_TR_NONE)
+	if (bind != TPM_RH_NULL)
 		intel_auth_helper(tssContext, bind, bindPassword);
 
 	return Esys_StartAuthSession(tssContext, tpmKey, bind, ESYS_TR_NONE,
-- 
2.35.3


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 2/8] libcommon: add ability to create a signing primary key
  2024-08-02 20:25 [PATCH 0/8] openssl_tpm2_engine: Add attestation functions for primary keys James Bottomley
  2024-08-02 20:25 ` [PATCH 1/8] tss: Fix handling of TPM_RH_NULL in intel-tss James Bottomley
@ 2024-08-02 20:26 ` James Bottomley
  2024-08-02 20:26 ` [PATCH 3/8] libcommon: add bin2hex and tmp2_get_hexname James Bottomley
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 20+ messages in thread
From: James Bottomley @ 2024-08-02 20:26 UTC (permalink / raw)
  To: openssl-tpm2-engine; +Cc: linux-integrity, Jarkko Sakkinen

Having a primary key that can sign things is useful for conducting
certification and quoting operations without having to go through the
makecredential/activatecredential dance, which is unnecessary for a
local TPM where you don't need privacy separated attestation keys.
Add the ability to use the signing key template to tpm2_load_srk().

Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
---
 src/include/tpm2-common.h   |  1 +
 src/libcommon/tpm2-common.c | 23 ++++++++++++++++++-----
 2 files changed, 19 insertions(+), 5 deletions(-)

diff --git a/src/include/tpm2-common.h b/src/include/tpm2-common.h
index 4520f76..97b60f2 100644
--- a/src/include/tpm2-common.h
+++ b/src/include/tpm2-common.h
@@ -23,6 +23,7 @@ enum tpm2_type {
 	TPM2_LOADABLE = 1,
 	TPM2_IMPORTABLE = 2,
 	TPM2_SEALED = 3,
+	TPM2_SIGNING = 4,
 };
 
 struct policies {
diff --git a/src/libcommon/tpm2-common.c b/src/libcommon/tpm2-common.c
index 3b9f785..b70ac27 100644
--- a/src/libcommon/tpm2-common.c
+++ b/src/libcommon/tpm2-common.c
@@ -743,17 +743,30 @@ TPM_RC tpm2_load_srk(TSS_CONTEXT *tssContext, TPM_HANDLE *h, const char *auth,
 		TPMA_OBJECT_NODA |
 		TPMA_OBJECT_SENSITIVEDATAORIGIN |
 		TPMA_OBJECT_USERWITHAUTH |
-		TPMA_OBJECT_DECRYPT |
 		TPMA_OBJECT_RESTRICTED;
+	if (type == TPM2_SIGNING)
+		VAL(inPublic.publicArea.objectAttributes) |=
+			TPMA_OBJECT_SIGN;
+	else
+		VAL(inPublic.publicArea.objectAttributes) |=
+			TPMA_OBJECT_DECRYPT;
 	if (type != TPM2_LEGACY)
 		VAL(inPublic.publicArea.objectAttributes) |=
 			TPMA_OBJECT_FIXEDPARENT |
 			TPMA_OBJECT_FIXEDTPM;
 
-	inPublic.publicArea.parameters.eccDetail.symmetric.algorithm = TPM_ALG_AES;
-	inPublic.publicArea.parameters.eccDetail.symmetric.keyBits.aes = 128;
-	inPublic.publicArea.parameters.eccDetail.symmetric.mode.aes = TPM_ALG_CFB;
-	inPublic.publicArea.parameters.eccDetail.scheme.scheme = TPM_ALG_NULL;
+	if (type != TPM2_SIGNING) {
+		inPublic.publicArea.parameters.eccDetail.symmetric.algorithm = TPM_ALG_AES;
+		inPublic.publicArea.parameters.eccDetail.symmetric.keyBits.aes = 128;
+		inPublic.publicArea.parameters.eccDetail.symmetric.mode.aes = TPM_ALG_CFB;
+		inPublic.publicArea.parameters.eccDetail.scheme.scheme = TPM_ALG_NULL;
+	} else {
+		inPublic.publicArea.parameters.eccDetail.symmetric.algorithm = TPM_ALG_NULL;
+		inPublic.publicArea.parameters.eccDetail.symmetric.keyBits.aes = 0;
+		inPublic.publicArea.parameters.eccDetail.symmetric.mode.aes = TPM_ALG_NULL;
+		inPublic.publicArea.parameters.eccDetail.scheme.scheme = TPM_ALG_ECDSA;
+		inPublic.publicArea.parameters.eccDetail.scheme.details.ecdsa.hashAlg = TPM_ALG_SHA256;
+	}
 	inPublic.publicArea.parameters.eccDetail.curveID = TPM_ECC_NIST_P256;
 	inPublic.publicArea.parameters.eccDetail.kdf.scheme = TPM_ALG_NULL;
 
-- 
2.35.3


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 3/8] libcommon: add bin2hex and tmp2_get_hexname
  2024-08-02 20:25 [PATCH 0/8] openssl_tpm2_engine: Add attestation functions for primary keys James Bottomley
  2024-08-02 20:25 ` [PATCH 1/8] tss: Fix handling of TPM_RH_NULL in intel-tss James Bottomley
  2024-08-02 20:26 ` [PATCH 2/8] libcommon: add ability to create a signing primary key James Bottomley
@ 2024-08-02 20:26 ` James Bottomley
  2024-08-03 17:21   ` Jarkko Sakkinen
  2024-08-02 20:26 ` [PATCH 4/8] libcommon: add primary creation from template James Bottomley
                   ` (4 subsequent siblings)
  7 siblings, 1 reply; 20+ messages in thread
From: James Bottomley @ 2024-08-02 20:26 UTC (permalink / raw)
  To: openssl-tpm2-engine; +Cc: linux-integrity, Jarkko Sakkinen

Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
---
 src/include/tpm2-common.h   |  5 +++++
 src/libcommon/tpm2-common.c | 16 ++++++++++++++++
 2 files changed, 21 insertions(+)

diff --git a/src/include/tpm2-common.h b/src/include/tpm2-common.h
index 97b60f2..0e0f28a 100644
--- a/src/include/tpm2-common.h
+++ b/src/include/tpm2-common.h
@@ -9,6 +9,9 @@
  * not a TPM error, so don't process the rc as one */
 #define NOT_TPM_ERROR (0xffffffff)
 
+/* maximum space for a sha256 name in ascii */
+#define MAX_HEXNAME 132
+
 extern TPM_ALG_ID name_alg;
 
 struct policy_command {
@@ -141,4 +144,6 @@ int tpm2_rsa_decrypt(const struct app_data *ad, PUBLIC_KEY_RSA_2B *cipherText,
 		     char *srk_auth);
 int tpm2_rm_signed_policy(char *tpmkey, int rmnum);
 int tpm2_get_signed_policy(char *tpmkey, STACK_OF(TSSAUTHPOLICY) **sk);
+void bin2hex(char *dst, const unsigned char *src, size_t count);
+void tpm2_get_hexname(char hexname[MAX_HEXNAME], TPM2B_PUBLIC *pub);
 #endif
diff --git a/src/libcommon/tpm2-common.c b/src/libcommon/tpm2-common.c
index b70ac27..3ffa773 100644
--- a/src/libcommon/tpm2-common.c
+++ b/src/libcommon/tpm2-common.c
@@ -2320,6 +2320,14 @@ int hex2bin(unsigned char *dst, const char *src, size_t count)
 	return 0;
 }
 
+void bin2hex(char *dst, const unsigned char *src, size_t count)
+{
+	int i;
+
+	for (i = 0; i < count; i++)
+		sprintf(&dst[i<<1], "%02x", src[i]);
+}
+
 TPM_RC tpm2_parse_policy_file(const char *policy_file,
 			      STACK_OF(TSSOPTPOLICY) *sk,
 			      char *auth, TPMT_HA *digest)
@@ -3376,6 +3384,14 @@ openssl_print_errors()
 	ERR_print_errors_fp(stderr);
 }
 
+void tpm2_get_hexname(char hexname[MAX_HEXNAME], TPM2B_PUBLIC *pub)
+{
+	NAME_2B n;
+
+	tpm2_ObjectPublic_GetName(&n, &pub->publicArea);
+	bin2hex(hexname, (unsigned char *)n.name, n.size);
+}
+
 IMPLEMENT_ASN1_FUNCTIONS(TSSOPTPOLICY)
 IMPLEMENT_ASN1_FUNCTIONS(TSSAUTHPOLICY)
 IMPLEMENT_ASN1_FUNCTIONS(TSSLOADABLE)
-- 
2.35.3


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 4/8] libcommon: add primary creation from template
  2024-08-02 20:25 [PATCH 0/8] openssl_tpm2_engine: Add attestation functions for primary keys James Bottomley
                   ` (2 preceding siblings ...)
  2024-08-02 20:26 ` [PATCH 3/8] libcommon: add bin2hex and tmp2_get_hexname James Bottomley
@ 2024-08-02 20:26 ` James Bottomley
  2024-08-02 20:26 ` [PATCH 5/8] tss: add tpm2_Certify, tpm2_ActivateCredential and tpm2_PolicyOR James Bottomley
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 20+ messages in thread
From: James Bottomley @ 2024-08-02 20:26 UTC (permalink / raw)
  To: openssl-tpm2-engine; +Cc: linux-integrity, Jarkko Sakkinen

Although for usual operation we only need the standard template to
create the keys, for EK operations we need to create the EK from the
exact EK template (of which there are a few), so add a new function to
allow the creation of a primary from any given template.

Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
---
 src/include/tpm2-common.h   |  3 +++
 src/libcommon/tpm2-common.c | 54 +++++++++++++++++++++----------------
 2 files changed, 34 insertions(+), 23 deletions(-)

diff --git a/src/include/tpm2-common.h b/src/include/tpm2-common.h
index 0e0f28a..026a2ae 100644
--- a/src/include/tpm2-common.h
+++ b/src/include/tpm2-common.h
@@ -59,6 +59,9 @@ struct app_data {
 void tpm2_error(TPM_RC rc, const char *reason);
 TPM_RC tpm2_load_srk(TSS_CONTEXT *tssContext, TPM_HANDLE *h, const char *auth,
 		     TPM2B_PUBLIC *pub, TPM_HANDLE handle, enum tpm2_type type);
+TPM_RC tpm2_load_srk_tmpl(TSS_CONTEXT *tssContext, TPM_HANDLE *h,
+			  const char *auth, TPM2B_PUBLIC *tmpl,
+			  TPM2B_PUBLIC *pub, TPM_HANDLE hierarchy);
 void tpm2_flush_handle(TSS_CONTEXT *tssContext, TPM_HANDLE h);
 EVP_PKEY *tpm2_to_openssl_public(TPMT_PUBLIC *pub);
 void tpm2_flush_srk(TSS_CONTEXT *tssContext, TPM_HANDLE hSRK);
diff --git a/src/libcommon/tpm2-common.c b/src/libcommon/tpm2-common.c
index 3ffa773..be3fe50 100644
--- a/src/libcommon/tpm2-common.c
+++ b/src/libcommon/tpm2-common.c
@@ -717,13 +717,12 @@ TPM_RC tpm2_ObjectPublic_GetName(NAME_2B *name,
 	return rc;
 }
 
-TPM_RC tpm2_load_srk(TSS_CONTEXT *tssContext, TPM_HANDLE *h, const char *auth,
-		     TPM2B_PUBLIC *pub, TPM_HANDLE hierarchy,
-		     enum tpm2_type type)
+TPM_RC tpm2_load_srk_tmpl(TSS_CONTEXT *tssContext, TPM_HANDLE *h,
+			  const char *auth, TPM2B_PUBLIC *tmpl,
+			  TPM2B_PUBLIC *pub, TPM_HANDLE hierarchy)
 {
 	TPM_RC rc;
 	TPM2B_SENSITIVE_CREATE inSensitive;
-	TPM2B_PUBLIC inPublic;
 	TPM_HANDLE session = TPM_RS_PW;
 
 	if (auth) {
@@ -736,7 +735,33 @@ TPM_RC tpm2_load_srk(TSS_CONTEXT *tssContext, TPM_HANDLE *h, const char *auth,
 	/* no sensitive date for storage keys */
 	VAL_2B(inSensitive.sensitive.data, size) = 0;
 
-	/* public parameters for an RSA2048 key  */
+	/* use a bound session here because we have no known key objects
+	 * to encrypt a salt to */
+	if (auth) {
+		rc = tpm2_get_bound_handle(tssContext, &session, hierarchy, auth);
+		if (rc)
+			return rc;
+	}
+
+	rc = tpm2_CreatePrimary(tssContext, hierarchy, &inSensitive, tmpl,
+				h, pub, session, auth);
+
+	if (rc) {
+		tpm2_error(rc, "TSS_CreatePrimary");
+		if (session != TPM_RS_PW)
+			tpm2_flush_handle(tssContext, session);
+	}
+
+	return rc;
+}
+
+TPM_RC tpm2_load_srk(TSS_CONTEXT *tssContext, TPM_HANDLE *h, const char *auth,
+		     TPM2B_PUBLIC *pub, TPM_HANDLE hierarchy,
+		     enum tpm2_type type)
+{
+	TPM2B_PUBLIC inPublic;
+
+	/* public parameters for a P-256 key  */
 	inPublic.publicArea.type = TPM_ALG_ECC;
 	inPublic.publicArea.nameAlg = TPM_ALG_SHA256;
 	VAL(inPublic.publicArea.objectAttributes) =
@@ -774,24 +799,7 @@ TPM_RC tpm2_load_srk(TSS_CONTEXT *tssContext, TPM_HANDLE *h, const char *auth,
 	VAL_2B(inPublic.publicArea.unique.ecc.y, size) = 0;
 	VAL_2B(inPublic.publicArea.authPolicy, size) = 0;
 
-	/* use a bound session here because we have no known key objects
-	 * to encrypt a salt to */
-	if (auth) {
-		rc = tpm2_get_bound_handle(tssContext, &session, hierarchy, auth);
-		if (rc)
-			return rc;
-	}
-
-	rc = tpm2_CreatePrimary(tssContext, hierarchy, &inSensitive, &inPublic,
-				h, pub, session, auth);
-
-	if (rc) {
-		tpm2_error(rc, "TSS_CreatePrimary");
-		if (session != TPM_RS_PW)
-			tpm2_flush_handle(tssContext, session);
-	}
-
-	return rc;
+	return tpm2_load_srk_tmpl(tssContext, h, auth, &inPublic, pub, hierarchy);
 }
 
 void tpm2_flush_srk(TSS_CONTEXT *tssContext, TPM_HANDLE hSRK)
-- 
2.35.3


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 5/8] tss: add tpm2_Certify, tpm2_ActivateCredential and tpm2_PolicyOR
  2024-08-02 20:25 [PATCH 0/8] openssl_tpm2_engine: Add attestation functions for primary keys James Bottomley
                   ` (3 preceding siblings ...)
  2024-08-02 20:26 ` [PATCH 4/8] libcommon: add primary creation from template James Bottomley
@ 2024-08-02 20:26 ` James Bottomley
  2024-08-02 20:26 ` [PATCH 6/8] tools: add new attest_tpm2_primary command James Bottomley
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 20+ messages in thread
From: James Bottomley @ 2024-08-02 20:26 UTC (permalink / raw)
  To: openssl-tpm2-engine; +Cc: linux-integrity, Jarkko Sakkinen

tpm2_Certify is used to verify that a given object is resident in the
TPM.  tpm2_ActivateCredential is used to decrypt a challenge from a
privacyCA and constructing the high template for the EK to use with
this requires PolicyOR.

Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
---
 src/include/ibm-tss.h   | 84 +++++++++++++++++++++++++++++++++++++++++
 src/include/intel-tss.h | 77 ++++++++++++++++++++++++++++++++++++-
 2 files changed, 160 insertions(+), 1 deletion(-)

diff --git a/src/include/ibm-tss.h b/src/include/ibm-tss.h
index 1b53319..b5da340 100644
--- a/src/include/ibm-tss.h
+++ b/src/include/ibm-tss.h
@@ -16,6 +16,7 @@
 #define VAL(X)			X.val
 #define VAL_2B(X, MEMBER)	X.b.MEMBER
 #define VAL_2B_P(X, MEMBER)	X->b.MEMBER
+#define VAL_T(X, MEMBER)	X.t.MEMBER
 
 static inline void
 tpm2_error(TPM_RC rc, const char *reason)
@@ -695,6 +696,26 @@ tpm2_PolicySecret(TSS_CONTEXT *tssContext, TPM_HANDLE authHandle,
 	return rc;
 }
 
+static inline TPM_RC
+tpm2_PolicyOR(TSS_CONTEXT *tssContext, TPM_HANDLE policySession,
+	      TPML_DIGEST *pHashList)
+{
+	PolicyOR_In in;
+	TPM_RC rc;
+
+	in.policySession = policySession;
+	in.pHashList = *pHashList;
+
+	rc = TSS_Execute(tssContext,
+			 NULL,
+			 (COMMAND_PARAMETERS *)&in,
+			 NULL,
+			 TPM_CC_PolicyOR,
+			 TPM_RH_NULL, NULL, 0);
+
+	return rc;
+}
+
 static inline TPM_RC
 tpm2_PolicyGetDigest(TSS_CONTEXT *tssContext, TPM_HANDLE policySession,
 		     DIGEST_2B *digest)
@@ -743,6 +764,69 @@ tpm2_PCR_Read(TSS_CONTEXT *tssContext, TPML_PCR_SELECTION *pcrSelectionIn,
 	return rc;
 }
 
+static inline TPM_RC
+tpm2_Certify(TSS_CONTEXT *tssContext, TPM_HANDLE objectHandle,
+	     TPM_HANDLE signHandle, DATA_2B *qualifyingData,
+	     ATTEST_2B *certifyInfo, TPMT_SIGNATURE *signature)
+{
+	Certify_In in;
+	Certify_Out out;
+	TPM_RC rc;
+
+	in.objectHandle = objectHandle;
+	in.signHandle = signHandle;
+	in.qualifyingData.t = *qualifyingData;
+	in.inScheme.scheme = TPM_ALG_NULL;
+
+	rc = TSS_Execute(tssContext,
+			 (RESPONSE_PARAMETERS *)&out,
+			 (COMMAND_PARAMETERS *)&in,
+			 NULL,
+			 TPM_CC_Certify,
+			 TPM_RS_PW, NULL, 0,
+			 TPM_RS_PW, NULL, 0,
+			 TPM_RH_NULL, NULL, 0);
+
+	if (rc)
+		return rc;
+
+	*certifyInfo = out.certifyInfo.t;
+	*signature = out.signature;
+
+	return rc;
+}
+
+static inline TPM_RC
+tpm2_ActivateCredential(TSS_CONTEXT *tssContext, TPM_HANDLE activateHandle,
+			TPM_HANDLE keyHandle, ID_OBJECT_2B *credentialBlob,
+			ENCRYPTED_SECRET_2B *secret, DIGEST_2B *certinfo,
+			TPM_HANDLE auth)
+{
+	ActivateCredential_In in;
+	ActivateCredential_Out out;
+	TPM_RC rc;
+
+	in.activateHandle = activateHandle;
+	in.keyHandle = keyHandle;
+	in.credentialBlob.t = *credentialBlob;
+	in.secret.t = *secret;
+
+	rc = TSS_Execute(tssContext,
+			 (RESPONSE_PARAMETERS *)&out,
+			 (COMMAND_PARAMETERS *)&in,
+			 NULL,
+			 TPM_CC_ActivateCredential,
+			 TPM_RS_PW, NULL, 0,
+			 auth, NULL, TPMA_SESSION_ENCRYPT,
+			 TPM_RH_NULL, NULL, 0);
+	if (rc)
+		return rc;
+
+	*certinfo = out.certInfo.t;
+
+	return rc;
+}
+
 static inline TPM_HANDLE
 tpm2_handle_int(TSS_CONTEXT *tssContext, TPM_HANDLE h)
 {
diff --git a/src/include/intel-tss.h b/src/include/intel-tss.h
index 5b8db20..3b8c18d 100644
--- a/src/include/intel-tss.h
+++ b/src/include/intel-tss.h
@@ -74,6 +74,7 @@
 #define TPM_CC_PolicySecret	TPM2_CC_PolicySecret
 
 #define TPM_ST_HASHCHECK	TPM2_ST_HASHCHECK
+#define TPM_ST_ATTEST_CERTIFY	TPM2_ST_ATTEST_CERTIFY
 
 #define TPM_RH_OWNER		ESYS_TR_RH_OWNER
 #define TPM_RH_PLATFORM		ESYS_TR_RH_PLATFORM
@@ -131,6 +132,7 @@
 
 /* Intel and IBM have slightly different names for all the 2B structures */
 
+#define ATTEST_2B		TPM2B_ATTEST
 #define NAME_2B			TPM2B_NAME
 #define DATA_2B			TPM2B_DATA
 #define PRIVATE_2B		TPM2B_PRIVATE
@@ -138,6 +140,7 @@
 #define KEY_2B			TPM2B_KEY
 #define TPM2B_KEY		TPM2B_DATA
 #define DIGEST_2B		TPM2B_DIGEST
+#define ID_OBJECT_2B		TPM2B_ID_OBJECT
 #define ECC_PARAMETER_2B	TPM2B_ECC_PARAMETER
 #define SENSITIVE_DATA_2B	TPM2B_SENSITIVE_DATA
 #define PUBLIC_KEY_RSA_2B	TPM2B_PUBLIC_KEY_RSA
@@ -196,8 +199,11 @@ TSS_CONVERT_MARSHAL(TPM2B_PRIVATE, )
 TSS_CONVERT_MARSHAL(TPML_PCR_SELECTION, )
 TSS_CONVERT_MARSHAL(TPMT_SIGNATURE, )
 TSS_CONVERT_MARSHAL(UINT32, *)
+#define Tss2_MU_TPM_HANDLE_Marshal Tss2_MU_TPM2_HANDLE_Marshal
+TSS_CONVERT_MARSHAL(TPM_HANDLE, *)
 #define TSS_TPM_CC_Marshal TSS_UINT32_Marshal
 
+TSS_CONVERT_UNMARSHAL(TPMS_ATTEST, )
 TSS_CONVERT_UNMARSHAL(TPML_PCR_SELECTION, )
 TSS_CONVERT_UNMARSHAL(TPM2B_PRIVATE, )
 TSS_CONVERT_UNMARSHAL(TPM2B_PUBLIC, X)
@@ -218,6 +224,7 @@ TSS_CONVERT_UNMARSHAL(TPMT_SIGNATURE, X)
 #define VAL(X) X
 #define VAL_2B(X, MEMBER) X.MEMBER
 #define VAL_2B_P(X, MEMBER) X->MEMBER
+#define VAL_T(X, MEMBER) X.MEMBER
 
 static const struct {
 	TPM_ALG_ID alg;
@@ -409,7 +416,6 @@ TSS_HMAC_Generate(TPMT_HA *digest, const TPM2B_KEY *hmacKey, ...)
 		OSSL_PARAM_construct_utf8_string("digest", TSS_GetDigestName(digest->hashAlg), 0),
 		OSSL_PARAM_construct_end()
 	};
-	fprintf(stderr, "HMAC\n");
 #endif
 	int length;
 	uint8_t *buffer;
@@ -1124,6 +1130,15 @@ tpm2_PolicySecret(TSS_CONTEXT *tssContext, TPM_HANDLE authHandle,
 	return rc;
 }
 
+static inline TPM_RC
+tpm2_PolicyOR(TSS_CONTEXT *tssContext, TPM_HANDLE policySession,
+	      TPML_DIGEST *pHashList)
+{
+	return Esys_PolicyOR(tssContext, policySession,
+			     ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE,
+			     pHashList);
+}
+
 static inline TPM_RC
 tpm2_PolicyGetDigest(TSS_CONTEXT *tssContext, TPM_HANDLE policySession,
 		     DIGEST_2B *digest)
@@ -1191,6 +1206,66 @@ tpm2_PCR_Read(TSS_CONTEXT *tssContext, TPML_PCR_SELECTION *pcrSelectionIn,
 	return rc;
 }
 
+static inline TPM_RC
+tpm2_Certify(TSS_CONTEXT *tssContext, TPM_HANDLE objectHandle,
+	     TPM_HANDLE signHandle, DATA_2B *qualifyingData,
+	     ATTEST_2B *certifyInfo, TPMT_SIGNATURE *signature)
+{
+	TPM_RC rc;
+	TPMT_SIG_SCHEME inScheme;
+	ATTEST_2B *a;
+	TPMT_SIGNATURE *s;
+	TPM2B_AUTH auth;
+
+	inScheme.scheme = TPM_ALG_NULL;
+
+	auth.size = 0;
+	Esys_TR_SetAuth(tssContext, objectHandle, &auth);
+	Esys_TR_SetAuth(tssContext, signHandle, &auth);
+
+	rc = Esys_Certify(tssContext, objectHandle, signHandle,
+			  ESYS_TR_PASSWORD, ESYS_TR_PASSWORD,
+			  ESYS_TR_NONE, qualifyingData, &inScheme,
+			  &a, &s);
+	if (rc)
+		return rc;
+
+	*certifyInfo = *a;
+	*signature = *s;
+
+	free(a);
+	free(s);
+
+	return rc;
+}
+
+static inline TPM_RC
+tpm2_ActivateCredential(TSS_CONTEXT *tssContext, TPM_HANDLE activateHandle,
+			TPM_HANDLE keyHandle,
+			const ID_OBJECT_2B *credentialBlob,
+			const ENCRYPTED_SECRET_2B *secret, DIGEST_2B *certinfo,
+			TPM_HANDLE authHandle)
+{
+	TPM_RC rc;
+	DIGEST_2B *cinfo;
+	TPM2B_AUTH auth;
+
+	auth.size = 0;
+	Esys_TR_SetAuth(tssContext, activateHandle, &auth);
+	Esys_TR_SetAuth(tssContext, keyHandle, &auth);
+	intel_sess_helper(tssContext, authHandle, TPMA_SESSION_ENCRYPT);
+	rc = Esys_ActivateCredential(tssContext, activateHandle, keyHandle,
+				     ESYS_TR_PASSWORD, authHandle, ESYS_TR_NONE,
+				     credentialBlob, secret, &cinfo);
+	if (rc)
+		return rc;
+
+	*certinfo = *cinfo;
+	free(cinfo);
+
+	return rc;
+}
+
 static inline TPM_HANDLE
 tpm2_handle_ext(TSS_CONTEXT *tssContext, TPM_HANDLE esysh)
 {
-- 
2.35.3


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 6/8] tools: add new attest_tpm2_primary command
  2024-08-02 20:25 [PATCH 0/8] openssl_tpm2_engine: Add attestation functions for primary keys James Bottomley
                   ` (4 preceding siblings ...)
  2024-08-02 20:26 ` [PATCH 5/8] tss: add tpm2_Certify, tpm2_ActivateCredential and tpm2_PolicyOR James Bottomley
@ 2024-08-02 20:26 ` James Bottomley
  2024-08-02 20:26 ` [PATCH 7/8] attest_tpm2_primary: add man page James Bottomley
  2024-08-02 20:26 ` [PATCH 8/8] tests: add tests for attest_tpm2_primary James Bottomley
  7 siblings, 0 replies; 20+ messages in thread
From: James Bottomley @ 2024-08-02 20:26 UTC (permalink / raw)
  To: openssl-tpm2-engine; +Cc: linux-integrity, Jarkko Sakkinen

This command can be used for three things which allow a system to
build an ongoing trust relationship with the TPM.  For TPMs which
don't have EK certificates (most fTPMs) it allows a trust on first use
model where the EK is squirreled away in a permanent location on the
filesystem as:

attest_tpm2_primary --ek > /etc/eksign.name

Which generates a signing EK that can be used to certify other objects
and permanently stores the name in /etc (ideally this should be stored
in an immutable location on OS install).

If the TPM does have a signing certificate, the next step is to verify
the cert back to the manufacturer and bind it to the signing EK by
doing

attest_tpm2_primary --attest --name /etc/eksign.name <ek cert file>

This will run a local makecredential/activatecredential on the signing
EK using the public key in the <ek cert file>.  Once this happens the
TPM is proven to be a genuine discrete TPM.

Finally, having the permanent name file allows the signing EK to
certify the NULL key used by the kernel on every boot via

attest_tpm2_primary --certify null --name /etc/eksign.name /sys/class/tpm/tpm0/null_name

Since the null_name changes on every boot this allows a user
confidence that the TPM booted up correctly and isn't being snooped.

Additionally, the command can generate the public SRK for importable
keys by running a certification against the signing EK to verify it
isn't being spoofed:

attest_tpm2_primary --certify owner --name /etc/eksign.name --file srk.pub

Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
---
 src/tools/Makefile.am           |   6 +-
 src/tools/attest_tpm2_primary.c | 842 ++++++++++++++++++++++++++++++++
 2 files changed, 847 insertions(+), 1 deletion(-)
 create mode 100644 src/tools/attest_tpm2_primary.c

diff --git a/src/tools/Makefile.am b/src/tools/Makefile.am
index 572847c..7cca442 100644
--- a/src/tools/Makefile.am
+++ b/src/tools/Makefile.am
@@ -12,7 +12,7 @@ endif
 AM_CPPFLAGS = -I ../include ${DEPRECATION}
 
 bin_PROGRAMS=create_tpm2_key load_tpm2_key seal_tpm2_data unseal_tpm2_data \
-	signed_tpm2_policy
+	signed_tpm2_policy attest_tpm2_primary
 COMMONLIB = ../libcommon/libcommon.a
 
 create_tpm2_key_SOURCES=create_tpm2_key.c
@@ -35,6 +35,10 @@ signed_tpm2_policy_SOURCES=signed_tpm2_policy.c
 signed_tpm2_policy_LDADD=${COMMONLIB} ${DEPS_LIBS}
 signed_tpm2_policy_CFLAGS=${DEPS_CFLAGS}
 
+attest_tpm2_primary_SOURCES=attest_tpm2_primary.c
+attest_tpm2_primary_LDADD=${COMMONLIB} ${DEPS_LIBS}
+attest_tpm2_primary_CFLAGS=${DEPS_CFLAGS}
+
 $(builddir)/%.1: $(srcdir)/%.1.in $(builddir)/%
 	$(HELP2MAN) --no-info -i $< -o $@ $(builddir)/$*
 
diff --git a/src/tools/attest_tpm2_primary.c b/src/tools/attest_tpm2_primary.c
new file mode 100644
index 0000000..cb252fe
--- /dev/null
+++ b/src/tools/attest_tpm2_primary.c
@@ -0,0 +1,842 @@
+/*
+ *
+ *   Copyright (C) 2024 James Bottomley <James.Bottomley@HansenPartnership.com>
+ *
+ *   SPDX-License-Identifier: LGPL-2.1-only
+ */
+
+
+#include <stdio.h>
+#include <getopt.h>
+#include <string.h>
+#include <strings.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#include <openssl/rsa.h>
+#include <openssl/pem.h>
+#include <openssl/evp.h>
+#include <openssl/err.h>
+#include <openssl/pkcs12.h>
+#include <openssl/rand.h>
+#include <openssl/x509.h>
+
+#include "tpm2-tss.h"
+#include "tpm2-asn.h"
+#include "tpm2-common.h"
+
+/*
+ *
+ * --eksign [--name <namefile>] [--file <file>]
+ *
+ * create P-256 EK signing key and check the name against <namefile>
+ * If name check is requested and passes, output the pem public key to <file>.
+ *
+ * --attest  <certfile> [--name <namefile>] [--file <file>]
+ *
+ * derive the EK signing key and verify its name against <namefile>
+ * (if present).  Then attest it as a signing key against the EK
+ * certificate in <certfile> using makecredential/activate credential.
+ * Optionally output the pub pem to <file> if the certification
+ * works.
+ *
+ * --certify <h> --name <ekname> [--outname] [--file <file>] [<hname>]
+ *
+ * derive the storage primary in hierarchy <h> and validate the name
+ * against that in the <hname> file if present.  If it validates (or
+ * hname is not present), derive the EK P-256 signing key and validate
+ * the EK name is <ekname>. Finally, certify the key derived from <h>
+ * using EK which proves the TPM knows the secret part of EK if the
+ * certification signature verifies and that <h> is genuine.  If the
+ * signature verifies, return success and optionally output the PEM
+ * form of the public key of <h> to <file> and print the name.
+ *
+ */
+
+static struct option long_options[] = {
+	{"help", 0, 0, 'h'},
+	{"auth", 1, 0, 'a'},
+	{"password", 1, 0, 'k'},
+	{"eksign", 0, 0, 'E'},
+	{"attest", 1, 0, 'A'},
+	{"certify", 1, 0, 'C'},
+	{"name", 1, 0, 'n'},
+	{"outname", 0, 0, 'o'},
+	{"file", 1, 0, 'f'},
+	{0, 0, 0, 0}
+};
+
+void
+usage(char *argv0)
+{
+	fprintf(stdout, "Usage: %s {--eksign|--attest|--certify} [options]\n\n"
+		"Options:\n"
+		"\t-E, --eksign                  construct a restricted signing EK and\n"
+		"\t                              output its name in hex if no other options\n"
+		"\t                              are given.\n"
+		"\t-A, --attest <certfile>       Attest the ek signing key based on the EK\n"
+		"\t                              certificate (in <certfile>) to prove it\n"
+		"\t                              is genuine.\n"
+		"\t-C, --certify <handle>        construct the restricted storage key for\n"
+		"\t                              <handle> and certify it against the EK\n"
+		"\t                              signing key.  The name file for the EK\n"
+		"\t                              signing key must be provided.\n"
+		"\t-h, --help                    print this help message.\n"
+		"\t-a, --auth                    provide EK authorization.\n"
+		"\t-k, --password <pwd>          use this password instead of prompting.\n"
+		"\t-n, --name <file>             force the checking of the constructed\n"
+		"\t                              hierarchy signing key against the name\n"
+		"\t                              in the give file.  Fail if they do not\n"
+		"\t                              match.\n"
+		"\t-f, --file <file>             Output the constructed hierarchy signing\n"
+		"\t                              or storage public key as a PEM file\n"
+		"\t                              suitable for import or external\n"
+		"\t                              cryptosystem use.\n"
+		"\t-o, --outname                 Print out the name of the certified key\n"
+		"\n"
+		"Report bugs to " PACKAGE_BUGREPORT "\n",
+		argv0);
+	exit(-1);
+}
+
+static int verify_hexname(const char *namefile, char hexname[MAX_HEXNAME])
+{
+	int fd;
+	struct stat st;
+	const int len = strlen(hexname);
+	int rc = NOT_TPM_ERROR;
+	char name[MAX_HEXNAME];
+
+	if (stat(namefile, &st) == -1) {
+		fprintf(stderr, "File %s cannot be accessed\n", namefile);
+		return rc;
+	}
+
+	/* sysfs null name is always 4096 to stat, so only check for too small */
+	if (st.st_size < len) {
+		fprintf(stderr, "Name file is too small should be at least %d\n",
+			len);
+		return rc;
+	}
+
+	fd = open(namefile, O_RDONLY);
+	if (fd < 0) {
+		perror("namefile can't be opened");
+		return rc;
+	}
+
+	if (!read(fd, name, len)) {
+		perror("namefile can't be read");
+		goto out_close;
+	}
+
+	if (memcmp(name, hexname, len) == 0)
+		rc = 0;
+ out_close:
+	close(fd);
+
+	return rc;
+}
+
+static int write_pubkey(const char *file, TPMT_PUBLIC *pub)
+{
+	BIO *bf;
+	int rc;
+	EVP_PKEY *pkey = tpm2_to_openssl_public(pub);
+
+	if (!pkey)
+		goto openssl_err;
+	bf = BIO_new_file(file, "w");
+	if (!bf)
+		goto openssl_err;
+	rc = PEM_write_bio_PUBKEY(bf, pkey);
+	BIO_free(bf);
+	if (!rc)
+		goto openssl_err;
+
+	EVP_PKEY_free(pkey);
+	return 0;
+
+ openssl_err:
+	ERR_print_errors_fp(stderr);
+	if (pkey)
+		EVP_PKEY_free(pkey);
+	return NOT_TPM_ERROR;
+}
+
+/* analogue of tpm2_sign_digest from tpm2-common.c */
+static TPM_RC tpm2_verify_digest(EVP_PKEY *pkey, TPMT_HA *digest,
+				 TPMT_SIGNATURE *sig)
+{
+	EVP_PKEY_CTX *ctx;
+	ECDSA_SIG *esig;
+	BIGNUM *r, *s;
+	unsigned char *p = NULL;
+	int p_len;
+	TPM_RC rc = NOT_TPM_ERROR;
+
+	ctx = EVP_PKEY_CTX_new(pkey, NULL);
+	if (!ctx)
+		goto openssl_err;
+	esig = ECDSA_SIG_new();
+	if (!esig)
+		goto openssl_ctx;
+
+	r = BN_bin2bn(VAL_2B(sig->signature.ecdsa.signatureR, buffer),
+		      VAL_2B(sig->signature.ecdsa.signatureR, size), NULL);
+	s = BN_bin2bn(VAL_2B(sig->signature.ecdsa.signatureS, buffer),
+		      VAL_2B(sig->signature.ecdsa.signatureS, size), NULL);
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000
+	esig->r = r;
+	esig->s = s;
+#else
+	ECDSA_SIG_set0(esig, r, s);
+#endif
+
+	p_len = i2d_ECDSA_SIG(esig, &p);
+	ECDSA_SIG_free(esig);
+
+	EVP_PKEY_verify_init(ctx);
+
+	if (EVP_PKEY_verify(ctx, p, p_len, (unsigned char *)&digest->digest,
+			    SHA256_DIGEST_LENGTH) == 1)
+		rc = 0;
+
+	OPENSSL_free(p);
+
+ openssl_ctx:
+	EVP_PKEY_CTX_free(ctx);
+
+ openssl_err:
+	if (rc)
+		ERR_print_errors_fp(stderr);
+	return rc;
+}
+
+void do_certify(const char *auth, TPM_HANDLE handle, const char *namefile,
+		const char *file, const char *hname, int outname)
+{
+	TPM_HANDLE h, ek;
+	TPM2B_PUBLIC pubh, pubek;
+	char hexname[MAX_HEXNAME];
+	const char *dir = tpm2_set_unique_tssdir();
+	TSS_CONTEXT *tssContext;
+	int rc;
+	DATA_2B qualifying;
+	ATTEST_2B att;
+	TPMT_SIGNATURE sig;
+	EVP_PKEY *pkey;
+	TPMT_HA digest;
+	TPMS_ATTEST a;
+	NAME_2B tmpname;
+
+	if (!namefile) {
+		fprintf(stderr, "signing EK name must be specified in --name argument\n");
+		exit(1);
+	}
+
+	rc = tpm2_create(&tssContext, dir);
+	if (rc)
+		goto out_rmdir;
+
+	handle = tpm2_handle_int(tssContext, handle);
+
+	/*
+	 * FIXME: Assumption here is that only the endorsement
+	 * hierarchy may have an authority password.  However, this
+	 * could be true of the owner hierarchy as well so might need
+	 * an additional password parameter.
+	 */
+	rc = tpm2_load_srk(tssContext, &h, NULL, &pubh, handle,
+			   TPM2_LOADABLE);
+	if (rc)
+		goto out_delete;
+
+	if (hname) {
+		tpm2_get_hexname(hexname, &pubh);
+		rc = verify_hexname(hname, hexname);
+		if (rc) {
+			fprintf(stderr, "Error: Handle for certification failed name verification\n");
+			goto out_free_h;
+		}
+	}
+
+	rc = tpm2_load_srk(tssContext, &ek, auth, &pubek, TPM_RH_ENDORSEMENT,
+			   TPM2_SIGNING);
+	if (rc)
+		goto out_free_h;
+
+	tpm2_get_hexname(hexname, &pubek);
+	rc = verify_hexname(namefile, hexname);
+	if (rc) {
+		fprintf(stderr, "Error: signing EK failed name verification\n");
+			goto out_free_ek;
+	}
+	qualifying.size = SHA256_DIGEST_LENGTH;
+	RAND_bytes(qualifying.buffer, qualifying.size);
+
+	rc = tpm2_Certify(tssContext, h, ek, &qualifying, &att, &sig);
+	if (rc) {
+		tpm2_error(rc, "TPM2_Certify");
+		goto out_free_ek;
+	}
+
+	pkey = tpm2_to_openssl_public(&pubek.publicArea);
+	if (!pkey)
+		goto out_free_ek;
+
+	digest.hashAlg = TPM_ALG_SHA256;
+	TSS_Hash_Generate(&digest,
+			  att.size, att.attestationData,
+			  0, NULL);
+	rc = tpm2_verify_digest(pkey, &digest, &sig);
+	if (rc) {
+		fprintf(stderr, "verification of the cerification signature failed\n");
+		goto out_free_ek;
+	}
+
+	/*
+	 * just proving the signature isn't enough, we need to verify
+	 * the expected values in the attestation area to make sure
+	 * this isn't a replay and that an attacker hasn't returned to
+	 * us a genuinely signed and qualified attestation report
+	 * containing differet keys.
+	 */
+	{
+		BYTE *buffer = att.attestationData;
+		INT32 size = att.size;
+
+		rc = TPMS_ATTEST_Unmarshal(&a, &buffer, &size);
+	}
+	if (rc) {
+		tpm2_error(rc, "Unmarshalling attestation data");
+		goto out_free_ek;
+	}
+
+	rc = NOT_TPM_ERROR;
+
+	if (a.type != TPM_ST_ATTEST_CERTIFY) {
+		fprintf(stderr, "Error: Attestation isn't for certification\n");
+		goto out_free_ek;
+	}
+
+	if (memcmp(qualifying.buffer, VAL_2B(a.extraData, buffer),
+		   qualifying.size) != 0) {
+		fprintf(stderr, "Error: qualifying data is not correct\n");
+		goto out_free_ek;
+	}
+
+	{
+		/* construct qualifiedName for EK */
+		NAME_2B ekn;
+		BYTE buffer[sizeof(TPM_HANDLE)];
+		BYTE *buf = buffer;
+		UINT16 written =0;
+		INT32 size = sizeof(buffer);
+		const TPM_HANDLE perm_ek = EXT_TPM_RH_ENDORSEMENT;
+
+		tpm2_ObjectPublic_GetName(&ekn, &pubek.publicArea);
+		TSS_TPM_HANDLE_Marshal(&perm_ek, &written, &buf, &size);
+		digest.hashAlg = TPM_ALG_SHA256;
+		TSS_Hash_Generate(&digest,
+				  written, buffer,
+				  ekn.size, ekn.name,
+				  0, NULL);
+
+		/* copy the leading hash algorithm */
+		memcpy(tmpname.name, ekn.name, 2);
+		memcpy(&tmpname.name[2], (char *)&digest.digest,
+		       SHA256_DIGEST_LENGTH);
+
+		if (memcmp(VAL_T(a.qualifiedSigner, name),
+			   tmpname.name, VAL_T(a.qualifiedSigner, size)) != 0) {
+			fprintf(stderr, "Error: qualified signer does not match EK\n");
+			goto out_free_ek;
+		}
+	}
+
+	/* finally the certified key name */
+	tpm2_ObjectPublic_GetName(&tmpname, &pubh.publicArea);
+	if (memcmp(VAL_T(a.attested.certify.name, name),
+		   tmpname.name, tmpname.size) != 0) {
+		fprintf(stderr, "Error: certified object name  does not match\n");
+		goto out_free_ek;
+	}
+
+	if (outname) {
+		tpm2_get_hexname(hexname, &pubh);
+		printf("%s\n", hexname);
+	} else {
+		printf("Good certification from TPM at %lu.%04d reset count %u\n",
+		       a.clockInfo.clock/1000, (int)(a.clockInfo.clock%1000),
+		       a.clockInfo.resetCount);
+	}
+	rc = 0;
+	if (file)
+		rc = write_pubkey(file, &pubh.publicArea);
+
+ out_free_ek:
+	tpm2_flush_srk(tssContext, ek);
+ out_free_h:
+	tpm2_flush_srk(tssContext, h);
+ out_delete:
+	TSS_Delete(tssContext);
+ out_rmdir:
+	if (dir)
+		rmdir(dir);
+
+	if (rc)
+		exit(1);
+}
+
+void do_ek(const char *auth, const char *namefile, const char *file)
+{
+	const char *dir = tpm2_set_unique_tssdir();
+	TSS_CONTEXT *tssContext;
+	TPM_RC rc;
+	TPM_HANDLE h;
+	TPM2B_PUBLIC pub;
+	char hexname[MAX_HEXNAME];
+
+	if (file && !namefile) {
+		fprintf(stderr, "for output of public key, require namefile to verify\n");
+		exit(1);
+	}
+
+	rc = tpm2_create(&tssContext, dir);
+	if (rc)
+		goto out_rmdir;
+	rc = tpm2_load_srk(tssContext, &h, auth, &pub, TPM_RH_ENDORSEMENT,
+			   TPM2_SIGNING);
+	tpm2_flush_srk(tssContext, h);
+	if (rc)
+		goto out_delete;
+
+	tpm2_get_hexname(hexname, &pub);
+
+	if (namefile) {
+		rc = verify_hexname(namefile, hexname);
+		if (rc)
+			fprintf(stderr, "Error: signing EK failed name verification\n");
+	} else {
+		printf("%s\n", hexname);
+	}
+
+	if (file)
+		rc = write_pubkey(file, &pub.publicArea);
+
+ out_delete:
+	TSS_Delete(tssContext);
+ out_rmdir:
+	if (dir)
+		rmdir(dir);
+
+	if (rc)
+		exit(1);
+}
+
+/*
+ * Try to find a template to construct the EK matching pkey
+ */
+static const unsigned char tcgPolicy[][SHA256_DIGEST_LENGTH] = {
+	/* Policy A - Low */
+	{
+		0x83, 0x71, 0x97, 0x67, 0x44, 0x84, 0xB3, 0xF8,
+		0x1A, 0x90, 0xCC, 0x8D, 0x46, 0xA5, 0xD7, 0x24,
+		0xFD, 0x52, 0xD7, 0x6E, 0x06, 0x52, 0x0B, 0x64,
+		0xF2, 0xA1, 0xDA, 0x1B, 0x33, 0x14, 0x69, 0xAA,
+	},
+	/* Policy B - High */
+	{
+		0xca, 0x3d, 0x0a, 0x99, 0xa2, 0xb9, 0x39, 0x06,
+		0xf7, 0xa3, 0x34, 0x24, 0x14, 0xef, 0xcf, 0xb3,
+		0xa3, 0x85, 0xd4, 0x4c, 0xd1, 0xfd, 0x45, 0x90,
+		0x89, 0xd1, 0x9b, 0x50, 0x71, 0xc0, 0xb7, 0xa0,
+	},
+	/* Policy C - B is OR of A and C */
+	{
+		0x37, 0x67, 0xe2, 0xed, 0xd4, 0x3f, 0xf4, 0x5a,
+		0x3a, 0x7e, 0x1e, 0xae, 0xfc, 0xef, 0x78, 0x64,
+		0x3d, 0xca, 0x96, 0x46, 0x32, 0xe7, 0xaa, 0xd8,
+		0x2c, 0x67, 0x3a, 0x30, 0xd8, 0x63, 0x3f, 0xde,
+	},
+};
+
+void get_template(EVP_PKEY *pkey, TPMT_PUBLIC *tmpl, int high)
+{
+	tmpl->nameAlg = TPM_ALG_SHA256;
+	VAL(tmpl->objectAttributes) = TPMA_OBJECT_FIXEDTPM |
+		TPMA_OBJECT_FIXEDPARENT |
+		TPMA_OBJECT_SENSITIVEDATAORIGIN |
+		TPMA_OBJECT_ADMINWITHPOLICY |
+		TPMA_OBJECT_RESTRICTED |
+		TPMA_OBJECT_DECRYPT;
+	if (high)
+		VAL(tmpl->objectAttributes) |=
+			TPMA_OBJECT_USERWITHAUTH;
+	VAL_T(tmpl->authPolicy, size) = sizeof(tcgPolicy[high]);
+	memcpy(&VAL_T(tmpl->authPolicy, buffer), tcgPolicy[high],
+		       sizeof(tcgPolicy[high]));
+	switch (EVP_PKEY_id(pkey)) {
+	case EVP_PKEY_RSA:
+		tmpl->type = TPM_ALG_RSA;
+		tmpl->parameters.rsaDetail.symmetric.algorithm = TPM_ALG_AES;
+		tmpl->parameters.rsaDetail.symmetric.keyBits.aes = 128;
+		tmpl->parameters.rsaDetail.symmetric.mode.aes = TPM_ALG_CFB;
+		tmpl->parameters.rsaDetail.scheme.scheme = TPM_ALG_NULL;
+		tmpl->parameters.rsaDetail.scheme.details.anySig.hashAlg = 0;
+		tmpl->parameters.rsaDetail.keyBits = 2048;
+		tmpl->parameters.rsaDetail.exponent = 0;
+		if (high) {
+			VAL_T(tmpl->unique.rsa, size) = 0;
+		} else {
+			VAL_T(tmpl->unique.rsa, size) = 256;
+			memset(VAL_T(tmpl->unique.rsa, buffer), 0, 256);
+		}
+		break;
+	case EVP_PKEY_EC:
+		tmpl->type = TPM_ALG_ECC;
+		tmpl->parameters.eccDetail.symmetric.algorithm = TPM_ALG_AES;
+		tmpl->parameters.eccDetail.symmetric.keyBits.aes = 128;
+		tmpl->parameters.eccDetail.symmetric.mode.aes = TPM_ALG_CFB;
+		tmpl->parameters.eccDetail.scheme.scheme = TPM_ALG_NULL;
+		tmpl->parameters.eccDetail.scheme.details.anySig.hashAlg = 0;
+		tmpl->parameters.eccDetail.curveID = TPM_ECC_NIST_P256;
+		tmpl->parameters.eccDetail.kdf.scheme = TPM_ALG_NULL;
+		tmpl->parameters.eccDetail.kdf.details.mgf1.hashAlg = 0;
+		if (high) {
+			VAL_T(tmpl->unique.ecc.x, size) = 0;
+			VAL_T(tmpl->unique.ecc.y, size) = 0;
+		} else {
+			VAL_T(tmpl->unique.ecc.x, size) = 32;
+			memset(VAL_T(tmpl->unique.ecc.x, buffer), 0, 32);
+			VAL_T(tmpl->unique.ecc.y, size) = 32;
+			memset(VAL_T(tmpl->unique.ecc.y, buffer), 0, 32);
+		}
+		break;
+	default:
+		fprintf(stderr, "Unknown certificate key type\n");
+		exit(1);
+	}
+}
+
+int load_ek(TSS_CONTEXT *tssContext, TPM_HANDLE *h, int *highp,
+	    const char *auth, EVP_PKEY *pkey)
+{
+	TPM2B_PUBLIC tmpl, target, pub;
+	TPM_RC rc;
+	int high;
+
+	rc = openssl_to_tpm_public(&target, pkey);
+
+	for (high = 0; high < 2; high++) {
+		get_template(pkey, &tmpl.publicArea, high);
+
+		rc = tpm2_load_srk_tmpl(tssContext, h, auth, &tmpl, &pub,
+					TPM_RH_ENDORSEMENT);
+		if (rc)
+			return rc;
+
+		if (target.publicArea.type == TPM_ALG_RSA &&
+		    memcmp(VAL_T(target.publicArea.unique.rsa, buffer),
+			   VAL_T(pub.publicArea.unique.rsa, buffer),
+			   VAL_T(target.publicArea.unique.rsa, size)) == 0)
+			break;
+		else if (target.publicArea.type == TPM_ALG_ECC &&
+			 memcmp(VAL_T(target.publicArea.unique.ecc.x, buffer),
+				VAL_T(pub.publicArea.unique.ecc.x, buffer),
+				VAL_T(target.publicArea.unique.ecc.x, size)) == 0
+			 &&
+			 memcmp(VAL_T(target.publicArea.unique.ecc.x, buffer),
+				VAL_T(pub.publicArea.unique.ecc.x, buffer),
+				VAL_T(target.publicArea.unique.ecc.x, size)) == 0)
+			break;
+
+		tpm2_flush_srk(tssContext, *h);
+		*h = 0;
+	}
+
+	if (*h == 0) {
+		fprintf(stderr, "Error: Can't find EK matching certificate\n");
+		return TPM_RC_ASYMMETRIC;
+	}
+	*highp = high;
+
+	return rc;
+}
+
+void do_attest(const char *auth, const char *namefile, const char *file,
+	       const char *certfile)
+{
+	const char *dir = tpm2_set_unique_tssdir();
+	TSS_CONTEXT *tssContext;
+	BIO *bf;
+	X509 *x;
+	EVP_PKEY *pkey;
+	NAME_2B eksignname;
+	PRIVATE_2B private;
+	ENCRYPTED_SECRET_2B enc_secret;
+	uint8_t challenge[SHA256_DIGEST_LENGTH];
+	TPM_HANDLE ek, eksign;
+	TPM_RC rc = NOT_TPM_ERROR;
+	TPM2B_PUBLIC pubeksign;
+	char hexname[MAX_HEXNAME];
+	DIGEST_2B digest;
+	const int integrity_skip = SHA256_DIGEST_LENGTH + 2;
+	TPM_HANDLE policy;
+	BYTE *buf;
+	INT32 size;
+	UINT16 written = 0;
+	int high;
+
+	bf = BIO_new_file(certfile, "r");
+	if (!bf)
+		goto out;
+
+	x = PEM_read_bio_X509(bf, NULL, NULL, NULL);
+	if (!x) {
+		BIO_reset(bf);
+		x = d2i_X509_bio(bf, NULL);
+	}
+	BIO_free(bf);
+	if (!x) {
+		X509_free(x);
+		goto out;
+	}
+	ERR_clear_error();
+	pkey = X509_get_pubkey(x);
+	X509_free(x);
+	if (!pkey)
+		goto out;
+
+	RAND_bytes(challenge, sizeof(challenge));
+
+
+	rc = tpm2_create(&tssContext, dir);
+	if (rc)
+		goto out;
+
+	rc = tpm2_load_srk(tssContext, &eksign, auth, &pubeksign,
+			   TPM_RH_ENDORSEMENT, TPM2_SIGNING);
+	if (rc)
+		goto out;
+
+	tpm2_ObjectPublic_GetName(&eksignname, &pubeksign.publicArea);
+
+	if (namefile) {
+		bin2hex(hexname, (unsigned char *)eksignname.name,
+			eksignname.size);
+		rc = verify_hexname(namefile, hexname);
+		if (rc) {
+			fprintf(stderr, "Error: signing EK failed name verification\n");
+			goto out_free_eksign;
+		}
+	}
+
+	/* run MakeCredential using just openssl */
+	memcpy(digest.buffer, challenge, sizeof(challenge));
+	digest.size = sizeof(challenge);
+	size = digest.size + 2;	/* size of marshalled digest */
+	private.size = size + integrity_skip;
+	buf = &private.buffer[integrity_skip];
+	TSS_TPM2B_DIGEST_Marshal((TPM2B_DIGEST *)&digest, &written,
+				 &buf, &size);
+	rc = tpm2_hmacwrap(pkey, &eksignname, "IDENTITY", &private, &enc_secret);
+	if (rc)
+		goto out_free_eksign;
+
+	rc = load_ek(tssContext, &ek, &high, auth, pkey);
+	if (rc)
+		goto out_free_eksign;
+
+	/* now we have the EK, we can only use it with a policy */
+
+	rc = tpm2_get_session_handle(tssContext, &policy, 0, TPM_SE_POLICY,
+				     TPM_ALG_SHA256);
+	if (rc)
+		goto out_free_ek;
+
+	/*
+	 * two different policies but amounting to the same thing
+	 *
+	 * low is policy A which is PolicySecret on the endorsement
+	 * hierarchy with a zero size policyRef.
+	 *
+	 * High is the PolicyOR of policy A (which we should satisfy)
+	 * and policyC (which we don't bother with because it's not
+	 * supported by all TPMs).
+	 */
+	digest.size = 0;	/* empty policyRef */
+	rc = tpm2_PolicySecret(tssContext, TPM_RH_ENDORSEMENT, policy,
+			       &digest, auth);
+	if (rc) {
+		tpm2_error(rc, "tpm2_PolicySecret");
+		tpm2_FlushContext(tssContext, policy);
+		goto out_free_ek;
+	}
+
+	if (high) {
+		/* high is PolicyOR of PolicyA and PolicyC */
+		TPML_DIGEST digests;
+
+		digests.count = 2;
+		/* PolicyA = tcgPolicy[0] */
+		VAL_T(digests.digests[0], size) = sizeof(tcgPolicy[0]);
+		memcpy(VAL_T(digests.digests[0], buffer), tcgPolicy[0],
+		       VAL_T(digests.digests[0], size));
+		/* PolicyC = tcgPolicy[2] */
+		VAL_T(digests.digests[1], size) = sizeof(tcgPolicy[2]);
+		memcpy(VAL_T(digests.digests[1], buffer), tcgPolicy[2],
+		       VAL_T(digests.digests[1], size));
+
+		rc = tpm2_PolicyOR(tssContext, policy, &digests);
+		if (rc) {
+			tpm2_error(rc, "tpm2_PolicyOR");
+			tpm2_FlushContext(tssContext, policy);
+			goto out_free_ek;
+		}
+	}
+
+	rc = tpm2_ActivateCredential(tssContext, eksign, ek,
+				     (ID_OBJECT_2B *)&private, &enc_secret,
+				     &digest, policy);
+	if (rc) {
+		tpm2_error(rc, "ActivateCredential");
+		tpm2_FlushContext(tssContext, policy);
+		goto out_free_ek;
+	}
+
+	if (memcmp(digest.buffer, challenge, sizeof(challenge)) != 0) {
+		fprintf(stderr, "Error: ActivateCredential returned incorrect challenge\n");
+		goto out_free_ek;
+	}
+
+	rc = 0;
+	printf("Attestation of signing EK successful\n");
+
+	if (file)
+		rc = write_pubkey(file, &pubeksign.publicArea);
+
+ out_free_ek:
+	tpm2_flush_srk(tssContext, ek);
+ out_free_eksign:
+	tpm2_flush_srk(tssContext, eksign);
+
+	TSS_Delete(tssContext);
+
+ out:
+	if (dir)
+		rmdir(dir);
+
+	if (ERR_peek_error()) {
+		rc = NOT_TPM_ERROR;
+		ERR_print_errors_fp(stderr);
+	}
+	if (rc)
+		exit(1);
+}
+
+int main(int argc, char **argv)
+{
+	char *auth = NULL, *pass = NULL;
+	char *file = NULL, *namefile = NULL;
+	char *handle_str = NULL, *certfile = NULL;
+	int ek = 0, outname = 0;
+	TPM_HANDLE handle;
+	const int max_auth_size = 128;
+#define ATTEST (certfile ? 1 : 0)
+#define CERTIFY (handle_str ? 1 : 0)
+
+
+	while (1) {
+		int option_index = 0, c;
+
+		c = getopt_long(argc, argv, "ak:EA:C:n:f:o",
+				long_options, &option_index);
+		if (c == -1)
+			break;
+
+		switch (c) {
+		case 'a':
+			auth = malloc(max_auth_size + 1);
+			break;
+		case 'k':
+			pass = optarg;
+			if (strlen(pass) > max_auth_size) {
+				fprintf(stderr, "password is too long\n");
+				exit(1);
+			}
+			break;
+		case 'E':
+			ek = 1;
+			break;
+		case 'A':
+			certfile = optarg;
+			break;
+		case 'C':
+			handle_str = optarg;
+			break;
+		case 'n':
+			namefile = optarg;
+			break;
+		case 'f':
+			file = optarg;
+			break;
+		case 'h':
+			usage(argv[0]);
+			break;
+		case 'o':
+			outname = 1;
+			break;
+		default:
+			printf("Unknown option '%c'\n", c);
+			usage(argv[0]);
+			break;
+		}
+	}
+
+	if (optind < argc - 1 || ((ek || ATTEST) && optind != argc)) {
+		printf("Unexpected additional arguments\n");
+		usage(argv[0]);
+	}
+
+	if (ek + ATTEST + CERTIFY > 1) {
+		fprintf(stderr, "only one of --ek, --attest or --certify may be specified\n");
+		exit(1);
+	} else if (ek + ATTEST + CERTIFY == 0) {
+		fprintf(stderr, "at least one of --ek, --attest or --certify must be specified\n");
+		exit(1);
+	}
+
+	if (auth) {
+		if (pass)
+			strcpy(auth, pass);
+		else
+			EVP_read_pw_string(auth, max_auth_size,
+					   "Enter EK hierarchy authority: ", 0);
+	}
+
+	if (ek) {
+		do_ek(auth, namefile, file);
+	} else if (certfile) {
+		do_attest(auth, namefile, file, certfile);
+	} else if (handle_str) {
+		const char *handlename = NULL;
+
+		handle = tpm2_get_parent_ext(handle_str);
+		if (!handle) {
+			fprintf(stderr, "Invalid handle for certification\n");
+			exit(1);
+		}
+
+		if (optind == argc - 1)
+			handlename = argv[argc - 1];
+		do_certify(auth, handle, namefile, file, handlename, outname);
+	}
+}
-- 
2.35.3


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 7/8] attest_tpm2_primary: add man page
  2024-08-02 20:25 [PATCH 0/8] openssl_tpm2_engine: Add attestation functions for primary keys James Bottomley
                   ` (5 preceding siblings ...)
  2024-08-02 20:26 ` [PATCH 6/8] tools: add new attest_tpm2_primary command James Bottomley
@ 2024-08-02 20:26 ` James Bottomley
  2024-08-02 20:26 ` [PATCH 8/8] tests: add tests for attest_tpm2_primary James Bottomley
  7 siblings, 0 replies; 20+ messages in thread
From: James Bottomley @ 2024-08-02 20:26 UTC (permalink / raw)
  To: openssl-tpm2-engine; +Cc: linux-integrity, Jarkko Sakkinen

Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
---
 src/tools/Makefile.am              |   5 +-
 src/tools/attest_tpm2_primary.1.in | 103 +++++++++++++++++++++++++++++
 2 files changed, 106 insertions(+), 2 deletions(-)
 create mode 100644 src/tools/attest_tpm2_primary.1.in

diff --git a/src/tools/Makefile.am b/src/tools/Makefile.am
index 7cca442..3012411 100644
--- a/src/tools/Makefile.am
+++ b/src/tools/Makefile.am
@@ -1,9 +1,10 @@
 if NATIVE_BUILD
 EXTRA_DIST = create_tpm2_key.1 load_tpm2_key.1 seal_tpm2_data.1 \
-	unseal_tpm2_data.1 signed_tpm2_policy.1 openssl_tpm2_engine.7
+	unseal_tpm2_data.1 signed_tpm2_policy.1 openssl_tpm2_engine.7 \
+	attest_tpm2_primary.1
 
 man1_MANS = create_tpm2_key.1 load_tpm2_key.1 seal_tpm2_data.1 \
-	unseal_tpm2_data.1 signed_tpm2_policy.1
+	unseal_tpm2_data.1 signed_tpm2_policy.1 attest_tpm2_primary.1
 man7_MANS = openssl_tpm2_engine.7
 
 CLEANFILES = $(man1_MANS)
diff --git a/src/tools/attest_tpm2_primary.1.in b/src/tools/attest_tpm2_primary.1.in
new file mode 100644
index 0000000..59476f7
--- /dev/null
+++ b/src/tools/attest_tpm2_primary.1.in
@@ -0,0 +1,103 @@
+[name]
+attest_tpm2_primary - perform certification and attestation operations for primary keys
+
+[description]
+
+TPMs have a complex set of commands for verifying primary keys.  Any
+TPM created signing key can be used to produce a "certification" of
+another key (a signed proof that key is present in the TPM).  However,
+the way this signing key is generated from a TPM X.509 certificate
+involves a complicated challenge/response round trip.  This tool is
+designed to present a simple way to perform the mechanics of these
+commands.
+
+[threat model]
+
+TPMs are vulnerable to man in the middle type attacks known as
+interposer attacks.  The first line of defence against them is to use
+TPM sessions for encryption and HMAC checking.  However, even after
+this is done, several other possible attacks remain including a reset
+based attack and a public key deception attack.  For more details see
+the Linux Kernel TPM security document:
+
+https://docs.kernel.org/security/tpm/tpm-security.html
+
+Public key deception is a problem because when salting sessions most
+TPM applications simply ask the TPM for a public key to encrypt the
+salt to.  So, if the interposer returns a key of its choosing, to
+which it has the private part, it can intercept and decrypt the
+session salt (and re-encrypt it with the correct key to pass on to the
+underlying TPM), significantly reducing or eliminating the security
+provided by sessions.  The solution to this problem is to verify the
+TPM provided key before it is used.
+
+[Attestation Keys]
+
+The original design of the TPM was to derive many disposable
+attestation keys (AKs) to frustrate tracking when used online.  This
+scheme involved a trusted PrivacyCA which would take the TPM EK,
+certificate and Attestation Key and return an Attestation Key
+Certificate if it all checked out.  The way this worked is that the
+PrivacyCA would construct a packet that could only be decrypted by a
+TPM2_ActivateCredential command, which involved a decryption operation
+that would only succeed if the TPM possessed the private parts of both
+the EK and the AK.  If this succeeded, the TPM could return the
+decrypted challenge to the PrivacyCA which would then issue the
+certificate.
+
+Unfortunately, no PrivacyCA was ever stood up and the threat model
+above really requires us to verify the TPM locally (so no privacy
+issues are involved).  The quick fix is to get the TPM to derive a
+signing EK key and attest it once with the TPMs EK certificate using
+the MakeCredential/ActivateCredential round trip locally.
+
+Ideally the name (unique hash) of this signing key should be
+permanently stored in the filesystem, say at /etc/eksign.name for use
+across boots.  Since this signing key is derived from the endorsement
+seed, which never changes even across TPM ownership changes it should
+be stable.
+
+For TPMs which don't have attestation certificates, this key should be
+collected when a laptop is first powered on with:
+
+  $ attest_tpm2_primary --eksign > /etc/eksign.name
+
+Which will derive the signing key and output it's name.
+
+If you do have an attestation certificate for your TPM, you should
+verify this signing key using the MakeCredential/ActivateCredential
+sequence thus:
+
+  $ attest_tpm2_primary --attest tpm-certificate.crt \\
+      --name /etc/eksign.name
+
+You should also verify tpm-certificate.crt chains back to the
+manufacturer.
+
+[kernel TPM verification]
+
+From version 6.10 onward, the Linux kernel uses sessions encrypted to
+the TPM NULL key to defeat interposer reset attacks.  Since the kernel
+exports the name of the NULL key it found, you can certify this key
+against your signing EK on every boot to be sure of the fidelity of
+the boot.
+
+  $ attest_tpm2_primary --certify null --name /etc/eksign.name \\
+      /sys/class/tpm/tpm0/null_name
+
+Which can be done via a systemd or other init system script.
+
+[Secure Import key]
+
+For importable keys and sealed objects, you need to be completely sure
+that the parent public key is correct.  Since most objects are stored
+in the owner hierarchy under the Storage Root Key (SRK), you can
+generate a verified public key to give out as an import key using
+
+  $ attest_tpm2_primary --certify --owner --name /etc/eksign.name \\
+      --file srk.pub
+
+Which will generate a PEM public key corresponding to the storage root
+only if the public part of the storage key can be certified against
+the signing EK, which ensures an interposer didn't give you the wrong
+public key to use for import.
-- 
2.35.3


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 8/8] tests: add tests for attest_tpm2_primary
  2024-08-02 20:25 [PATCH 0/8] openssl_tpm2_engine: Add attestation functions for primary keys James Bottomley
                   ` (6 preceding siblings ...)
  2024-08-02 20:26 ` [PATCH 7/8] attest_tpm2_primary: add man page James Bottomley
@ 2024-08-02 20:26 ` James Bottomley
  7 siblings, 0 replies; 20+ messages in thread
From: James Bottomley @ 2024-08-02 20:26 UTC (permalink / raw)
  To: openssl-tpm2-engine; +Cc: linux-integrity, Jarkko Sakkinen

Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
---
 tests/attestation.sh       | 30 ++++++++++++++++++++++++++++++
 tests/check_importable.sh  |  3 +--
 tests/engine/Makefile.am   |  3 ++-
 tests/provider/Makefile.am |  3 ++-
 tests/seal_unseal.sh       |  3 +--
 tests/start_sw_tpm.sh      |  2 ++
 6 files changed, 38 insertions(+), 6 deletions(-)
 create mode 100755 tests/attestation.sh

diff --git a/tests/attestation.sh b/tests/attestation.sh
new file mode 100755
index 0000000..bd927fa
--- /dev/null
+++ b/tests/attestation.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+set -x
+
+##
+# We already created eksign.name and null.name, so check them first
+##
+${bindir}/attest_tpm2_primary --eksign --name ${testdir}/eksign.name || exit 1
+${bindir}/attest_tpm2_primary --eksign --name ${testdir}/null.name && exit 1
+${bindir}/attest_tpm2_primary --certify null --name ${testdir}/eksign.name ${testdir}/null.name || exit 1
+##
+# Run through certification of all the keys (already done null above
+##
+for h in owner endorsement platform; do
+    rm -f tmp.name
+    ${bindir}/attest_tpm2_primary -C ${h} -n ${testdir}/eksign.name -o > tmp.name || exit 1
+    ${bindir}/attest_tpm2_primary -C ${h} -n ${testdir}/eksign.name tmp.name || exit 1
+    ${bindir}/attest_tpm2_primary -C ${h} -n ${testdir}/eksign.name null.name && exit 1
+done
+##
+# attestation tests
+# 1. create both P-256 and RSA2048 attestation certs
+##
+openssl genrsa 2048 > ca.key || exit 1
+# several EK templates exist, so try RSA and EC for each
+for high in "" "-high"; do
+    for alg in "-rsa 2048" "-ecc nistp256"; do
+	tsscreateekcert ${high} ${alg} -cakey ca.key -of cert.der || exit 1
+	${bindir}/attest_tpm2_primary --attest cert.der --name ${testdir}/eksign.name || exit 1
+    done
+done
diff --git a/tests/check_importable.sh b/tests/check_importable.sh
index eeafe03..ee84f16 100755
--- a/tests/check_importable.sh
+++ b/tests/check_importable.sh
@@ -2,8 +2,7 @@
 
 
 # export the parent key as a EC and RSA public key
-prim=$(tsscreateprimary -ecc nistp256 -hi o -opem srk.pub | sed 's/Handle //') || exit 1
-tssflushcontext -ha ${prim} || exit 1
+${bindir}/attest_tpm2_primary --certify owner --name ${testdir}/eksign.name --file srk.pub || exit 1
 prim=$(tsscreateprimary -rsa 2048 -hi o -opem srkrsa.pub | sed 's/Handle //') || exit 1
 tssflushcontext -ha ${prim} || exit 1
 
diff --git a/tests/engine/Makefile.am b/tests/engine/Makefile.am
index ec6f321..7bade2b 100644
--- a/tests/engine/Makefile.am
+++ b/tests/engine/Makefile.am
@@ -30,6 +30,7 @@ TESTS += ../check_curves.sh \
 	../check_locality.sh \
 	../check_secret_policies.sh \
 	../dynamic_engine.sh \
+	../attestation.sh \
 	../stop_sw_tpm.sh
 
 fail_connect.sh: tpm_server_found
@@ -53,6 +54,6 @@ AM_TESTS_ENVIRONMENT = TPM_INTERFACE_TYPE=socsim; export TPM_INTERFACE_TYPE; \
 
 TEST_EXTENSIONS = .sh
 
-CLEANFILES = key*.tpm key*.pub key*.priv tmp.* NVChip h*.bin key*.der seal.* fifo tss2.*
+CLEANFILES = key*.tpm key*.pub key*.priv tmp.* NVChip h*.bin key*.der seal.* fifo tss2.* *.name
 clean-local:
 	rm -fr testdir
diff --git a/tests/provider/Makefile.am b/tests/provider/Makefile.am
index 1080036..05bbee1 100644
--- a/tests/provider/Makefile.am
+++ b/tests/provider/Makefile.am
@@ -31,6 +31,7 @@ TESTS += ../check_curves.sh \
 	../check_signed_policies.sh \
 	../check_locality.sh \
 	../check_secret_policies.sh \
+	../attestation.sh \
 	../stop_sw_tpm.sh
 
 fail_connect.sh: tpm_server_found
@@ -56,7 +57,7 @@ endif
 
 TEST_EXTENSIONS = .sh
 
-CLEANFILES = key*.tpm key*.pub key*.priv tmp.* NVChip h*.bin key*.der seal.* fifo tss2.*
+CLEANFILES = key*.tpm key*.pub key*.priv tmp.* NVChip h*.bin key*.der seal.* fifo tss2.* *.name
 clean-local:
 	rm -fr testdir
 
diff --git a/tests/seal_unseal.sh b/tests/seal_unseal.sh
index 6d05a4c..2df3aa8 100755
--- a/tests/seal_unseal.sh
+++ b/tests/seal_unseal.sh
@@ -48,8 +48,7 @@ for n in sha1 sha256 sha384; do
     else
 	POLICYFILE="${testdir}/policies/policy_pcr${n}.txt"
     fi
-    prim=$(tsscreateprimary -hi o -st -ecc nistp256 -opem srk.pub | sed 's/Handle //') || exit 1
-    tssflushcontext -ha $prim
+    ${bindir}/attest_tpm2_primary -C owner -n ${testdir}/eksign.name -f srk.pub || exit 1
     TPM_INTERFACE_TYPE= echo $DATA | ${bindir}/seal_tpm2_data -n ${n} -a -k ${AUTH} --import srk.pub seal.tpm || exit 1;
     ${bindir}/unseal_tpm2_data -k ${AUTH} seal.tpm | grep -q "${DATA}" || exit 1;
     rm seal.tpm
diff --git a/tests/start_sw_tpm.sh b/tests/start_sw_tpm.sh
index 5f249a5..1e0e4db 100755
--- a/tests/start_sw_tpm.sh
+++ b/tests/start_sw_tpm.sh
@@ -56,3 +56,5 @@ key=$(tsscreateprimary -hi o -st -rsa|sed 's/Handle //') && \
 tssevictcontrol -hi o -ho ${key} -hp 81000001 && \
 tssflushcontext -ha ${key}
 
+${bindir}/attest_tpm2_primary --ek > ${testdir}/eksign.name || exit 1
+${bindir}/attest_tpm2_primary --certify null --outname --name ${testdir}/eksign.name > ${testdir}/null.name || exit 1
-- 
2.35.3


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* Re: [PATCH 1/8] tss: Fix handling of TPM_RH_NULL in intel-tss
  2024-08-02 20:25 ` [PATCH 1/8] tss: Fix handling of TPM_RH_NULL in intel-tss James Bottomley
@ 2024-08-03 17:08   ` Jarkko Sakkinen
  2024-08-03 17:51     ` James Bottomley
  0 siblings, 1 reply; 20+ messages in thread
From: Jarkko Sakkinen @ 2024-08-03 17:08 UTC (permalink / raw)
  To: James Bottomley, openssl-tpm2-engine; +Cc: linux-integrity, Jarkko Sakkinen

On Fri Aug 2, 2024 at 11:25 PM EEST, James Bottomley wrote:
> Now that we're going to be using the NULL primary to salt sessions,
> the Intel TSS shim needs fixing to cope with this.  In the Intel TSS,
> there are two internal handles representing NULL: ESYS_TR_NONE and
> ESYS_TR_RH_NULL.  We translate TPM_RH_NULL to ESYS_TR_NONE because

Can you split this into two paragraphs.

I'm lost why it has two representations.

> most of the time it does mean no value.  However, for the NULL primary
> handle we must use ESYS_TR_RH_NULL, so check for that specific case
> and fix it.  Additionally remove the intel_handle() code which was
> supposed to do this: it's unused because 0 is never passed in as a
> handle number.
>
> Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
> ---
>  src/include/intel-tss.h | 18 +++++-------------
>  1 file changed, 5 insertions(+), 13 deletions(-)
>
> diff --git a/src/include/intel-tss.h b/src/include/intel-tss.h
> index 1870b4e..5b8db20 100644
> --- a/src/include/intel-tss.h
> +++ b/src/include/intel-tss.h
> @@ -251,14 +251,6 @@ intel_sess_helper(TSS_CONTEXT *tssContext, TPM_HANDLE auth, TPMA_SESSION flags)
>  				  TPMA_SESSION_CONTINUESESSION | flags);
>  }
>  
> -static inline TPM_HANDLE
> -intel_handle(TPM_HANDLE h)
> -{
> -	if (h == 0)
> -		return ESYS_TR_NONE;
> -	return h;
> -}
> -
>  static inline void
>  TSS_Delete(TSS_CONTEXT *tssContext)
>  {
> @@ -937,8 +929,10 @@ tpm2_CreatePrimary(TSS_CONTEXT *tssContext, TPM_HANDLE primaryHandle,
>  	TPM2B_PUBLIC *opub;
>  	TPM_RC rc;
>  
> -	/* FIXME will generate wrong value for NULL hierarchy */
> -	primaryHandle = intel_handle(primaryHandle);
> +
> +	/* TPM_RH_NULL is mapped to ESYS_TR_NONE, which won't work here */

I simply don't understand what E


> +	if (primaryHandle == TPM_RH_NULL)
> +		primaryHandle = INT_TPM_RH_NULL;
>  
>  	outsideInfo.size = 0;
>  	creationPcr.count = 0;
> @@ -993,9 +987,7 @@ tpm2_StartAuthSession(TSS_CONTEXT *tssContext, TPM_HANDLE tpmKey,
>  		      TPM_HANDLE *sessionHandle,
>  		      const char *bindPassword)
>  {
> -	bind = intel_handle(bind);
> -	tpmKey = intel_handle(tpmKey);
> -	if (bind != ESYS_TR_NONE)
> +	if (bind != TPM_RH_NULL)
>  		intel_auth_helper(tssContext, bind, bindPassword);
>  
>  	return Esys_StartAuthSession(tssContext, tpmKey, bind, ESYS_TR_NONE,


BR, Jarkko

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 3/8] libcommon: add bin2hex and tmp2_get_hexname
  2024-08-02 20:26 ` [PATCH 3/8] libcommon: add bin2hex and tmp2_get_hexname James Bottomley
@ 2024-08-03 17:21   ` Jarkko Sakkinen
  0 siblings, 0 replies; 20+ messages in thread
From: Jarkko Sakkinen @ 2024-08-03 17:21 UTC (permalink / raw)
  To: James Bottomley, openssl-tpm2-engine; +Cc: linux-integrity

On Fri Aug 2, 2024 at 11:26 PM EEST, James Bottomley wrote:
> Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
> ---
>  src/include/tpm2-common.h   |  5 +++++
>  src/libcommon/tpm2-common.c | 16 ++++++++++++++++
>  2 files changed, 21 insertions(+)

Would not hurt to introduce them in the commit message.

>
> diff --git a/src/include/tpm2-common.h b/src/include/tpm2-common.h
> index 97b60f2..0e0f28a 100644
> --- a/src/include/tpm2-common.h
> +++ b/src/include/tpm2-common.h
> @@ -9,6 +9,9 @@
>   * not a TPM error, so don't process the rc as one */
>  #define NOT_TPM_ERROR (0xffffffff)
>  
> +/* maximum space for a sha256 name in ascii */
> +#define MAX_HEXNAME 132
> +
>  extern TPM_ALG_ID name_alg;
>  
>  struct policy_command {
> @@ -141,4 +144,6 @@ int tpm2_rsa_decrypt(const struct app_data *ad, PUBLIC_KEY_RSA_2B *cipherText,
>  		     char *srk_auth);
>  int tpm2_rm_signed_policy(char *tpmkey, int rmnum);
>  int tpm2_get_signed_policy(char *tpmkey, STACK_OF(TSSAUTHPOLICY) **sk);
> +void bin2hex(char *dst, const unsigned char *src, size_t count);
> +void tpm2_get_hexname(char hexname[MAX_HEXNAME], TPM2B_PUBLIC *pub);
>  #endif
> diff --git a/src/libcommon/tpm2-common.c b/src/libcommon/tpm2-common.c
> index b70ac27..3ffa773 100644
> --- a/src/libcommon/tpm2-common.c
> +++ b/src/libcommon/tpm2-common.c
> @@ -2320,6 +2320,14 @@ int hex2bin(unsigned char *dst, const char *src, size_t count)
>  	return 0;
>  }
>  
> +void bin2hex(char *dst, const unsigned char *src, size_t count)
> +{
> +	int i;
> +
> +	for (i = 0; i < count; i++)
> +		sprintf(&dst[i<<1], "%02x", src[i]);
> +}
> +
>  TPM_RC tpm2_parse_policy_file(const char *policy_file,
>  			      STACK_OF(TSSOPTPOLICY) *sk,
>  			      char *auth, TPMT_HA *digest)
> @@ -3376,6 +3384,14 @@ openssl_print_errors()
>  	ERR_print_errors_fp(stderr);
>  }
>  
> +void tpm2_get_hexname(char hexname[MAX_HEXNAME], TPM2B_PUBLIC *pub)
> +{
> +	NAME_2B n;
> +
> +	tpm2_ObjectPublic_GetName(&n, &pub->publicArea);
> +	bin2hex(hexname, (unsigned char *)n.name, n.size);
> +}
> +
>  IMPLEMENT_ASN1_FUNCTIONS(TSSOPTPOLICY)
>  IMPLEMENT_ASN1_FUNCTIONS(TSSAUTHPOLICY)
>  IMPLEMENT_ASN1_FUNCTIONS(TSSLOADABLE)


BR, Jarkko

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 1/8] tss: Fix handling of TPM_RH_NULL in intel-tss
  2024-08-03 17:08   ` Jarkko Sakkinen
@ 2024-08-03 17:51     ` James Bottomley
  2024-08-03 19:31       ` Jarkko Sakkinen
  0 siblings, 1 reply; 20+ messages in thread
From: James Bottomley @ 2024-08-03 17:51 UTC (permalink / raw)
  To: Jarkko Sakkinen, openssl-tpm2-engine; +Cc: linux-integrity, Jarkko Sakkinen

On Sat, 2024-08-03 at 20:08 +0300, Jarkko Sakkinen wrote:
> On Fri Aug 2, 2024 at 11:25 PM EEST, James Bottomley wrote:
> > Now that we're going to be using the NULL primary to salt sessions,
> > the Intel TSS shim needs fixing to cope with this.  In the Intel
> > TSS, there are two internal handles representing NULL: ESYS_TR_NONE
> > and ESYS_TR_RH_NULL.  We translate TPM_RH_NULL to ESYS_TR_NONE
> > because
> 
> Can you split this into two paragraphs.
> 
> I'm lost why it has two representations.

Well, I actually have no idea why the Intel TSS has two representations
for *every* handle: an internal one (specific to the TSS) and an
external one that everyone uses, like 81000001 or 40000007. As far as I
can see it just adds pointless complexity to the coding.  The IBM TSS
only has one, so for code which works with both, the shim has to
transform between internal and external handle representations before
sending the command onward to the Intel TSS.

Even more mysteriously the Intel TSS has three representations for the
NULL handle: an internal one, an external one (40000007) and one you
use for an empty session (ESYS_TR_NONE).  The IBM TSS uses TPM_RH_NULL
for all three so you can't just translate from external to internal you
have to know if you're using the handle for a session or a hierarchy as
well.

James


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 1/8] tss: Fix handling of TPM_RH_NULL in intel-tss
  2024-08-03 17:51     ` James Bottomley
@ 2024-08-03 19:31       ` Jarkko Sakkinen
  2024-08-03 19:47         ` James Bottomley
  0 siblings, 1 reply; 20+ messages in thread
From: Jarkko Sakkinen @ 2024-08-03 19:31 UTC (permalink / raw)
  To: James Bottomley, Jarkko Sakkinen, openssl-tpm2-engine; +Cc: linux-integrity

On Sat Aug 3, 2024 at 8:51 PM EEST, James Bottomley wrote:
> On Sat, 2024-08-03 at 20:08 +0300, Jarkko Sakkinen wrote:
> > On Fri Aug 2, 2024 at 11:25 PM EEST, James Bottomley wrote:
> > > Now that we're going to be using the NULL primary to salt sessions,
> > > the Intel TSS shim needs fixing to cope with this.  In the Intel
> > > TSS, there are two internal handles representing NULL: ESYS_TR_NONE
> > > and ESYS_TR_RH_NULL.  We translate TPM_RH_NULL to ESYS_TR_NONE
> > > because
> > 
> > Can you split this into two paragraphs.
> > 
> > I'm lost why it has two representations.
>
> Well, I actually have no idea why the Intel TSS has two representations
> for *every* handle: an internal one (specific to the TSS) and an
> external one that everyone uses, like 81000001 or 40000007. As far as I
> can see it just adds pointless complexity to the coding.  The IBM TSS
> only has one, so for code which works with both, the shim has to
> transform between internal and external handle representations before
> sending the command onward to the Intel TSS.

Is it possible to address this complexity and move into a single
representation? I.e. use external presentation all the way.

>
> Even more mysteriously the Intel TSS has three representations for the
> NULL handle: an internal one, an external one (40000007) and one you
> use for an empty session (ESYS_TR_NONE).  The IBM TSS uses TPM_RH_NULL
> for all three so you can't just translate from external to internal you
> have to know if you're using the handle for a session or a hierarchy as
> well.

Same question applies to this too.

> James

I'm doing my own TPM2 stack with Rust, which just re-implements the
functionality of my old tpm2-scripts and tpm2.py module as nice small
app called tpm2-cli.

It will be more use case based interface than than protocol spec as
a software interface. Main goal for now is to get this whole flow
into it (with varying parameters for the key) as single function:

tpm2_createprimary --hierarchy o -G ecc -c owner.txt
tpm2_evictcontrol -c owner.txt 0x81000001
openssl ecparam -name prime256v1 -genkey -noout -out private.pem
tpm2_import -C 0x81000001 -G ecc -i private.pem -u key.pub -r key.priv
tpm2_encodeobject -C 0x81000001 -u key.pub -r key.priv -o key.priv.pem
openssl asn1parse -inform pem -in key.priv.pem -noout -out key.priv.der

BR, Jarkko

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 1/8] tss: Fix handling of TPM_RH_NULL in intel-tss
  2024-08-03 19:31       ` Jarkko Sakkinen
@ 2024-08-03 19:47         ` James Bottomley
  2024-08-03 20:43           ` Jarkko Sakkinen
  0 siblings, 1 reply; 20+ messages in thread
From: James Bottomley @ 2024-08-03 19:47 UTC (permalink / raw)
  To: Jarkko Sakkinen, Jarkko Sakkinen, openssl-tpm2-engine; +Cc: linux-integrity

On Sat, 2024-08-03 at 22:31 +0300, Jarkko Sakkinen wrote:
> On Sat Aug 3, 2024 at 8:51 PM EEST, James Bottomley wrote:
> > On Sat, 2024-08-03 at 20:08 +0300, Jarkko Sakkinen wrote:
> > > On Fri Aug 2, 2024 at 11:25 PM EEST, James Bottomley wrote:
> > > > Now that we're going to be using the NULL primary to salt
> > > > sessions, the Intel TSS shim needs fixing to cope with this. 
> > > > In the Intel TSS, there are two internal handles representing
> > > > NULL: ESYS_TR_NONE and ESYS_TR_RH_NULL.  We translate
> > > > TPM_RH_NULL to ESYS_TR_NONE because
> > > 
> > > Can you split this into two paragraphs.
> > > 
> > > I'm lost why it has two representations.
> > 
> > Well, I actually have no idea why the Intel TSS has two
> > representations for *every* handle: an internal one (specific to
> > the TSS) and an external one that everyone uses, like 81000001 or
> > 40000007. As far as I can see it just adds pointless complexity to
> > the coding.  The IBM TSS only has one, so for code which works with
> > both, the shim has to transform between internal and external
> > handle representations before sending the command onward to the
> > Intel TSS.
> 
> Is it possible to address this complexity and move into a single
> representation? I.e. use external presentation all the way.

Yes, that's what the current code does.  It began life as pure IBM TSS
so it used what the Intel TSS would consider as all external handle
representations.  The external to internal shift (and back) happens
inside the TSS shim.

> > Even more mysteriously the Intel TSS has three representations for
> > the NULL handle: an internal one, an external one (40000007) and
> > one you use for an empty session (ESYS_TR_NONE).  The IBM TSS uses
> > TPM_RH_NULL for all three so you can't just translate from external
> > to internal you have to know if you're using the handle for a
> > session or a hierarchy as well.
> 
> Same question applies to this too.

Remember this is just fixing the Intel TSS Shim.  The fact that we have
to use three different handles for NULL isn't visible outside the shim,
so a consumer of these APIs just uses TPM_RH_NULL everywhere.  The fix
is that the Intel TSS Shim was using the wrong handle for some
operations.

> 
> > James
> 
> I'm doing my own TPM2 stack with Rust, which just re-implements the
> functionality of my old tpm2-scripts and tpm2.py module as nice small
> app called tpm2-cli.
> 
> It will be more use case based interface than than protocol spec as
> a software interface. Main goal for now is to get this whole flow
> into it (with varying parameters for the key) as single function:
> 
> tpm2_createprimary --hierarchy o -G ecc -c owner.txt
> tpm2_evictcontrol -c owner.txt 0x81000001
> openssl ecparam -name prime256v1 -genkey -noout -out private.pem
> tpm2_import -C 0x81000001 -G ecc -i private.pem -u key.pub -r
> key.priv
> tpm2_encodeobject -C 0x81000001 -u key.pub -r key.priv -o
> key.priv.pem
> openssl asn1parse -inform pem -in key.priv.pem -noout -out
> key.priv.der

Well, that's simply the library API.  That's actually what the IBM TSS
uses.  I think the Library API is by far the simplest TPM API to use,
so beyond trying to sort out the complexity of getting a compatibility
shim I've not really used the Intel TSS.  I think the Intel TSS is
based on the ESAPI document which introduces the handle conversion
complexity.  I haven't read the ESAPI spec, I just tried to reverse
engineer it (from the source) into supporting an API which can be
compatible with the library spec/IBM TSS.

James


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 1/8] tss: Fix handling of TPM_RH_NULL in intel-tss
  2024-08-03 19:47         ` James Bottomley
@ 2024-08-03 20:43           ` Jarkko Sakkinen
  2024-08-04 13:42             ` [PATCH v2 " James Bottomley
  0 siblings, 1 reply; 20+ messages in thread
From: Jarkko Sakkinen @ 2024-08-03 20:43 UTC (permalink / raw)
  To: James Bottomley, Jarkko Sakkinen, openssl-tpm2-engine; +Cc: linux-integrity

On Sat Aug 3, 2024 at 10:47 PM EEST, James Bottomley wrote:
> On Sat, 2024-08-03 at 22:31 +0300, Jarkko Sakkinen wrote:
> > On Sat Aug 3, 2024 at 8:51 PM EEST, James Bottomley wrote:
> > > On Sat, 2024-08-03 at 20:08 +0300, Jarkko Sakkinen wrote:
> > > > On Fri Aug 2, 2024 at 11:25 PM EEST, James Bottomley wrote:
> > > > > Now that we're going to be using the NULL primary to salt
> > > > > sessions, the Intel TSS shim needs fixing to cope with this. 
> > > > > In the Intel TSS, there are two internal handles representing
> > > > > NULL: ESYS_TR_NONE and ESYS_TR_RH_NULL.  We translate
> > > > > TPM_RH_NULL to ESYS_TR_NONE because
> > > > 
> > > > Can you split this into two paragraphs.
> > > > 
> > > > I'm lost why it has two representations.
> > > 
> > > Well, I actually have no idea why the Intel TSS has two
> > > representations for *every* handle: an internal one (specific to
> > > the TSS) and an external one that everyone uses, like 81000001 or
> > > 40000007. As far as I can see it just adds pointless complexity to
> > > the coding.  The IBM TSS only has one, so for code which works with
> > > both, the shim has to transform between internal and external
> > > handle representations before sending the command onward to the
> > > Intel TSS.
> > 
> > Is it possible to address this complexity and move into a single
> > representation? I.e. use external presentation all the way.
>
> Yes, that's what the current code does.  It began life as pure IBM TSS
> so it used what the Intel TSS would consider as all external handle
> representations.  The external to internal shift (and back) happens
> inside the TSS shim.

Ah, right, OK now I'm on page, thank you.

>
> > > Even more mysteriously the Intel TSS has three representations for
> > > the NULL handle: an internal one, an external one (40000007) and
> > > one you use for an empty session (ESYS_TR_NONE).  The IBM TSS uses
> > > TPM_RH_NULL for all three so you can't just translate from external
> > > to internal you have to know if you're using the handle for a
> > > session or a hierarchy as well.
> > 
> > Same question applies to this too.
>
> Remember this is just fixing the Intel TSS Shim.  The fact that we have
> to use three different handles for NULL isn't visible outside the shim,
> so a consumer of these APIs just uses TPM_RH_NULL everywhere.  The fix
> is that the Intel TSS Shim was using the wrong handle for some
> operations.

OK, got it, thanks.

BR, Jarkko

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v2 1/8] tss: Fix handling of TPM_RH_NULL in intel-tss
  2024-08-03 20:43           ` Jarkko Sakkinen
@ 2024-08-04 13:42             ` James Bottomley
  2024-08-04 15:37               ` [openssl-tpm2-engine] " James Bottomley
  2024-08-04 21:28               ` Jarkko Sakkinen
  0 siblings, 2 replies; 20+ messages in thread
From: James Bottomley @ 2024-08-04 13:42 UTC (permalink / raw)
  To: openssl-tpm2-engine, jarkko, Jarkko Sakkinen; +Cc: linux-integrity

The design of the intel-tss shim is to hide the difference between the
internal and the external handles by doing the internal to external
transform on entry.  Unfortunately, the NULL handle (TPM_RH_NULL,
40000007) has two possible internal representations depending on
whether it's used to indicate no session or the null hierarcy.

There is a bug in the intel-tss in that it uses the wrong internal
NULL handle to try to create the NULL seed primary (and thus fails).
Now that we're going to be using the NULL primary to salt sessions,
the Intel TSS shim needs fixing to cope with thi correctly.

The fix is to do the correct transform to the internal hierarchy
representation on NULL hierarchy creation and to do the session handle
conversion everywhere else.  Additionally remove the intel_handle()
code which was supposed to do this: it's unused because 0 is never
passed in as a handle number.

Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>

---
v2: reword commit message

---
 src/include/intel-tss.h | 18 +++++-------------
 1 file changed, 5 insertions(+), 13 deletions(-)

diff --git a/src/include/intel-tss.h b/src/include/intel-tss.h
index 1870b4e..5b8db20 100644
--- a/src/include/intel-tss.h
+++ b/src/include/intel-tss.h
@@ -251,14 +251,6 @@ intel_sess_helper(TSS_CONTEXT *tssContext,
TPM_HANDLE auth, TPMA_SESSION flags)
 				  TPMA_SESSION_CONTINUESESSION |
flags);
 }
 
-static inline TPM_HANDLE
-intel_handle(TPM_HANDLE h)
-{
-	if (h == 0)
-		return ESYS_TR_NONE;
-	return h;
-}
-
 static inline void
 TSS_Delete(TSS_CONTEXT *tssContext)
 {
@@ -937,8 +929,10 @@ tpm2_CreatePrimary(TSS_CONTEXT *tssContext,
TPM_HANDLE primaryHandle,
 	TPM2B_PUBLIC *opub;
 	TPM_RC rc;
 
-	/* FIXME will generate wrong value for NULL hierarchy */
-	primaryHandle = intel_handle(primaryHandle);
+
+	/* TPM_RH_NULL is mapped to ESYS_TR_NONE, which won't work
here */
+	if (primaryHandle == TPM_RH_NULL)
+		primaryHandle = INT_TPM_RH_NULL;
 
 	outsideInfo.size = 0;
 	creationPcr.count = 0;
@@ -993,9 +987,7 @@ tpm2_StartAuthSession(TSS_CONTEXT *tssContext,
TPM_HANDLE tpmKey,
 		      TPM_HANDLE *sessionHandle,
 		      const char *bindPassword)
 {
-	bind = intel_handle(bind);
-	tpmKey = intel_handle(tpmKey);
-	if (bind != ESYS_TR_NONE)
+	if (bind != TPM_RH_NULL)
 		intel_auth_helper(tssContext, bind, bindPassword);
 
 	return Esys_StartAuthSession(tssContext, tpmKey, bind,
ESYS_TR_NONE,
-- 
2.35.3



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* Re: [openssl-tpm2-engine] [PATCH v2 1/8] tss: Fix handling of TPM_RH_NULL in intel-tss
  2024-08-04 13:42             ` [PATCH v2 " James Bottomley
@ 2024-08-04 15:37               ` James Bottomley
  2024-08-04 21:28               ` Jarkko Sakkinen
  1 sibling, 0 replies; 20+ messages in thread
From: James Bottomley @ 2024-08-04 15:37 UTC (permalink / raw)
  To: openssl-tpm2-engine, jarkko, Jarkko Sakkinen; +Cc: linux-integrity

On Sun, 2024-08-04 at 09:42 -0400, James Bottomley wrote:
> The design of the intel-tss shim is to hide the difference between
> the
> internal and the external handles by doing the internal to external
> transform on entry.  Unfortunately, the NULL handle (TPM_RH_NULL,
> 40000007) has two possible internal representations depending on
> whether it's used to indicate no session or the null hierarcy.
> 
> There is a bug in the intel-tss in that it uses the wrong internal
> NULL handle to try to create the NULL seed primary (and thus fails).
> Now that we're going to be using the NULL primary to salt sessions,
> the Intel TSS shim needs fixing to cope with thi correctly.
> 
> The fix is to do the correct transform to the internal hierarchy
> representation on NULL hierarchy creation and to do the session
> handle
> conversion everywhere else.  Additionally remove the intel_handle()
> code which was supposed to do this: it's unused because 0 is never
> passed in as a handle number.

Going over all the internal to external handle conversions, I found one
more use case that would produce a bug. This one isn't actually used in
the openssl_tpm2_engine code, so it's an unmanifested bug but
nevertheless it should be fixed to avoid problems later on.  I'll fold
the below fix into this patch.

Regards,

James

---

diff --git a/src/include/intel-tss.h b/src/include/intel-tss.h
index 3b8c18d..a2050ba 100644
--- a/src/include/intel-tss.h
+++ b/src/include/intel-tss.h
@@ -1271,6 +1271,19 @@ tpm2_handle_ext(TSS_CONTEXT *tssContext,
TPM_HANDLE esysh)
 {
 	TPM2_HANDLE realh = 0;
 
+	switch (esysh) {
+	case ESYS_TR_RH_OWNER:
+		return EXT_TPM_RH_OWNER;
+	case ESYS_TR_RH_PLATFORM:
+		return EXT_TPM_RH_PLATFORM;
+	case ESYS_TR_RH_ENDORSEMENT:
+		return EXT_TPM_RH_ENDORSEMENT;
+	case ESYS_TR_RH_NULL:
+		return EXT_TPM_RH_NULL;
+	case ESYS_TR_NONE:
+		return EXT_TPM_RH_NULL;
+	}
+
 	Esys_TR_GetTpmHandle(tssContext, esysh, &realh);
 
 	return realh;


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 1/8] tss: Fix handling of TPM_RH_NULL in intel-tss
  2024-08-04 13:42             ` [PATCH v2 " James Bottomley
  2024-08-04 15:37               ` [openssl-tpm2-engine] " James Bottomley
@ 2024-08-04 21:28               ` Jarkko Sakkinen
  2024-08-05  2:48                 ` [openssl-tpm2-engine] " James Bottomley
  1 sibling, 1 reply; 20+ messages in thread
From: Jarkko Sakkinen @ 2024-08-04 21:28 UTC (permalink / raw)
  To: James Bottomley, openssl-tpm2-engine, Jarkko Sakkinen; +Cc: linux-integrity

On Sun Aug 4, 2024 at 4:42 PM EEST, James Bottomley wrote:
> The design of the intel-tss shim is to hide the difference between the
> internal and the external handles by doing the internal to external
> transform on entry.  Unfortunately, the NULL handle (TPM_RH_NULL,
> 40000007) has two possible internal representations depending on
> whether it's used to indicate no session or the null hierarcy.
>
> There is a bug in the intel-tss in that it uses the wrong internal
> NULL handle to try to create the NULL seed primary (and thus fails).
> Now that we're going to be using the NULL primary to salt sessions,
> the Intel TSS shim needs fixing to cope with thi correctly.
>
> The fix is to do the correct transform to the internal hierarchy
> representation on NULL hierarchy creation and to do the session handle
> conversion everywhere else.  Additionally remove the intel_handle()
> code which was supposed to do this: it's unused because 0 is never
> passed in as a handle number.
>
> Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
>
> ---
> v2: reword commit message
>
> ---
>  src/include/intel-tss.h | 18 +++++-------------
>  1 file changed, 5 insertions(+), 13 deletions(-)
>
> diff --git a/src/include/intel-tss.h b/src/include/intel-tss.h
> index 1870b4e..5b8db20 100644
> --- a/src/include/intel-tss.h
> +++ b/src/include/intel-tss.h
> @@ -251,14 +251,6 @@ intel_sess_helper(TSS_CONTEXT *tssContext,
> TPM_HANDLE auth, TPMA_SESSION flags)
>  				  TPMA_SESSION_CONTINUESESSION |
> flags);
>  }
>  
> -static inline TPM_HANDLE
> -intel_handle(TPM_HANDLE h)
> -{
> -	if (h == 0)
> -		return ESYS_TR_NONE;
> -	return h;
> -}
> -
>  static inline void
>  TSS_Delete(TSS_CONTEXT *tssContext)
>  {
> @@ -937,8 +929,10 @@ tpm2_CreatePrimary(TSS_CONTEXT *tssContext,
> TPM_HANDLE primaryHandle,
>  	TPM2B_PUBLIC *opub;
>  	TPM_RC rc;
>  
> -	/* FIXME will generate wrong value for NULL hierarchy */
> -	primaryHandle = intel_handle(primaryHandle);
> +
> +	/* TPM_RH_NULL is mapped to ESYS_TR_NONE, which won't work
> here */
> +	if (primaryHandle == TPM_RH_NULL)
> +		primaryHandle = INT_TPM_RH_NULL;
>  
>  	outsideInfo.size = 0;
>  	creationPcr.count = 0;
> @@ -993,9 +987,7 @@ tpm2_StartAuthSession(TSS_CONTEXT *tssContext,
> TPM_HANDLE tpmKey,
>  		      TPM_HANDLE *sessionHandle,
>  		      const char *bindPassword)
>  {
> -	bind = intel_handle(bind);
> -	tpmKey = intel_handle(tpmKey);
> -	if (bind != ESYS_TR_NONE)
> +	if (bind != TPM_RH_NULL)
>  		intel_auth_helper(tssContext, bind, bindPassword);

Not blaming the patch but just have hard time coping this.

The most basic question is probably this: what is the application for
INT_TPM_RH_NULL?

Let's imagine that we have a flow chart describing Intel shim as a state
machine. I decide to shoot it with these three stimulus:

1. INT_TPM_RH_NULL
2. TPM_RH_NULL
3. A valid handle

What happens in each case to its state?

>  
>  	return Esys_StartAuthSession(tssContext, tpmKey, bind,
> ESYS_TR_NONE,

BR, Jarkko

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [openssl-tpm2-engine] [PATCH v2 1/8] tss: Fix handling of TPM_RH_NULL in intel-tss
  2024-08-04 21:28               ` Jarkko Sakkinen
@ 2024-08-05  2:48                 ` James Bottomley
  2024-08-05 11:54                   ` Jarkko Sakkinen
  0 siblings, 1 reply; 20+ messages in thread
From: James Bottomley @ 2024-08-05  2:48 UTC (permalink / raw)
  To: openssl-tpm2-engine, jarkko, Jarkko Sakkinen; +Cc: linux-integrity

On Mon, 2024-08-05 at 00:28 +0300, Jarkko Sakkinen wrote:
[...]
> > --- a/src/include/intel-tss.h
> > +++ b/src/include/intel-tss.h
> > @@ -251,14 +251,6 @@ intel_sess_helper(TSS_CONTEXT *tssContext,
> > TPM_HANDLE auth, TPMA_SESSION flags)
> >                                   TPMA_SESSION_CONTINUESESSION |
> > flags);
> >  }
> >  
> > -static inline TPM_HANDLE
> > -intel_handle(TPM_HANDLE h)
> > -{
> > -       if (h == 0)
> > -               return ESYS_TR_NONE;
> > -       return h;
> > -}
> > -
> >  static inline void
> >  TSS_Delete(TSS_CONTEXT *tssContext)
> >  {
> > @@ -937,8 +929,10 @@ tpm2_CreatePrimary(TSS_CONTEXT *tssContext,
> > TPM_HANDLE primaryHandle,
> >         TPM2B_PUBLIC *opub;
> >         TPM_RC rc;
> >  
> > -       /* FIXME will generate wrong value for NULL hierarchy */
> > -       primaryHandle = intel_handle(primaryHandle);
> > +
> > +       /* TPM_RH_NULL is mapped to ESYS_TR_NONE, which won't work
> > here */
> > +       if (primaryHandle == TPM_RH_NULL)
> > +               primaryHandle = INT_TPM_RH_NULL;
> >  
> >         outsideInfo.size = 0;
> >         creationPcr.count = 0;
> > @@ -993,9 +987,7 @@ tpm2_StartAuthSession(TSS_CONTEXT *tssContext,
> > TPM_HANDLE tpmKey,
> >                       TPM_HANDLE *sessionHandle,
> >                       const char *bindPassword)
> >  {
> > -       bind = intel_handle(bind);
> > -       tpmKey = intel_handle(tpmKey);
> > -       if (bind != ESYS_TR_NONE)
> > +       if (bind != TPM_RH_NULL)
> >                 intel_auth_helper(tssContext, bind, bindPassword);
> 
> Not blaming the patch but just have hard time coping this.
> 
> The most basic question is probably this: what is the application for
> INT_TPM_RH_NULL?

Ah, well, it turns out that the Intel TSS also isn't very performant
and part of the performance loss is using a memory database for
translating external TPM objects into internal ones.  Some of the
performance can be recovered by not doing this.

It turns out that the engine code never uses volatile external handles,
so the switch from volatile internal -> external and vice versa (which
occurs on load followed by key operation) was eliminated and the code
uses internal representation for volatile handles and external
representation for all other handles.  This scheme worked fine until
non-volatile key indexes were introduced and then it broke down a bit.
It would be logically very nice to redo the internal representation to
be entirely contained within the intel tss shim, but then that would
reintroduce the performance problem (although on the other hand, the
Intel TSS is still twice as slow as the IBM TSS so perhaps this
wouldn't be such a huge addition).

Regards,

James


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [openssl-tpm2-engine] [PATCH v2 1/8] tss: Fix handling of TPM_RH_NULL in intel-tss
  2024-08-05  2:48                 ` [openssl-tpm2-engine] " James Bottomley
@ 2024-08-05 11:54                   ` Jarkko Sakkinen
  0 siblings, 0 replies; 20+ messages in thread
From: Jarkko Sakkinen @ 2024-08-05 11:54 UTC (permalink / raw)
  To: James Bottomley, openssl-tpm2-engine, Jarkko Sakkinen; +Cc: linux-integrity

On Mon Aug 5, 2024 at 5:48 AM EEST, James Bottomley wrote:
> On Mon, 2024-08-05 at 00:28 +0300, Jarkko Sakkinen wrote:
> [...]
> > > --- a/src/include/intel-tss.h
> > > +++ b/src/include/intel-tss.h
> > > @@ -251,14 +251,6 @@ intel_sess_helper(TSS_CONTEXT *tssContext,
> > > TPM_HANDLE auth, TPMA_SESSION flags)
> > >                                   TPMA_SESSION_CONTINUESESSION |
> > > flags);
> > >  }
> > >  
> > > -static inline TPM_HANDLE
> > > -intel_handle(TPM_HANDLE h)
> > > -{
> > > -       if (h == 0)
> > > -               return ESYS_TR_NONE;
> > > -       return h;
> > > -}
> > > -
> > >  static inline void
> > >  TSS_Delete(TSS_CONTEXT *tssContext)
> > >  {
> > > @@ -937,8 +929,10 @@ tpm2_CreatePrimary(TSS_CONTEXT *tssContext,
> > > TPM_HANDLE primaryHandle,
> > >         TPM2B_PUBLIC *opub;
> > >         TPM_RC rc;
> > >  
> > > -       /* FIXME will generate wrong value for NULL hierarchy */
> > > -       primaryHandle = intel_handle(primaryHandle);
> > > +
> > > +       /* TPM_RH_NULL is mapped to ESYS_TR_NONE, which won't work
> > > here */
> > > +       if (primaryHandle == TPM_RH_NULL)
> > > +               primaryHandle = INT_TPM_RH_NULL;
> > >  
> > >         outsideInfo.size = 0;
> > >         creationPcr.count = 0;
> > > @@ -993,9 +987,7 @@ tpm2_StartAuthSession(TSS_CONTEXT *tssContext,
> > > TPM_HANDLE tpmKey,
> > >                       TPM_HANDLE *sessionHandle,
> > >                       const char *bindPassword)
> > >  {
> > > -       bind = intel_handle(bind);
> > > -       tpmKey = intel_handle(tpmKey);
> > > -       if (bind != ESYS_TR_NONE)
> > > +       if (bind != TPM_RH_NULL)
> > >                 intel_auth_helper(tssContext, bind, bindPassword);
> > 
> > Not blaming the patch but just have hard time coping this.
> > 
> > The most basic question is probably this: what is the application for
> > INT_TPM_RH_NULL?
>
> Ah, well, it turns out that the Intel TSS also isn't very performant
> and part of the performance loss is using a memory database for
> translating external TPM objects into internal ones.  Some of the
> performance can be recovered by not doing this.

So INT_RH_NULL is  a flag that translates to "do not translate to
internal object if it is derived from the nulll seed?". I.e. is it
some kind of skip flag?

External presentation is any TPM object I guess, but what is
conceptually the internal representation we are talking about here?

I wonder why nobody ever got idea that all kinds of TPM daemons
could use u64 for handles, and have future-proof robustness as in
TPM2 handles are u32.

BR, Jarkko

^ permalink raw reply	[flat|nested] 20+ messages in thread

end of thread, other threads:[~2024-08-05 11:54 UTC | newest]

Thread overview: 20+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-08-02 20:25 [PATCH 0/8] openssl_tpm2_engine: Add attestation functions for primary keys James Bottomley
2024-08-02 20:25 ` [PATCH 1/8] tss: Fix handling of TPM_RH_NULL in intel-tss James Bottomley
2024-08-03 17:08   ` Jarkko Sakkinen
2024-08-03 17:51     ` James Bottomley
2024-08-03 19:31       ` Jarkko Sakkinen
2024-08-03 19:47         ` James Bottomley
2024-08-03 20:43           ` Jarkko Sakkinen
2024-08-04 13:42             ` [PATCH v2 " James Bottomley
2024-08-04 15:37               ` [openssl-tpm2-engine] " James Bottomley
2024-08-04 21:28               ` Jarkko Sakkinen
2024-08-05  2:48                 ` [openssl-tpm2-engine] " James Bottomley
2024-08-05 11:54                   ` Jarkko Sakkinen
2024-08-02 20:26 ` [PATCH 2/8] libcommon: add ability to create a signing primary key James Bottomley
2024-08-02 20:26 ` [PATCH 3/8] libcommon: add bin2hex and tmp2_get_hexname James Bottomley
2024-08-03 17:21   ` Jarkko Sakkinen
2024-08-02 20:26 ` [PATCH 4/8] libcommon: add primary creation from template James Bottomley
2024-08-02 20:26 ` [PATCH 5/8] tss: add tpm2_Certify, tpm2_ActivateCredential and tpm2_PolicyOR James Bottomley
2024-08-02 20:26 ` [PATCH 6/8] tools: add new attest_tpm2_primary command James Bottomley
2024-08-02 20:26 ` [PATCH 7/8] attest_tpm2_primary: add man page James Bottomley
2024-08-02 20:26 ` [PATCH 8/8] tests: add tests for attest_tpm2_primary James Bottomley

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).