From 5c224b8d981e9b70a877e433d89979e081c756be Mon Sep 17 00:00:00 2001 From: Stefan Berger Date: Tue, 27 Oct 2020 17:35:39 -0400 Subject: [PATCH] tpm2: EcSchnorr: Enforce that the OpenSSL-generated bnD has no leading zeros To avoid a potential side channel in the EcSchnorr signing algorithm, enforce that the OpenSSL-generated bnD does not have leading zeros that may then cause a timing side channel in the BnEccModMult() operation. We modified BnEccGenerateKeyPair() so it calls BnEccModMult with a scalar of constant number of bytes (for a particular curve): In this version of BnEccGenerateKeyPair we take a dual approach to constant time requirements: For curves whose order is at the byte boundary, e.g. NIST P224/P256/P384, we make sure that bnD has all bytes set (no leading zeros) so that OpenSSL BIGNUM code will not reduce the number of bytes and the subsequent BnEccModMult() would run faster for a shoter value. For all other curves whose order is not at the byte boundary, e.g. NIST P521, we simply always add the order to bnD and call BnEccModMult() with the result bnD1, which leads to the same result. Signed-off-by: Stefan Berger Suggested-by: Charanjit Jutla Reviewed-by: Charanjit Jutla Tested-by: Stefan Berger --- src/tpm2/crypto/CryptEccMain_fp.h | 2 + src/tpm2/crypto/openssl/CryptEccMain.c | 65 +++++++++++++++++++-- src/tpm2/crypto/openssl/CryptEccSignature.c | 2 +- src/tpm2/crypto/openssl/Helpers.c | 36 ++++++++++-- src/tpm2/crypto/openssl/Helpers_fp.h | 3 +- 5 files changed, 95 insertions(+), 13 deletions(-) diff --git a/src/tpm2/crypto/CryptEccMain_fp.h b/src/tpm2/crypto/CryptEccMain_fp.h index c47343ed..32c0ee1d 100644 --- a/src/tpm2/crypto/CryptEccMain_fp.h +++ b/src/tpm2/crypto/CryptEccMain_fp.h @@ -169,6 +169,8 @@ BnEccGetPrivate( const ECC_CURVE_DATA *C, // IN: curve for which the private key #if USE_OPENSSL_FUNCTIONS_EC const EC_GROUP *G, // IN: the EC_GROUP to use; must be != NULL for rand == NULL + BOOL noLeadingZeros, // IN: require that all bytes in the private key be set + // result may not have leading zero bytes #endif // needs to be appropriate RAND_STATE *rand // IN: state for DRBG diff --git a/src/tpm2/crypto/openssl/CryptEccMain.c b/src/tpm2/crypto/openssl/CryptEccMain.c index cf4db9f7..d11f5008 100644 --- a/src/tpm2/crypto/openssl/CryptEccMain.c +++ b/src/tpm2/crypto/openssl/CryptEccMain.c @@ -575,6 +575,8 @@ BnEccGetPrivate( bigNum dOut, // OUT: the qualified random value const ECC_CURVE_DATA *C, // IN: curve for which the private key const EC_GROUP *G, // IN: the EC_GROUP to use; must be != NULL for rand == NULL + BOOL noLeadingZeros, // IN: require that all bytes in the private key be set + // result may not have leading zero bytes // needs to be appropriate RAND_STATE *rand // IN: state for DRBG ) @@ -583,11 +585,16 @@ BnEccGetPrivate( BOOL OK; UINT32 orderBits = BnSizeInBits(order); UINT32 orderBytes = BITS_TO_BYTES(orderBits); + UINT32 requestedBits = 0; BN_VAR(bnExtraBits, MAX_ECC_KEY_BITS + 64); BN_VAR(nMinus1, MAX_ECC_KEY_BITS); - if (rand == NULL) - return OpenSSLEccGetPrivate(dOut, G); + if (rand == NULL) { + if (noLeadingZeros) + requestedBits = orderBits; + + return OpenSSLEccGetPrivate(dOut, G, requestedBits); + } // OK = BnGetRandomBits(bnExtraBits, (orderBytes * 8) + 64, rand); @@ -600,6 +607,8 @@ BnEccGetPrivate( /* 10.2.11.2.21 BnEccGenerateKeyPair() */ /* This function gets a private scalar from the source of random bits and does the point multiply to get the public key. */ +#if !USE_OPENSSL_FUNCTIONS_EC // libtpms added + BOOL BnEccGenerateKeyPair( bigNum bnD, // OUT: private scalar @@ -610,11 +619,7 @@ BnEccGenerateKeyPair( { BOOL OK = FALSE; // Get a private scalar -#if USE_OPENSSL_FUNCTIONS_EC // libtpms added beging - OK = BnEccGetPrivate(bnD, AccessCurveData(E), E->G, rand); -#else // libtpms added end OK = BnEccGetPrivate(bnD, AccessCurveData(E), rand); -#endif // libtpms added // Do a point multiply OK = OK && BnEccModMult(ecQ, NULL, bnD, E); if(!OK) @@ -623,6 +628,54 @@ BnEccGenerateKeyPair( BnSetWord(ecQ->z, 1); return OK; } + +#else // libtpms added begin + +/* In this version of BnEccGenerateKeyPair we take a dual approach to constant + time requirements: For curves whose order is at the byte boundary, e.g. + NIST P224/P256/P384, we make sure that bnD has all bytes set (no leading zeros) + so that OpenSSL BIGNUM code will not reduce the number of bytes and the + subsequent BnEccModMult() would run faster for a shoter value. For all other + curves whose order is not at the byte boundary, e.g. NIST P521, we simply + always add the order of the curve to bnD and call BnEccModMult() with the + result bnD1, which leads to the same result. */ +BOOL +BnEccGenerateKeyPair( + bigNum bnD, // OUT: private scalar + bn_point_t *ecQ, // OUT: public point + bigCurve E, // IN: curve for the point + RAND_STATE *rand // IN: DRBG state to use + ) +{ + BOOL OK = FALSE; + bigConst order = CurveGetOrder(AccessCurveData(E)); + UINT32 orderBits = BnSizeInBits(order); + BOOL atByteBoundary = (orderBits & 7) == 0; + BOOL noLeadingZeros = atByteBoundary; + ECC_NUM(bnD1); + + // We request that bnD not have leading zeros if it is at byte-boundary, + // like for example it is the case for NIST P256. + OK = BnEccGetPrivate(bnD, AccessCurveData(E), E->G, noLeadingZeros, rand); + if (!atByteBoundary) { + // for NIST P521 we can add the order to bnD to ensure we have + // a constant amount of bytes; the result is the same as if we + // were doing the BnEccModMult() calculation with bnD. + OK = OK && BnAdd(bnD1, bnD, order); + OK = OK && BnEccModMult(ecQ, NULL, bnD1, E); + } else { + OK = OK && BnEccModMult(ecQ, NULL, bnD, E); + } + + if(!OK) + BnSetWord(ecQ->z, 0); + else + BnSetWord(ecQ->z, 1); + return OK; +} + +#endif // libtpms added end + /* 10.2.12.2.21 CryptEccNewKeyPair */ /* This function creates an ephemeral ECC. It is ephemeral in that is expected that the private part of the key will be discarded */ diff --git a/src/tpm2/crypto/openssl/CryptEccSignature.c b/src/tpm2/crypto/openssl/CryptEccSignature.c index 12479632..3b8853d8 100644 --- a/src/tpm2/crypto/openssl/CryptEccSignature.c +++ b/src/tpm2/crypto/openssl/CryptEccSignature.c @@ -303,7 +303,7 @@ BnSignEcdaa( // generate nonceK such that 0 < nonceK < n // use bnT as a temp. #if USE_OPENSSL_FUNCTIONS_EC // libtpms added begin - if(!BnEccGetPrivate(bnT, AccessCurveData(E), E->G, rand)) + if(!BnEccGetPrivate(bnT, AccessCurveData(E), E->G, false, rand)) #else // libtpms added end if(!BnEccGetPrivate(bnT, AccessCurveData(E), rand)) #endif // libtpms added diff --git a/src/tpm2/crypto/openssl/Helpers.c b/src/tpm2/crypto/openssl/Helpers.c index cb9e9f2b..cae3f88c 100644 --- a/src/tpm2/crypto/openssl/Helpers.c +++ b/src/tpm2/crypto/openssl/Helpers.c @@ -210,12 +210,17 @@ evpfunc GetEVPCipher(TPM_ALG_ID algorithm, // IN BOOL OpenSSLEccGetPrivate( bigNum dOut, // OUT: the qualified random value - const EC_GROUP *G // IN: the EC_GROUP to use + const EC_GROUP *G, // IN: the EC_GROUP to use + const UINT32 requestedBits // IN: if not 0, then dOut must have that many bits ) { BOOL OK = FALSE; const BIGNUM *D; EC_KEY *eckey = EC_KEY_new(); + UINT32 requestedBytes = BITS_TO_BYTES(requestedBits); + int repeats = 0; + int maxRepeats; + int numBytes; pAssert(G != NULL); @@ -225,10 +230,31 @@ OpenSSLEccGetPrivate( if (EC_KEY_set_group(eckey, G) != 1) goto Exit; - if (EC_KEY_generate_key(eckey) == 1) { - OK = TRUE; - D = EC_KEY_get0_private_key(eckey); - OsslToTpmBn(dOut, D); + maxRepeats = 8; + // non-byte boundary order'ed curves, like NIST P521, need more loops to + // have a result with topmost byte != 0 + if (requestedBits & 7) + maxRepeats += (9 - (requestedBits & 7)); + + while (true) { + if (EC_KEY_generate_key(eckey) == 1) { + D = EC_KEY_get0_private_key(eckey); + // if we need a certain amount of bytes and we are below a threshold + // of loops, check the number of bytes we have, otherwise take the + // result + if ((requestedBytes != 0) && (repeats < maxRepeats)) { + numBytes = BN_num_bytes(D); + if ((int)requestedBytes != numBytes) { + // result does not have enough bytes + repeats++; + continue; + } + // result is sufficient + } + OK = TRUE; + OsslToTpmBn(dOut, D); + } + break; } Exit: diff --git a/src/tpm2/crypto/openssl/Helpers_fp.h b/src/tpm2/crypto/openssl/Helpers_fp.h index f5f58595..99d984e4 100644 --- a/src/tpm2/crypto/openssl/Helpers_fp.h +++ b/src/tpm2/crypto/openssl/Helpers_fp.h @@ -85,7 +85,8 @@ evpfunc GetEVPCipher(TPM_ALG_ID algorithm, // IN #if USE_OPENSSL_FUNCTIONS_EC BOOL OpenSSLEccGetPrivate( bigNum dOut, // OUT: the qualified random value - const EC_GROUP *G // IN: the EC_GROUP to use + const EC_GROUP *G, // IN: the EC_GROUP to use + const UINT32 requestedBits // IN: if not 0, then dOut must have that many bits ); #endif