Add ECDSA nonce-testing functions.
There are a few places where it is useful to run ECDSA with a specified
nonce:
- An ECDSA KAT in the module self-check
- Unit tests for particular test vectors
- Fuzzing the implementation (requested by the cryptofuzz project)
This replaces the fixed_k machinery with a separate function. Although
they are effectively the same, I've used two different functions.
One is internal and only used in the module self-check. The other is
exported for unit tests and cryptofuzz but marked with a for_testing.
(Chromium's presubmits flag uses of "for_testing" functions outside of
unit tests. The KAT version isn't in a test per se, so it's a separate
function.)
Bug: 391
Change-Id: I0f764d89bf0ac2081307e1079623d508fb0f2df7
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/45867
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/crypto/fipsmodule/ec/ec_key.c b/crypto/fipsmodule/ec/ec_key.c
index bc09e0e..7a6daab 100644
--- a/crypto/fipsmodule/ec/ec_key.c
+++ b/crypto/fipsmodule/ec/ec_key.c
@@ -171,7 +171,6 @@
EC_GROUP_free(r->group);
EC_POINT_free(r->pub_key);
ec_wrapped_scalar_free(r->priv_key);
- BN_free(r->fixed_k);
CRYPTO_free_ex_data(g_ec_ex_data_class_bss_get(), r, &r->ex_data);
diff --git a/crypto/fipsmodule/ec/internal.h b/crypto/fipsmodule/ec/internal.h
index 18aabb0..289c3aa 100644
--- a/crypto/fipsmodule/ec/internal.h
+++ b/crypto/fipsmodule/ec/internal.h
@@ -729,10 +729,6 @@
EC_POINT *pub_key;
EC_WRAPPED_SCALAR *priv_key;
- // fixed_k may contain a specific value of 'k', to be used in ECDSA signing.
- // This is only for the FIPS power-on tests.
- BIGNUM *fixed_k;
-
unsigned int enc_flag;
point_conversion_form_t conv_form;
diff --git a/crypto/fipsmodule/ecdsa/ecdsa.c b/crypto/fipsmodule/ecdsa/ecdsa.c
index e519a69..591f1bc 100644
--- a/crypto/fipsmodule/ecdsa/ecdsa.c
+++ b/crypto/fipsmodule/ecdsa/ecdsa.c
@@ -61,9 +61,10 @@
#include <openssl/sha.h>
#include <openssl/type_check.h>
+#include "../../internal.h"
#include "../bn/internal.h"
#include "../ec/internal.h"
-#include "../../internal.h"
+#include "internal.h"
// digest_to_scalar interprets |digest_len| bytes from |digest| as a scalar for
@@ -261,6 +262,41 @@
return ret;
}
+ECDSA_SIG *ecdsa_sign_with_nonce_for_known_answer_test(const uint8_t *digest,
+ size_t digest_len,
+ const EC_KEY *eckey,
+ const uint8_t *nonce,
+ size_t nonce_len) {
+ if (eckey->ecdsa_meth && eckey->ecdsa_meth->sign) {
+ OPENSSL_PUT_ERROR(ECDSA, ECDSA_R_NOT_IMPLEMENTED);
+ return NULL;
+ }
+
+ const EC_GROUP *group = EC_KEY_get0_group(eckey);
+ if (group == NULL || eckey->priv_key == NULL) {
+ OPENSSL_PUT_ERROR(ECDSA, ERR_R_PASSED_NULL_PARAMETER);
+ return NULL;
+ }
+ const EC_SCALAR *priv_key = &eckey->priv_key->scalar;
+
+ EC_SCALAR k;
+ if (!ec_scalar_from_bytes(group, &k, nonce, nonce_len)) {
+ return NULL;
+ }
+ int retry_ignored;
+ return ecdsa_sign_impl(group, &retry_ignored, priv_key, &k, digest,
+ digest_len);
+}
+
+// This function is only exported for testing and is not called in production
+// code.
+ECDSA_SIG *ECDSA_sign_with_nonce_and_leak_private_key_for_testing(
+ const uint8_t *digest, size_t digest_len, const EC_KEY *eckey,
+ const uint8_t *nonce, size_t nonce_len) {
+ return ecdsa_sign_with_nonce_for_known_answer_test(digest, digest_len, eckey,
+ nonce, nonce_len);
+}
+
ECDSA_SIG *ECDSA_do_sign(const uint8_t *digest, size_t digest_len,
const EC_KEY *eckey) {
if (eckey->ecdsa_meth && eckey->ecdsa_meth->sign) {
@@ -276,30 +312,21 @@
const BIGNUM *order = EC_GROUP_get0_order(group);
const EC_SCALAR *priv_key = &eckey->priv_key->scalar;
+ // Pass a SHA512 hash of the private key and digest as additional data
+ // into the RBG. This is a hardening measure against entropy failure.
+ OPENSSL_STATIC_ASSERT(SHA512_DIGEST_LENGTH >= 32,
+ "additional_data is too large for SHA-512");
+ SHA512_CTX sha;
+ uint8_t additional_data[SHA512_DIGEST_LENGTH];
+ SHA512_Init(&sha);
+ SHA512_Update(&sha, priv_key->words, order->width * sizeof(BN_ULONG));
+ SHA512_Update(&sha, digest, digest_len);
+ SHA512_Final(additional_data, &sha);
+
for (;;) {
EC_SCALAR k;
- if (eckey->fixed_k != NULL) {
- if (!ec_bignum_to_scalar(group, &k, eckey->fixed_k)) {
- return NULL;
- }
- if (ec_scalar_is_zero(group, &k)) {
- OPENSSL_PUT_ERROR(ECDSA, ERR_R_INTERNAL_ERROR);
- return NULL;
- }
- } else {
- // Pass a SHA512 hash of the private key and digest as additional data
- // into the RBG. This is a hardening measure against entropy failure.
- OPENSSL_STATIC_ASSERT(SHA512_DIGEST_LENGTH >= 32,
- "additional_data is too large for SHA-512");
- SHA512_CTX sha;
- uint8_t additional_data[SHA512_DIGEST_LENGTH];
- SHA512_Init(&sha);
- SHA512_Update(&sha, priv_key->words, order->width * sizeof(BN_ULONG));
- SHA512_Update(&sha, digest, digest_len);
- SHA512_Final(additional_data, &sha);
- if (!ec_random_nonzero_scalar(group, &k, additional_data)) {
- return NULL;
- }
+ if (!ec_random_nonzero_scalar(group, &k, additional_data)) {
+ return NULL;
}
int retry;
diff --git a/crypto/fipsmodule/ecdsa/ecdsa_test.cc b/crypto/fipsmodule/ecdsa/ecdsa_test.cc
index 95e26cf..18fdb83 100644
--- a/crypto/fipsmodule/ecdsa/ecdsa_test.cc
+++ b/crypto/fipsmodule/ecdsa/ecdsa_test.cc
@@ -434,8 +434,8 @@
ASSERT_TRUE(x);
bssl::UniquePtr<BIGNUM> y = GetBIGNUM(t, "Y");
ASSERT_TRUE(y);
- bssl::UniquePtr<BIGNUM> k = GetBIGNUM(t, "K");
- ASSERT_TRUE(k);
+ std::vector<uint8_t> k;
+ ASSERT_TRUE(t->GetBytes(&k, "K"));
bssl::UniquePtr<BIGNUM> r = GetBIGNUM(t, "R");
ASSERT_TRUE(r);
bssl::UniquePtr<BIGNUM> s = GetBIGNUM(t, "S");
@@ -454,10 +454,9 @@
ASSERT_TRUE(EC_KEY_set_public_key(key.get(), pub_key.get()));
ASSERT_TRUE(EC_KEY_check_key(key.get()));
- // Set the fixed k for testing purposes.
- key->fixed_k = k.release();
bssl::UniquePtr<ECDSA_SIG> sig(
- ECDSA_do_sign(digest.data(), digest.size(), key.get()));
+ ECDSA_sign_with_nonce_and_leak_private_key_for_testing(
+ digest.data(), digest.size(), key.get(), k.data(), k.size()));
ASSERT_TRUE(sig);
EXPECT_EQ(0, BN_cmp(r.get(), sig->r));
diff --git a/crypto/fipsmodule/ecdsa/internal.h b/crypto/fipsmodule/ecdsa/internal.h
new file mode 100644
index 0000000..5115dfa
--- /dev/null
+++ b/crypto/fipsmodule/ecdsa/internal.h
@@ -0,0 +1,39 @@
+/* Copyright (c) 2021, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+#ifndef OPENSSL_HEADER_CRYPTO_FIPSMODULE_ECDSA_INTERNAL_H
+#define OPENSSL_HEADER_CRYPTO_FIPSMODULE_ECDSA_INTERNAL_H
+
+#include <openssl/base.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+
+// ecdsa_sign_with_nonce_for_known_answer_test behaves like |ECDSA_do_sign| but
+// takes a fixed nonce. This function is used as part of known-answer tests in
+// the FIPS module.
+ECDSA_SIG *ecdsa_sign_with_nonce_for_known_answer_test(const uint8_t *digest,
+ size_t digest_len,
+ const EC_KEY *eckey,
+ const uint8_t *nonce,
+ size_t nonce_len);
+
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif // OPENSSL_HEADER_CRYPTO_FIPSMODULE_ECDSA_INTERNAL_H
diff --git a/crypto/fipsmodule/self_check/self_check.c b/crypto/fipsmodule/self_check/self_check.c
index 638500b..39570bd 100644
--- a/crypto/fipsmodule/self_check/self_check.c
+++ b/crypto/fipsmodule/self_check/self_check.c
@@ -32,6 +32,7 @@
#include "../../internal.h"
#include "../ec/internal.h"
+#include "../ecdsa/internal.h"
#include "../rand/internal.h"
#include "../tls/internal.h"
@@ -727,14 +728,12 @@
// ECDSA Sign/Verify KAT
// The 'k' value for ECDSA is fixed to avoid an entropy draw.
- ec_key->fixed_k = BN_new();
- if (ec_key->fixed_k == NULL ||
- !BN_set_word(ec_key->fixed_k, 42)) {
- fprintf(stderr, "Out of memory\n");
- goto err;
- }
+ uint8_t ecdsa_k[32] = {0};
+ ecdsa_k[31] = 42;
- sig = ECDSA_do_sign(kPlaintextSHA256, sizeof(kPlaintextSHA256), ec_key);
+ sig = ecdsa_sign_with_nonce_for_known_answer_test(
+ kPlaintextSHA256, sizeof(kPlaintextSHA256), ec_key, ecdsa_k,
+ sizeof(ecdsa_k));
uint8_t ecdsa_r_bytes[sizeof(kECDSASigR)];
uint8_t ecdsa_s_bytes[sizeof(kECDSASigS)];
diff --git a/include/openssl/ecdsa.h b/include/openssl/ecdsa.h
index 9cd19a5..08950e3 100644
--- a/include/openssl/ecdsa.h
+++ b/include/openssl/ecdsa.h
@@ -162,6 +162,25 @@
OPENSSL_EXPORT size_t ECDSA_SIG_max_len(size_t order_len);
+// Testing-only functions.
+
+// ECDSA_sign_with_nonce_and_leak_private_key_for_testing behaves like
+// |ECDSA_do_sign| but uses |nonce| for the ECDSA nonce 'k', instead of a random
+// value. |nonce| is interpreted as a big-endian integer. It must be reduced
+// modulo the group order and padded with zeros up to |BN_num_bytes(order)|
+// bytes.
+//
+// WARNING: This function is only exported for testing purposes, when using test
+// vectors or fuzzing strategies. It must not be used outside tests and may leak
+// any private keys it is used with.
+OPENSSL_EXPORT ECDSA_SIG *
+ECDSA_sign_with_nonce_and_leak_private_key_for_testing(const uint8_t *digest,
+ size_t digest_len,
+ const EC_KEY *eckey,
+ const uint8_t *nonce,
+ size_t nonce_len);
+
+
// Deprecated functions.
// d2i_ECDSA_SIG parses an ASN.1, DER-encoded, signature from |len| bytes at