tpm: correctly check for and use a NV index for the authenticate counter

TODO: create the index if it doesn't exist.

Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
diff --git a/hidgd.1.in b/hidgd.1.in
index 6d388b5..5d313a2 100644
--- a/hidgd.1.in
+++ b/hidgd.1.in
@@ -5,14 +5,28 @@
 
 Handles the hidg end of a FIDO2 device.  Note that the certificate
 file is simply placed straight into the register reply and therefore
-must be correctly DER encoded.
+must be correctly DER encoded.  The parent is assumed to be the
+storage seed unless you specify something different and the counter NV
+index is asumed to be 01000101.  If the counter NV index doesn't exist
+in the TPM it will be created and thus we can assure that a
+monotonically increasing count is attached to every authentication
+response as required by the standard.
+
+The way the system works is that the registration certificate and key
+are used to sign registration responses, but each registration request
+generates a new TPM key, which is serialized into the registration key
+handle so that when it is presented at authentication time, it can be
+loaded into the TPM.  This is so that the TPM itself never has to
+remember any key information and the only persistent TPM resource used
+is the NV counter index.
 
 [examples]
 
-Create a master parent key and place it at 81000101
+Generate a certificate and key pair for registration
 
-openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:prime256v1 -pkeyopt ec_param_enc:named_curve -out parent_key.key
+openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:prime256v1 -pkeyopt ec_param_enc:named_curve -out reg_key.key
 
-create_tpm2_key --restricted -w parent_key.key parent_key.tpm
+And then generate a self signed DER form certificate with a common
+name:
 
-load_tpm2_key parent_key.tpm 8100101
+openssl req -new -x509 -subj '/CN=My Fido Token/' -key reg_key.key -out reg_key.der -outform DER
diff --git a/hidgd.c b/hidgd.c
index a191a7b..38f7f5e 100644
--- a/hidgd.c
+++ b/hidgd.c
@@ -27,11 +27,14 @@
 
 /* choose TPM default parent */
 static uint32_t parent = 0;
+/* choose the TPM default counter index */
+static uint32_t counter = 0;
 
 static struct option long_options[] = {
 	{"help", 0, 0, 'h'},
 	{"version", 0, 0, 'v'},
 	{"parent", 1, 0, 'p'},
+	{"counter", 1, 0, 'c'},
 	{0, 0, 0, 0,}
 };
 
@@ -41,7 +44,8 @@
 		"Options:\n"
 		"\t-h, --help                print this help message\n"
 		"\t-v, --version             print package version\n"
-		"\t-p, --parent              Specify the parent key\n"
+		"\t-p, --parent <key>        Specify the parent key\n"
+		"\t-c, --counter <nv>        TPM Counter NV index\n"
 		"\n",
 		argv0);
 }
@@ -262,7 +266,7 @@
 			err = U2F_SW_WRONG_DATA;
 		goto send;
 	}
-	len = tpm_sign(parent, req, resp->ctr, resp->sig);
+	len = tpm_sign(parent, counter, req, resp->ctr, resp->sig);
 	if (len) {
 		err = U2F_SW_NO_ERROR;
 		/* tpm_sign returns signature length, so account for
@@ -388,6 +392,9 @@
 		case 'p':
 			parent = strtoul(optarg, NULL, 16);
 			break;
+		case 'c':
+			counter = strtoul(optarg, NULL, 16);
+			break;
 		default:
 			usage(argv[0], stderr);
 			exit(1);
diff --git a/hidgd.h b/hidgd.h
index 9465a00..787599b 100644
--- a/hidgd.h
+++ b/hidgd.h
@@ -6,8 +6,8 @@
 /* tpm.c */
 int tpm_get_public_point(uint32_t parent, U2F_EC_POINT *pub, uint8_t *handle);
 int tpm_check_key(uint32_t parent, uint8_t len, uint8_t *key);
-int tpm_sign(uint32_t parent, U2F_AUTHENTICATE_REQ *req, uint8_t *ctr,
-	     uint8_t *sig);
+int tpm_sign(uint32_t parent, uint32_t counter, U2F_AUTHENTICATE_REQ *req,
+	     uint8_t *ctr, uint8_t *sig);
 
 /* crypto.c */
 int crypto_fill_register_sig(uint32_t parent, U2F_REGISTER_REQ *req,
diff --git a/tpm.c b/tpm.c
index 3d6337b..7343927 100644
--- a/tpm.c
+++ b/tpm.c
@@ -22,7 +22,6 @@
 
 static char *dir = NULL;
 static TSS_CONTEXT *tssContext;
-static uint32_t count = 1000;
 
 static void tpm2_error(TPM_RC rc, const char *reason)
 {
@@ -41,6 +40,8 @@
         unlink(keyfile);
         snprintf(keyfile, sizeof(keyfile), "%s/hp%08x.bin", dir, key);
         unlink(keyfile);
+        snprintf(keyfile, sizeof(keyfile), "%s/nvp%08x.bin", dir, key);
+        unlink(keyfile);
 }
 
 static void tpm2_delete(void)
@@ -292,6 +293,139 @@
 	return 0;
 }
 
+static int tpm2_rc_is_handle(TPM_RC rc)
+{
+	/* rc also has which handle encoded in it
+	 * so strip that off */
+	return (rc & 0xff) == TPM_RC_HANDLE;
+}
+
+static int tpm2_readpublic_nv(uint32_t nv)
+{
+	NV_ReadPublic_In in;
+	NV_ReadPublic_Out out;
+	TPM_RC rc;
+
+	in.nvIndex = nv;
+
+	rc = TSS_Execute(tssContext,
+			 (RESPONSE_PARAMETERS *)&out,
+			 (COMMAND_PARAMETERS *)&in,
+			 NULL,
+			 TPM_CC_NV_ReadPublic,
+			 TPM_RH_NULL, NULL, 0);
+
+	if (rc) {
+		if (!tpm2_rc_is_handle(rc))
+			tpm2_error(rc, "TPM2_NV_ReadPublic");
+		return rc;
+	}
+
+	if ((out.nvPublic.nvPublic.attributes.val & TPMA_NVA_TPM_NT_MASK) >> 4
+	    == TPM_NT_COUNTER)
+		return rc;
+
+	fprintf(stderr, "NV index %x is not a counter\n", nv);
+
+	return TPM_RC_VALUE;
+}
+
+static int tpm2_read_nv(uint32_t nv, uint64_t *val)
+{
+	NV_Read_In in;
+	NV_Read_Out out;
+	TPM_RC rc;
+	int i;
+
+	in.authHandle = nv;
+	in.nvIndex = nv;
+	in.offset = 0;
+	in.size = sizeof(*val);
+
+	rc = TSS_Execute(tssContext,
+			 (RESPONSE_PARAMETERS *)&out,
+			 (COMMAND_PARAMETERS *)&in,
+			 NULL,
+			 TPM_CC_NV_Read,
+			 TPM_RS_PW, NULL, 0,
+			 TPM_RH_NULL, NULL, 0);
+
+	if (rc) {
+		if (!tpm2_rc_is_handle(rc))
+			tpm2_error(rc, "TPM2_NV_Read");
+		return rc;
+	}
+
+	*val = 0;
+	/* TPM values are big endian */
+	for (i = 0; i < sizeof(*val); i++)
+		*val |= ((uint8_t *)out.data.b.buffer)[i]
+			<< ((sizeof(*val) - i - 1)* 8);
+
+	return rc;
+}
+
+static int tpm2_increment_nv(uint32_t nv, uint64_t *val)
+{
+	NV_Increment_In in;
+	TPM_RC rc;
+
+	printf("NV increment on %x\n", nv);
+
+	/* must do a read first for the TSS to get the nv files */
+	rc = tpm2_readpublic_nv(nv);
+	if (rc)
+		return rc;
+
+	in.authHandle = nv;
+	in.nvIndex = nv;
+
+	rc = TSS_Execute(tssContext,
+			 NULL,
+			 (COMMAND_PARAMETERS *)&in,
+			 NULL,
+			 TPM_CC_NV_Increment,
+			 TPM_RS_PW, NULL, 0,
+			 TPM_RH_NULL, NULL, 0);
+	if (rc != TPM_RC_SUCCESS) {
+		if (tpm2_rc_is_handle(rc))
+			tpm2_error(rc, "TPM2_NV_Increment");
+
+		return rc;
+	}
+
+	rc = tpm2_read_nv(nv, val);
+
+	return rc;
+}
+
+static int tpm2_create_nv(uint32_t nv)
+{
+	return -1;
+}
+
+static int tpm2_get_counter(uint32_t nv)
+{
+	uint64_t val = 0;
+	TPM_RC rc;
+
+	if (nv == 0)
+		/* default NV index */
+		nv = 0x01000101;
+
+	rc = tpm2_increment_nv(nv, &val);
+	if (tpm2_rc_is_handle(rc)) {
+		rc = tpm2_create_nv(nv);
+		if (rc == TPM_RC_SUCCESS)
+			rc = tpm2_increment_nv(nv, &val);
+	}
+
+	tpm2_rm_keyfile(nv);
+
+	/* truncate to 32 bits */
+	return val;
+}
+
 int tpm_check_key(uint32_t parent, uint8_t len, uint8_t *key)
 {
 	TPM_HANDLE k;
@@ -316,8 +450,8 @@
 	return ret;
 }
 
-int tpm_sign(uint32_t parent, U2F_AUTHENTICATE_REQ *req, uint8_t *ctr,
-	     uint8_t *sig)
+int tpm_sign(uint32_t parent, uint32_t counter, U2F_AUTHENTICATE_REQ *req,
+	     uint8_t *ctr, uint8_t *sig)
 {
 	TPMT_HA digest;
 	TPM_RC rc;
@@ -327,6 +461,7 @@
 	uint8_t presence[1];
 	BIGNUM *r,*s;
 	int len = 0;
+	int count;
 	TPM_HANDLE k;
 	int i;
 
@@ -343,7 +478,7 @@
 		goto error;
 
 	presence[0] = 1;
-	count++;
+	count = tpm2_get_counter(counter);
 	printf("COUNTER: %d\n", count);
 	for (i = 0; i < U2F_CTR_SIZE; i++) {
 		ctr[i] = (count>>((U2F_CTR_SIZE - i - 1)*8)) & 0xff;