Topics

[PATCH v3] Truncate hashes for ECDSA signing, to allow SHA512 to work

David Woodhouse
 

The TPM has no business knowing what hash we were using. ECDSA signatures
truncate the digest to the size of the curve anyway. So just pick any
hash between the curve size and the actual size of the digest, and tell
the TPM it's that. Then we don't care that most TPMs don't support SHA512.

Signed-off-by: David Woodhouse <dwmw2@...>
---
e_tpm2-ecc.c | 74 +++++++++++++++++++++++++++++++++++++--------
tests/create_ecc.sh | 2 +-
2 files changed, 62 insertions(+), 14 deletions(-)

diff --git a/e_tpm2-ecc.c b/e_tpm2-ecc.c
index 21a636c..2cbb3aa 100644
--- a/e_tpm2-ecc.c
+++ b/e_tpm2-ecc.c
@@ -123,6 +123,33 @@ static void tpm2_ecc_free(void *parent, void *ptr, CRYPTO_EX_DATA *ad,
tpm2_delete(data);
}

+#if OPENSSL_VERSION_NUMBER < 0x10100000
+static int EC_GROUP_order_bits(const EC_GROUP *group)
+{
+ if (!group)
+ return 0;
+
+ BIGNUM *order = BN_new();
+
+ if (order == NULL) {
+ ERR_clear_error();
+ return 0;
+ }
+
+ int ret = 0;
+
+ if (!EC_GROUP_get_order(group, order, NULL)) {
+ ERR_clear_error();
+ BN_free(order);
+ return 0;
+ }
+
+ ret = BN_num_bits(order);
+ BN_free(order);
+ return ret;
+}
+#endif
+
static ECDSA_SIG *tpm2_ecdsa_sign(const unsigned char *dgst, int dgst_len,
const BIGNUM *kinv, const BIGNUM *rp,
EC_KEY *eck)
@@ -139,25 +166,46 @@ static ECDSA_SIG *tpm2_ecdsa_sign(const unsigned char *dgst, int dgst_len,
int num_commands;
struct policy_command *commands;
TPM_ALG_ID nameAlg;
-
- /* The TPM insists on knowing the digest type, so
- * calculate that from the size */
- switch (dgst_len) {
- case SHA_DIGEST_LENGTH:
+ int curve_len;
+
+ /*
+ * ECDSA signatures truncate the incoming hash to fit the curve,
+ * and the signature mechanism is the same regardless of the
+ * hash being used.
+ *
+ * The TPM bizarrely wants to be told the hash algorithm, and
+ * either it or the TSS will validate that the digest length
+ * matches the hash that it's told, despite it having no business
+ * caring about such things.
+ *
+ * So, we can truncate the digest and pretend it's any smaller
+ * digest that the TPM actually does support, as long as that
+ * digest is larger than the size of the curve.
+ */
+ curve_len = (EC_GROUP_order_bits(EC_KEY_get0_group(eck)) + 7) / 8;
+ /* If we couldn't work it out, don't truncate */
+ if (!curve_len)
+ curve_len = dgst_len;
+
+ if (dgst_len == SHA_DIGEST_LENGTH ||
+ (curve_len <= SHA_DIGEST_LENGTH && dgst_len > SHA_DIGEST_LENGTH)) {
in.inScheme.details.ecdsa.hashAlg = TPM_ALG_SHA1;
- break;
- case SHA256_DIGEST_LENGTH:
+ dgst_len = SHA_DIGEST_LENGTH;
+ } else if (dgst_len == SHA256_DIGEST_LENGTH ||
+ (curve_len <= SHA256_DIGEST_LENGTH && dgst_len > SHA256_DIGEST_LENGTH)) {
in.inScheme.details.ecdsa.hashAlg = TPM_ALG_SHA256;
- break;
- case SHA384_DIGEST_LENGTH:
+ dgst_len = SHA256_DIGEST_LENGTH;
+ } else if (dgst_len == SHA384_DIGEST_LENGTH ||
+ (curve_len <= SHA384_DIGEST_LENGTH && dgst_len > SHA384_DIGEST_LENGTH)) {
in.inScheme.details.ecdsa.hashAlg = TPM_ALG_SHA384;
- break;
+ dgst_len = SHA384_DIGEST_LENGTH;
#ifdef TPM_ALG_SHA512
- case SHA512_DIGEST_LENGTH:
+ } else if (dgst_len == SHA512_DIGEST_LENGTH ||
+ (curve_len <= SHA512_DIGEST_LENGTH && dgst_len > SHA512_DIGEST_LENGTH)) {
in.inScheme.details.ecdsa.hashAlg = TPM_ALG_SHA512;
- break;
+ dgst_len = SHA512_DIGEST_LENGTH;
#endif
- default:
+ } else {
printf("ECDSA signature: Unknown digest length, cannot deduce hash type for TPM\n");
return NULL;
}
diff --git a/tests/create_ecc.sh b/tests/create_ecc.sh
index 061cedb..092c743 100755
--- a/tests/create_ecc.sh
+++ b/tests/create_ecc.sh
@@ -14,7 +14,7 @@ for curve in $(${bindir}/create_tpm2_key --list-curves); do
echo "Checking curve ${curve}"
${bindir}/create_tpm2_key -p 81000001 --ecc ${curve} key.tpm || \
exit 1
- for hash in sha1 sha256 sha384; do
+ for hash in sha1 sha256 sha384 sha512; do
openssl req -new -x509 -${hash} -subj '/CN=test/' -key key.tpm -engine tpm2 -keyform engine -out tmp.crt && \
openssl verify -CAfile tmp.crt -check_ss_sig tmp.crt || \
exit 1