439 lines
34 KiB
Diff
439 lines
34 KiB
Diff
diff --git a/crypto/ecdsa/ecdsa.c b/crypto/ecdsa/ecdsa.c
|
|
index 6938325..2b30f51 100644
|
|
--- a/crypto/ecdsa/ecdsa.c
|
|
+++ b/crypto/ecdsa/ecdsa.c
|
|
@@ -105,7 +105,7 @@ err:
|
|
/* digest_to_bn interprets |digest_len| bytes from |digest| as a big-endian
|
|
* number and sets |out| to that value. It then truncates |out| so that it's,
|
|
* at most, as long as |order|. It returns one on success and zero otherwise. */
|
|
-static int digest_to_bn(BIGNUM *out, const uint8_t *digest, size_t digest_len,
|
|
+int digest_to_bn(BIGNUM *out, const uint8_t *digest, size_t digest_len,
|
|
const BIGNUM *order) {
|
|
size_t num_bits;
|
|
|
|
diff --git a/tool/CMakeLists.txt b/tool/CMakeLists.txt
|
|
index f0af283..8889960 100644
|
|
--- a/tool/CMakeLists.txt
|
|
+++ b/tool/CMakeLists.txt
|
|
@@ -9,6 +9,7 @@ add_executable(
|
|
const.cc
|
|
digest.cc
|
|
generate_ed25519.cc
|
|
+ generate_tests.cc
|
|
genrsa.cc
|
|
pkcs12.cc
|
|
rand.cc
|
|
diff --git a/tool/generate_tests.cc b/tool/generate_tests.cc
|
|
new file mode 100644
|
|
index 0000000..6831af0
|
|
--- /dev/null
|
|
+++ b/tool/generate_tests.cc
|
|
@@ -0,0 +1,383 @@
|
|
+/* Copyright 2016 Brian Smith.
|
|
+ *
|
|
+ * 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. */
|
|
+
|
|
+// Generate test vectors for ECDSA signature verification edge cases.
|
|
+
|
|
+#include <openssl/ec.h>
|
|
+
|
|
+#include <assert.h>
|
|
+#include <vector>
|
|
+
|
|
+#include "../crypto/ec/internal.h"
|
|
+#include "../crypto/test/scoped_types.h"
|
|
+
|
|
+#include "internal.h"
|
|
+
|
|
+extern "C" int digest_to_bn(BIGNUM *out, const uint8_t *digest,
|
|
+ size_t digest_len, const BIGNUM *order);
|
|
+
|
|
+
|
|
+void print_hex(FILE *f, const uint8_t *data, size_t len) {
|
|
+ for (size_t i = 0; i < len; i++) {
|
|
+ fprintf(f, "%02x", data[i]);
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+static bool GenerateMaxwellTestsForCurveR(const EC_GROUP *group,
|
|
+ const char *curve_name,
|
|
+ const BIGNUM *r,
|
|
+ const BIGNUM *r_override, BN_CTX *ctx,
|
|
+ const char *comment) {
|
|
+ ScopedEC_POINT pub_key(EC_POINT_new(group));
|
|
+ if (!pub_key ||
|
|
+ !EC_POINT_set_compressed_coordinates_GFp(
|
|
+ group, pub_key.get(), (r_override ? r_override : r), 0, NULL)) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ uint8_t *sig_bytes = NULL;
|
|
+ size_t sig_bytes_len = 0;
|
|
+ ScopedECDSA_SIG sig(ECDSA_SIG_new());
|
|
+ if (!sig ||
|
|
+ !BN_nnmod(sig->r, r, EC_GROUP_get0_order(group), ctx) ||
|
|
+ !BN_set_word(sig->s, 4) || // Arbitrarily chosen.
|
|
+ !ECDSA_SIG_to_bytes(&sig_bytes, &sig_bytes_len, sig.get())) {
|
|
+ return false;
|
|
+ }
|
|
+ ScopedOpenSSLBytes sig_bytes_free(sig_bytes);
|
|
+
|
|
+ // Any message will do.
|
|
+ uint8_t digest[32];
|
|
+ if (SHA256((const uint8_t *)"", 0, digest) == NULL) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ ScopedBIGNUM z_neg(BN_new());
|
|
+ if (!z_neg ||
|
|
+ !digest_to_bn(z_neg.get(), digest, sizeof(digest),
|
|
+ EC_GROUP_get0_order(group))) {
|
|
+ return false;
|
|
+ }
|
|
+ BN_set_negative(z_neg.get(), true);
|
|
+
|
|
+ ScopedEC_POINT intermediate(EC_POINT_new(group));
|
|
+ if (!intermediate ||
|
|
+ !EC_POINT_mul(group, intermediate.get(), z_neg.get(), pub_key.get(),
|
|
+ sig->s, NULL)) {
|
|
+ return false;
|
|
+ }
|
|
+ ScopedBIGNUM r_inv(BN_new());
|
|
+ if (!r_inv ||
|
|
+ BN_mod_inverse(r_inv.get(), r, EC_GROUP_get0_order(group), ctx) == NULL) {
|
|
+ return false;
|
|
+ }
|
|
+ ScopedEC_POINT result(EC_POINT_new(group));
|
|
+ if (!result ||
|
|
+ !EC_POINT_mul(group, result.get(), NULL, intermediate.get(), r_inv.get(),
|
|
+ NULL)) {
|
|
+ return false;
|
|
+ }
|
|
+ uint8_t pub_key_encoded[1024];
|
|
+ size_t pub_key_encoded_len =
|
|
+ EC_POINT_point2oct(group, result.get(), POINT_CONVERSION_UNCOMPRESSED,
|
|
+ pub_key_encoded, sizeof(pub_key_encoded), NULL);
|
|
+ if (pub_key_encoded_len == 0) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ printf("\n");
|
|
+ printf("%s\n", comment);
|
|
+ printf("Curve = %s\n", curve_name);
|
|
+ printf("Digest = SHA256\n");
|
|
+ printf("Msg = \"\"\n");
|
|
+ printf("Q = ");
|
|
+ print_hex(stdout, pub_key_encoded, pub_key_encoded_len);
|
|
+ printf("\n");
|
|
+ printf("Sig = ");
|
|
+ print_hex(stdout, sig_bytes, sig_bytes_len);
|
|
+ printf("\n");
|
|
+ printf("Result = %s\n", (r_override == NULL) ? "P (0 )" : "F");
|
|
+
|
|
+ return true;
|
|
+}
|
|
+
|
|
+static bool GenerateMaxwellTestsForCurve(int nid, const char *curve_name,
|
|
+ BN_ULONG r_word, BN_ULONG offset,
|
|
+ BN_CTX *ctx) {
|
|
+ ScopedEC_GROUP group(EC_GROUP_new_by_curve_name(nid));
|
|
+ ScopedBIGNUM r(BN_new());
|
|
+ ScopedBIGNUM q(BN_new());
|
|
+ ScopedBIGNUM q_minus_n(BN_new());
|
|
+ ScopedBIGNUM q_minus_n_ish(BN_new());
|
|
+ ScopedBIGNUM wrong_r(BN_new());
|
|
+ if (!group || !r || !q || !q_minus_n || !q_minus_n_ish || !wrong_r ||
|
|
+ !EC_GROUP_get_curve_GFp(group.get(), q.get(), NULL, NULL, NULL) ||
|
|
+ !BN_sub(q_minus_n.get(), q.get(), EC_GROUP_get0_order(group.get())) ||
|
|
+ !BN_copy(q_minus_n_ish.get(), q_minus_n.get()) ||
|
|
+ !BN_add_word(q_minus_n_ish.get(), offset) ||
|
|
+ !BN_mod_add(wrong_r.get(), q_minus_n_ish.get(),
|
|
+ EC_GROUP_get0_order(group.get()), q.get(), ctx)) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ if (!BN_set_word(r.get(), r_word) ||
|
|
+ !GenerateMaxwellTestsForCurveR(group.get(), curve_name, r.get(), NULL, ctx,
|
|
+ "# The signature has r < q - n. This is the control case for the next\n"
|
|
+ "# test case; this signature is the same but the public key is\n"
|
|
+ "# different. Notice that both public keys work for the same signature!\n"
|
|
+ "# This signature will validate even if the implementation doesn't\n"
|
|
+ "# reduce the X coordinate of the multiplication result (mod n).")) {
|
|
+ return false;
|
|
+ }
|
|
+ if (!BN_add(r.get(), r.get(), EC_GROUP_get0_order(group.get())) ||
|
|
+ !GenerateMaxwellTestsForCurveR(group.get(), curve_name, r.get(), NULL, ctx,
|
|
+ "# The signature has r < q - n. s Since r < q - n, r + n < q. Notice\n"
|
|
+ "# that this signature is the same as the signature in the preceding\n"
|
|
+ "# test case, but the public key is different. That the signature\n"
|
|
+ "# validates for this case too is what's special about the case where\n"
|
|
+ "# r < q - n. If this test case fails it is likely that the\n"
|
|
+ "# implementation doesn't reduce the X coordinate of the multiplication\n"
|
|
+ "# result (mod n), or it is missing the second step of Gregory\n"
|
|
+ "# Maxwell's trick.")) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ if (!GenerateMaxwellTestsForCurveR(group.get(), curve_name,
|
|
+ q_minus_n_ish.get(), NULL, ctx,
|
|
+ "# The signature has r > q - n. The signature is for the public key\n"
|
|
+ "# recovered from r. r + n > q since r > q - n. This is the control\n"
|
|
+ "# for the next test case; this signature is the same as the signature\n"
|
|
+ "# in the following test case but the public key is different.")) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ if (!GenerateMaxwellTestsForCurveR(group.get(), curve_name,
|
|
+ q_minus_n_ish.get(), wrong_r.get(), ctx,
|
|
+ "# The signature has r > q - n. The signature is for the public key\n"
|
|
+ "# recovered from r + n (mod q). r + n > q since r > q - n, and so\n"
|
|
+ "# r + n (mod q) < r because r + n (mod n) != r + n (mod q). Notice\n"
|
|
+ "# that this signature is the same as the signature in the preceding\n"
|
|
+ "# test case but the public key is different. Also, notice that the\n"
|
|
+ "# signature fails to validate in this case, unlike other related test\n"
|
|
+ "# cases. If this test case fails (the signature validates), it is\n"
|
|
+ "# likely that the implementation didn't guard the second case of\n"
|
|
+ "# Gregory Maxwell's trick on the condition r < q - n.")) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ return true;
|
|
+}
|
|
+
|
|
+static bool GenerateTestsMaxwell(BN_CTX *ctx) {
|
|
+ printf(
|
|
+ "# Test vectors for Gregory Maxwell's trick.\n"
|
|
+ "#\n"
|
|
+ "# These test vectors were generated by applying the patch in\n"
|
|
+ "# util/generate-tests.patch to BoringSSL, and then running\n"
|
|
+ "# `bssl generate-tests maxwell`.\n"
|
|
+ "#\n"
|
|
+ "# In all cases, the `s` component of the signature was selected\n"
|
|
+ "# arbitrarily as 4 and then the `r` component was chosen to be the\n"
|
|
+ "# smallest value where the public key recovery from the signature\n"
|
|
+ "# works.\n");
|
|
+
|
|
+
|
|
+ // The numbers (6, 0) and (3, 2) were determined using the guess-and-check
|
|
+ // method. Using smaller/different numbers causes the public key recovery
|
|
+ // from the signature to fail.
|
|
+ if (!GenerateMaxwellTestsForCurve(NID_X9_62_prime256v1, "P-256", 6, 0, ctx) ||
|
|
+ !GenerateMaxwellTestsForCurve(NID_secp384r1, "P-384", 3, 2, ctx)) {
|
|
+ return false;
|
|
+ }
|
|
+ return true;
|
|
+}
|
|
+
|
|
+static void GenerateECCPublicKeyTestEncoded(const char *curve_name,
|
|
+ const uint8_t *pub_key_encoded,
|
|
+ size_t pub_key_encoded_len,
|
|
+ const char *result,
|
|
+ const char *comment) {
|
|
+ printf("\n");
|
|
+ printf("%s\n", comment);
|
|
+ printf("Curve = %s\n", curve_name);
|
|
+ printf("Q = ");
|
|
+ print_hex(stdout, pub_key_encoded, pub_key_encoded_len);
|
|
+ printf("\n");
|
|
+ printf("Result = %s\n", result);
|
|
+}
|
|
+
|
|
+static bool GenerateECCPublicKeyTest(const char *curve_name,
|
|
+ const EC_GROUP *group,
|
|
+ const EC_POINT *point, const char *result,
|
|
+ const char *comment) {
|
|
+ uint8_t pub_key_encoded[1024];
|
|
+ size_t pub_key_encoded_len =
|
|
+ EC_POINT_point2oct(group, point, POINT_CONVERSION_UNCOMPRESSED,
|
|
+ pub_key_encoded, sizeof(pub_key_encoded), NULL);
|
|
+ if (pub_key_encoded_len == 0) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ GenerateECCPublicKeyTestEncoded(curve_name, pub_key_encoded,
|
|
+ pub_key_encoded_len, result, comment);
|
|
+
|
|
+ return true;
|
|
+}
|
|
+
|
|
+static bool GenerateECCPublicKeyTestWithAffineDecodedCoordinates(
|
|
+ const char *curve_name, const EC_GROUP *group, const BIGNUM *x,
|
|
+ const BIGNUM *y, const char *result, const char *comment) {
|
|
+ unsigned coord_len = (EC_GROUP_get_degree(group) + 7) / 8;
|
|
+
|
|
+ uint8_t pub_key_encoded[1024];
|
|
+ size_t pub_key_encoded_len = 1 + (2 * coord_len);
|
|
+ assert(pub_key_encoded_len <= sizeof(pub_key_encoded));
|
|
+
|
|
+ pub_key_encoded[0] = 0x04; // Uncompressed
|
|
+ if (!BN_bn2bin_padded(&pub_key_encoded[1], coord_len, x) ||
|
|
+ !BN_bn2bin_padded(&pub_key_encoded[1 + coord_len], coord_len, y)) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ GenerateECCPublicKeyTestEncoded(curve_name, pub_key_encoded,
|
|
+ pub_key_encoded_len, result, comment);
|
|
+
|
|
+ return true;
|
|
+}
|
|
+
|
|
+static bool GenerateECCPublicKeyTestsForCurve(int nid, const char *curve_name,
|
|
+ BN_CTX *ctx) {
|
|
+ ScopedEC_GROUP group(EC_GROUP_new_by_curve_name(nid));
|
|
+ ScopedBIGNUM q(BN_new());
|
|
+ if (!group ||
|
|
+ !q ||
|
|
+ !EC_GROUP_get_curve_GFp(group.get(), q.get(), NULL, NULL, NULL)) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ ScopedEC_POINT point(EC_POINT_new(group.get()));
|
|
+ if (!point) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ ScopedBIGNUM zero(BN_new());
|
|
+ if (!zero) {
|
|
+ return false;
|
|
+ }
|
|
+ BN_zero(zero.get());
|
|
+ ScopedBIGNUM y(BN_new());
|
|
+ if (!y) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ if (!EC_POINT_set_compressed_coordinates_GFp(group.get(), point.get(),
|
|
+ zero.get(), 0, ctx) ||
|
|
+ !GenerateECCPublicKeyTest(curve_name, group.get(), point.get(), "P",
|
|
+ "# X == 0, decompressed with y_bit == 0. This verifies that the\n"
|
|
+ "# implementation doesn't reject zero-valued field elements (they\n"
|
|
+ "# aren't scalars).")) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ if (!EC_POINT_get_affine_coordinates_GFp(group.get(), point.get(), NULL,
|
|
+ y.get(), ctx) ||
|
|
+ !GenerateECCPublicKeyTestWithAffineDecodedCoordinates(curve_name,
|
|
+ group.get(),
|
|
+ q.get(), y.get(),
|
|
+ "F (X is out of range)",
|
|
+ "# X == q. This is invalid because q isn't a valid field element. Some\n"
|
|
+ "# broken implementations might accept this if they reduce X mod q\n"
|
|
+ "# since q mod q == 0 and the Y coordinate matches the one from the\n"
|
|
+ "# x == 0 test case above.")) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ if (!EC_POINT_set_compressed_coordinates_GFp(group.get(), point.get(),
|
|
+ zero.get(), 1, ctx) ||
|
|
+ !GenerateECCPublicKeyTest(curve_name, group.get(), point.get(), "P",
|
|
+ "# X == 0, decompressed with y_bit == 1.")) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ if (!EC_POINT_get_affine_coordinates_GFp(group.get(), point.get(), NULL,
|
|
+ y.get(), ctx) ||
|
|
+ !GenerateECCPublicKeyTestWithAffineDecodedCoordinates(curve_name,
|
|
+ group.get(),
|
|
+ q.get(), y.get(),
|
|
+ "F (X is out of range)",
|
|
+ "# X == q, decompressed with y_bit == 1. See the previous X == q test\n"
|
|
+ "# case.")) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ // Find the largest valid x coordinate for the curve.
|
|
+ // XXX: Assumes EC_POINT_set_compressed_coordinates_GFp won't fail for any
|
|
+ // reason other than the X value not resulting in X**3 + a*x + b being a
|
|
+ // perfect square.
|
|
+ ScopedBIGNUM largest_x(BN_new());
|
|
+ if (!BN_copy(largest_x.get(), q.get())) {
|
|
+ return false;
|
|
+ }
|
|
+ do {
|
|
+ if (!BN_sub_word(largest_x.get(), 1)) {
|
|
+ return false;
|
|
+ }
|
|
+ } while (!EC_POINT_set_compressed_coordinates_GFp(group.get(), point.get(),
|
|
+ largest_x.get(), 0, ctx));
|
|
+ if (!GenerateECCPublicKeyTest(curve_name, group.get(), point.get(), "P",
|
|
+ "# The largest valid X coordinate, decompressed with y_bit == 0. This\n"
|
|
+ "# helps ensure that the upper bound on coordinate values is not too\n"
|
|
+ "# low.")) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ return true;
|
|
+}
|
|
+
|
|
+static bool GenerateECCPublicKeyTests(BN_CTX *ctx) {
|
|
+ printf(
|
|
+ "# Test vectors for Public Key Point Validation.\n"
|
|
+ "#\n"
|
|
+ "# These test vectors were generated by applying the patch in\n"
|
|
+ "# util/generate-tests.patch to BoringSSL, and then running\n"
|
|
+ "# `bssl generate-tests ecc-public-key`.\n"
|
|
+ "#\n");
|
|
+
|
|
+ if (!GenerateECCPublicKeyTestsForCurve(NID_X9_62_prime256v1, "P-256", ctx) ||
|
|
+ !GenerateECCPublicKeyTestsForCurve(NID_secp384r1, "P-384", ctx)) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ return true;
|
|
+}
|
|
+
|
|
+bool GenerateTests(const std::vector<std::string> &args) {
|
|
+ ScopedBN_CTX ctx(BN_CTX_new());
|
|
+ if (!ctx) {
|
|
+ return false;
|
|
+ }
|
|
+ if (args.size() == 0) {
|
|
+ printf("No test set specified.\n");
|
|
+ return false;
|
|
+ }
|
|
+ if (args[0] == "maxwell") {
|
|
+ return GenerateTestsMaxwell(ctx.get());
|
|
+ } else if (args[0] == "ecc-public-key") {
|
|
+ return GenerateECCPublicKeyTests(ctx.get());
|
|
+ } else {
|
|
+ printf("Unrecognized test set.\n");
|
|
+ return false;
|
|
+ }
|
|
+}
|
|
diff --git a/tool/internal.h b/tool/internal.h
|
|
index fd66e00..24b84a3 100644
|
|
--- a/tool/internal.h
|
|
+++ b/tool/internal.h
|
|
@@ -79,6 +79,7 @@ bool SHA384Sum(const std::vector<std::string> &args);
|
|
bool SHA512Sum(const std::vector<std::string> &args);
|
|
bool Server(const std::vector<std::string> &args);
|
|
bool Speed(const std::vector<std::string> &args);
|
|
+bool GenerateTests(const std::vector<std::string> &args);
|
|
|
|
// These values are DER encoded, RSA private keys.
|
|
extern const uint8_t kDERRSAPrivate2048[];
|
|
diff --git a/tool/tool.cc b/tool/tool.cc
|
|
index 34851b4..0057a0f 100644
|
|
--- a/tool/tool.cc
|
|
+++ b/tool/tool.cc
|
|
@@ -40,6 +40,7 @@ static const Tool kTools[] = {
|
|
{ "ciphers", Ciphers },
|
|
{ "client", Client },
|
|
{ "generate-ed25519", GenerateEd25519Key },
|
|
+ { "generate-tests", GenerateTests },
|
|
{ "genrsa", GenerateRSAKey },
|
|
{ "md5sum", MD5Sum },
|
|
{ "pkcs12", DoPKCS12 },
|