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;