hidgd: add correct AUTHENTICATE response

The key handle now contains the TPM representation of an elliptic
curve key, so unpack this key and use it to sign the incoming
challenge.  This scheme is now sufficient to pass the
https://webauthn.org test for both registration and login.  However,
the counter is ephemeral to the hidgd so we need a permanent solution
for that as well.

Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
diff --git a/hidgd.c b/hidgd.c
index 542fce7..a191a7b 100644
--- a/hidgd.c
+++ b/hidgd.c
@@ -227,18 +227,11 @@
 	U2F_AUTHENTICATE_REQ *req;
 	U2F_AUTHENTICATE_RESP *resp = (U2F_AUTHENTICATE_RESP *)buf;
 	uint8_t *ptr = &ctap[4];	/* point to APDU lengths */
+	int err = U2F_SW_NO_ERROR;
 	int len;
 
 	len = get_apdu(&ptr);
-	(void)len;
-	/*
-	if (len > sizeof(U2F_AUTHENTICATE_REQ)) {
-		fprintf(stderr, "Wrong AUTHENTICATE REQ len %d > %ld\n",
-			len, sizeof(U2F_AUTHENTICATE_REQ));
-		process_error(cid, ERR_INVALID_CMD);
-		return;
-	}
-	*/
+
 	/*
 	 * standard seems to require this but Mozilla doesn't transmit it
 	len = get_apdu(&ptr);
@@ -250,13 +243,38 @@
 	}
 	*/
 	req = (U2F_AUTHENTICATE_REQ *)ptr;
-	(void)*req;
+
+	if (len != U2F_CHAL_SIZE + U2F_APPID_SIZE + 1 + req->keyHandleLen) {
+		fprintf(stderr, "Wrong AUTHENTICATE REQ len %d > %ld\n",
+			len, sizeof(U2F_AUTHENTICATE_REQ));
+		process_error(cid, ERR_INVALID_CMD);
+		return;
+	}
 
 	memset(buf, 0, sizeof(buf));
+
+	if (ctap[2] == U2F_AUTH_CHECK_ONLY) {
+		len = 0;
+		if (tpm_check_key(parent, req->keyHandleLen, req->keyHandle))
+			/* yes this is the success return */
+			err = U2F_SW_CONDITIONS_NOT_SATISFIED;
+		else
+			err = U2F_SW_WRONG_DATA;
+		goto send;
+	}
+	len = tpm_sign(parent, req, resp->ctr, resp->sig);
+	if (len) {
+		err = U2F_SW_NO_ERROR;
+		/* tpm_sign returns signature length, so account for
+		 * user presence and counter */
+		len += 5;
+	} else {
+		err = U2F_SW_WRONG_DATA;
+	}
+
+ send:
 	resp->flags = U2F_AUTH_FLAG_TUP; /* pretend we have user presence */
-	send_payload(buf, sizeof(U2F_AUTHENTICATE_RESP), cid,
-		     ctap[2] == U2F_AUTH_CHECK_ONLY ?
-		     U2F_SW_CONDITIONS_NOT_SATISFIED : U2F_SW_NO_ERROR);
+	send_payload(buf, len, cid, err);
 }
 
 static void process_msg(U2FHID_FRAME *frame)
diff --git a/hidgd.h b/hidgd.h
index e2c8ed2..9465a00 100644
--- a/hidgd.h
+++ b/hidgd.h
@@ -5,6 +5,9 @@
 
 /* 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);
 
 /* crypto.c */
 int crypto_fill_register_sig(uint32_t parent, U2F_REGISTER_REQ *req,
diff --git a/tpm.c b/tpm.c
index 2e073a1..3d6337b 100644
--- a/tpm.c
+++ b/tpm.c
@@ -14,6 +14,7 @@
 #include <tssresponsecode.h>
 #include <tsscryptoh.h>
 #include <tssmarshal.h>
+#include <Unmarshal_fp.h>
 
 #include <openssl/ecdsa.h>
 
@@ -21,6 +22,7 @@
 
 static char *dir = NULL;
 static TSS_CONTEXT *tssContext;
+static uint32_t count = 1000;
 
 static void tpm2_error(TPM_RC rc, const char *reason)
 {
@@ -241,10 +243,155 @@
 
 	pt = &out.outPublic.publicArea.unique.ecc;
 	pub->pointFormat = U2F_POINT_UNCOMPRESSED;
-	printf("PUBLIC POINTS  %d,%d\n", pt->x.t.size, pt->y.t.size);
 	memcpy(pub->x, pt->x.t.buffer, pt->x.t.size);
 	memcpy(pub->y, pt->y.t.buffer, pt->y.t.size);
-	printf("DONE\n");
 
+	int i;
+	for (i = 0; i < 65; i++) {
+		printf("%02x:", *(&pub->pointFormat + i));
+		if (i % 15 == 14)
+			printf("\n");
+	}
+	printf("\n");
+
+	return len;
+}
+
+static int tpm2_load_key(uint32_t parent, uint32_t len, uint8_t *key)
+{
+	Load_In in;
+        Load_Out out;
+        TPM_RC rc;
+	const char *reason;
+
+	in.parentHandle = parent;
+	rc = TSS_TPM2B_PUBLIC_Unmarshalu(&in.inPublic, &key, &len, FALSE);
+	if (rc) {
+		reason = "PUBLIC_Unmarshal";
+		goto error;
+	}
+	rc = TSS_TPM2B_PRIVATE_Unmarshalu(&in.inPrivate, &key, &len);
+	if (rc) {
+		reason = "PRIVATE_Unmarshal";
+		goto error;
+	}
+	rc = TSS_Execute(tssContext,
+			 (RESPONSE_PARAMETERS *)&out,
+			 (COMMAND_PARAMETERS *)&in,
+			 NULL,
+			 TPM_CC_Load,
+			 TPM_RS_PW, NULL, 0,
+			 TPM_RH_NULL, NULL, 0);
+	if (rc) {
+		reason= "TPM2_Load";
+		goto error;
+	}
+	return out.objectHandle;
+ error:
+	tpm2_error(rc, reason);
+	return 0;
+}
+
+int tpm_check_key(uint32_t parent, uint8_t len, uint8_t *key)
+{
+	TPM_HANDLE k;
+	TPM_RC rc;
+	int ret = 0;
+
+	rc = tpm2_create();
+	if (rc)
+		return 0;
+
+	parent = tpm2_get_parent(parent);
+
+	k = tpm2_load_key(parent, len, key);
+	if (!k)
+		goto error;
+	ret = 1;
+	tpm2_flush_handle(k);
+ error:
+	tpm2_put_parent(parent);
+	tpm2_delete();
+
+	return ret;
+}
+
+int tpm_sign(uint32_t parent, U2F_AUTHENTICATE_REQ *req, uint8_t *ctr,
+	     uint8_t *sig)
+{
+	TPMT_HA digest;
+	TPM_RC rc;
+	Sign_In in;
+	Sign_Out out;
+	ECDSA_SIG *osig;
+	uint8_t presence[1];
+	BIGNUM *r,*s;
+	int len = 0;
+	TPM_HANDLE k;
+	int i;
+
+	rc = tpm2_create();
+	if (rc)
+		return 0;
+
+	parent = tpm2_get_parent(parent);
+
+	k = tpm2_load_key(parent, req->keyHandleLen, req->keyHandle);
+	tpm2_put_parent(parent);
+
+	if (!k)
+		goto error;
+
+	presence[0] = 1;
+	count++;
+	printf("COUNTER: %d\n", count);
+	for (i = 0; i < U2F_CTR_SIZE; i++) {
+		ctr[i] = (count>>((U2F_CTR_SIZE - i - 1)*8)) & 0xff;
+		printf("ctr[%d]=%d\n", i, ctr[i]);
+	}
+	printf("sizeof presence = %ld\n", sizeof(req->appId));
+
+	digest.hashAlg = TPM_ALG_SHA256;
+	TSS_Hash_Generate(&digest,
+			  sizeof(req->appId), req->appId,
+			  sizeof(presence), presence,
+			  U2F_CTR_SIZE, ctr,
+			  sizeof(req->chal), req->chal,
+			  0, NULL);
+	in.inScheme.details.ecdsa.hashAlg = digest.hashAlg;
+	in.keyHandle = k;
+	in.inScheme.scheme = TPM_ALG_ECDSA;
+	in.digest.t.size = TSS_GetDigestSize(digest.hashAlg);
+	memcpy(in.digest.t.buffer, digest.digest.tssmax, in.digest.t.size);
+	in.validation.tag = TPM_ST_HASHCHECK;
+	in.validation.hierarchy = TPM_RH_NULL;
+	in.validation.digest.t.size = 0;
+	rc = TSS_Execute(tssContext,
+                         (RESPONSE_PARAMETERS *)&out,
+                         (COMMAND_PARAMETERS *)&in,
+                         NULL,
+                         TPM_CC_Sign,
+			 TPM_RS_PW, NULL, 0,
+                         TPM_RH_NULL, NULL, 0);
+        if (rc) {
+                tpm2_error(rc, "TPM2_Sign");
+        }
+	tpm2_flush_handle(k);
+ error:
+	tpm2_delete();
+
+	if (rc == TPM_RC_SUCCESS) {
+		osig = ECDSA_SIG_new();
+		r = BN_bin2bn(out.signature.signature.ecdsa.signatureR.t.buffer,
+			      out.signature.signature.ecdsa.signatureR.t.size,
+			      NULL);
+		s = BN_bin2bn(out.signature.signature.ecdsa.signatureS.t.buffer,
+			      out.signature.signature.ecdsa.signatureS.t.size,
+			      NULL);
+		ECDSA_SIG_set0(osig, r, s);
+		len = i2d_ECDSA_SIG(osig, &sig);
+		ECDSA_SIG_free(osig);
+	}
+	printf("Sig of len %d\n", len);
 	return len;
 }