Merge pull request #10540 from Frauschi/small_order_check

Reject small-order public keys for Ed25519 and Ed448
This commit is contained in:
Sean Parkinson
2026-06-04 09:58:24 +10:00
committed by GitHub
7 changed files with 626 additions and 36 deletions
+181
View File
@@ -725,3 +725,184 @@ int test_wc_Ed25519KeyToDer_oneasymkey_version(void)
return EXPECT_RESULT();
}
/* Ed25519 identity and small-order public keys must be rejected. When
* the public key is the identity point (or any small-order point), any
* signature of the form (R = [S]B, S) verifies for arbitrary messages
* because h*A is the neutral element. Gated on FIPS_VERSION3_GE(7,0,0)
* because older FIPS-certified modules do not have this check in their
* frozen copy of ed25519.c and would fail this test. */
int test_wc_ed25519_reject_small_order_keys(void)
{
EXPECT_DECLS;
#if (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) && \
defined(HAVE_ED25519) && defined(HAVE_ED25519_KEY_IMPORT)
/* Each entry holds an encoded small-order Ed25519 public key. The
* sign-bit variants of each y-coordinate are listed explicitly so
* the test catches both possible encodings of each y. */
static const byte small_order_keys[][ED25519_PUB_KEY_SIZE] = {
/* identity (y = 1) */
{0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
/* identity with x-sign bit set */
{0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80},
/* order 2: y = p - 1 */
{0xec,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f},
/* order 2: y = p - 1 with x-sign bit set */
{0xec,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff},
/* non-canonical y = p (decodes to y = 0) */
{0xed,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f},
/* non-canonical y = p with x-sign bit set */
{0xed,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff},
/* non-canonical y = p + 1 (decodes to y = 1) */
{0xee,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f},
/* non-canonical y = p + 1 with x-sign bit set */
{0xee,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff},
/* order 4: y = 0 */
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
/* order 4 with x-sign bit set */
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80},
/* order 8 */
{0x26,0xe8,0x95,0x8f,0xc2,0xb2,0x27,0xb0,
0x45,0xc3,0xf4,0x89,0xf2,0xef,0x98,0xf0,
0xd5,0xdf,0xac,0x05,0xd3,0xc6,0x33,0x39,
0xb1,0x38,0x02,0x88,0x6d,0x53,0xfc,0x05},
/* order 8 with x-sign bit set */
{0x26,0xe8,0x95,0x8f,0xc2,0xb2,0x27,0xb0,
0x45,0xc3,0xf4,0x89,0xf2,0xef,0x98,0xf0,
0xd5,0xdf,0xac,0x05,0xd3,0xc6,0x33,0x39,
0xb1,0x38,0x02,0x88,0x6d,0x53,0xfc,0x85},
/* order 8 (other y) */
{0xc7,0x17,0x6a,0x70,0x3d,0x4d,0xd8,0x4f,
0xba,0x3c,0x0b,0x76,0x0d,0x10,0x67,0x0f,
0x2a,0x20,0x53,0xfa,0x2c,0x39,0xcc,0xc6,
0x4e,0xc7,0xfd,0x77,0x92,0xac,0x03,0x7a},
/* order 8 (other y) with x-sign bit set */
{0xc7,0x17,0x6a,0x70,0x3d,0x4d,0xd8,0x4f,
0xba,0x3c,0x0b,0x76,0x0d,0x10,0x67,0x0f,
0x2a,0x20,0x53,0xfa,0x2c,0x39,0xcc,0xc6,
0x4e,0xc7,0xfd,0x77,0x92,0xac,0x03,0xfa},
};
/* Forged signature: R = B (base point), S = 1.
* With public key A = identity, S*B - h*A = B = R for any message. */
static const byte forged_sig[ED25519_SIG_SIZE] = {
0x58,0x66,0x66,0x66,0x66,0x66,0x66,0x66,
0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,
0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,
0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,
0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};
ed25519_key key;
word32 i;
word32 num_keys = (word32)(sizeof(small_order_keys) / ED25519_PUB_KEY_SIZE);
/* (1) Untrusted wc_ed25519_import_public must reject every small-order
* encoding (it runs wc_ed25519_check_key as part of the import). */
for (i = 0; i < num_keys; i++) {
int rc;
XMEMSET(&key, 0, sizeof(key));
ExpectIntEQ(wc_ed25519_init(&key), 0);
rc = wc_ed25519_import_public(small_order_keys[i],
ED25519_PUB_KEY_SIZE, &key);
if (rc != WC_NO_ERR_TRACE(PUBLIC_KEY_E)) {
fprintf(stderr, "small_order_keys[%u]: import_public returned %d, "
"expected PUBLIC_KEY_E\n", (unsigned)i, rc);
}
ExpectIntEQ(rc, WC_NO_ERR_TRACE(PUBLIC_KEY_E));
wc_ed25519_free(&key);
}
/* (2) wc_ed25519_check_key called directly must also reject. Guards
* against a refactor that moves the small-order check out of
* check_key and into the import path: (1) would still pass, but the
* documented check_key contract would silently regress. */
for (i = 0; i < num_keys; i++) {
int rc;
XMEMSET(&key, 0, sizeof(key));
ExpectIntEQ(wc_ed25519_init(&key), 0);
/* trusted = 1 bypasses the import-time check_key call so the
* direct check_key below is what's under test. */
ExpectIntEQ(wc_ed25519_import_public_ex(small_order_keys[i],
ED25519_PUB_KEY_SIZE, &key, 1), 0);
rc = wc_ed25519_check_key(&key);
if (rc != WC_NO_ERR_TRACE(PUBLIC_KEY_E)) {
fprintf(stderr, "small_order_keys[%u]: check_key returned %d, "
"expected PUBLIC_KEY_E\n", (unsigned)i, rc);
}
ExpectIntEQ(rc, WC_NO_ERR_TRACE(PUBLIC_KEY_E));
wc_ed25519_free(&key);
}
/* (3) Even a "trusted" import (which bypasses wc_ed25519_check_key)
* must not let wc_ed25519_verify_msg accept a forged signature against
* an identity public key. Test both the canonical encoding (y = 1,
* small_order_keys[0]) and the non-canonical encoding (y = p + 1,
* small_order_keys[6]) so the verify-side check is exercised against
* the canonical-form bypass route, not just the byte-for-byte
* identity. The forged sig (R = B, S = 1) verifies for an identity
* public key only - other small-order points would reject it on the
* math alone, so they aren't useful here. */
{
static const word32 identity_indices[] = { 0, 6 };
const char* msg = "forged message";
word32 j;
for (j = 0;
j < sizeof(identity_indices)/sizeof(identity_indices[0]);
j++) {
word32 idx = identity_indices[j];
int verify_result = 1;
int rc;
XMEMSET(&key, 0, sizeof(key));
ExpectIntEQ(wc_ed25519_init(&key), 0);
ExpectIntEQ(wc_ed25519_import_public_ex(small_order_keys[idx],
ED25519_PUB_KEY_SIZE, &key, 1), 0);
rc = wc_ed25519_verify_msg(forged_sig, sizeof(forged_sig),
(const byte*)msg, (word32)XSTRLEN(msg), &verify_result, &key);
if (rc != WC_NO_ERR_TRACE(BAD_FUNC_ARG) || verify_result != 0) {
fprintf(stderr, "verify_msg with identity-equiv "
"small_order_keys[%u]: rc=%d verify_result=%d "
"(expected BAD_FUNC_ARG and 0)\n",
(unsigned)idx, rc, verify_result);
}
ExpectIntEQ(rc, WC_NO_ERR_TRACE(BAD_FUNC_ARG));
ExpectIntEQ(verify_result, 0);
wc_ed25519_free(&key);
}
}
#endif
return EXPECT_RESULT();
}
+3 -1
View File
@@ -37,6 +37,7 @@ int test_wc_Ed25519PublicKeyToDer(void);
int test_wc_Ed25519KeyToDer(void);
int test_wc_Ed25519PrivateKeyToDer(void);
int test_wc_Ed25519KeyToDer_oneasymkey_version(void);
int test_wc_ed25519_reject_small_order_keys(void);
#define TEST_ED25519_DECLS \
TEST_DECL_GROUP("ed25519", test_wc_ed25519_make_key), \
@@ -51,6 +52,7 @@ int test_wc_Ed25519KeyToDer_oneasymkey_version(void);
TEST_DECL_GROUP("ed25519", test_wc_Ed25519PublicKeyToDer), \
TEST_DECL_GROUP("ed25519", test_wc_Ed25519KeyToDer), \
TEST_DECL_GROUP("ed25519", test_wc_Ed25519PrivateKeyToDer), \
TEST_DECL_GROUP("ed25519", test_wc_Ed25519KeyToDer_oneasymkey_version)
TEST_DECL_GROUP("ed25519", test_wc_Ed25519KeyToDer_oneasymkey_version), \
TEST_DECL_GROUP("ed25519", test_wc_ed25519_reject_small_order_keys)
#endif /* WOLFCRYPT_TEST_ED25519_H */
+221
View File
@@ -649,3 +649,224 @@ int test_wc_Ed448KeyToDer_oneasymkey_version(void)
return EXPECT_RESULT();
}
/* Ed448 identity and small-order public keys must be rejected.
* Edwards448 has cofactor 4, so the small-order subgroup contains the
* identity, an order-2 point, and two order-4 points. With any of these
* as the public key, h*A is the neutral element and forged signatures
* verify for arbitrary messages. Gated on FIPS_VERSION3_GE(7,0,0)
* because older FIPS-certified modules do not have this check in their
* frozen copy of ed448.c. */
int test_wc_ed448_reject_small_order_keys(void)
{
EXPECT_DECLS;
#if (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) && \
defined(HAVE_ED448) && defined(HAVE_ED448_KEY_IMPORT)
/* Two regressions are guarded here. Both sign-bit variants of each
* y are listed so weakening the "clear all of byte 56" mask in
* ed448_is_small_order() would be caught. The non-canonical rows
* (y = p, y = p + 1) guard against dropping the canonical-form
* coverage: fe448_from_bytes reads bytes 0-55 modulo p with no
* canonical-form check, so y = p decodes to 0 and y = p + 1
* decodes to 1, both of which are small order. */
static const byte small_order_keys[][ED448_PUB_KEY_SIZE] = {
/* identity (y = 1), sign 0 */
{0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00},
/* identity (y = 1), sign bit set */
{0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x80},
/* order 4: y = 0, x-sign 0 */
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00},
/* order 4: y = 0, x-sign 1 */
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x80},
/* order 2: y = p - 1, x = 0, sign 0 */
{0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xfe,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0x00},
/* order 2: y = p - 1, sign bit set */
{0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xfe,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0x80},
/* non-canonical y = p (decodes to y = 0), sign 0 */
{0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xfe,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0x00},
/* non-canonical y = p, sign bit set */
{0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xfe,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0x80},
/* non-canonical y = p + 1 (decodes to y = 1), sign 0 */
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0x00},
/* non-canonical y = p + 1, sign bit set */
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0x80},
};
/* Arbitrary signature bytes: S = 1 (must be below the Ed448 group
* order or wc_ed448_verify_msg() returns BAD_FUNC_ARG before the
* small-order check has a chance to fire). The R bytes do not need
* to encode a valid curve point for this test - the small-order
* defence in ed448_verify_msg_final_with_sha() rejects the public
* key before the R/S verification math runs. */
static const byte forged_sig[ED448_SIG_SIZE] = {
/* R: 57 bytes of arbitrary data (last byte 0 to satisfy the
* spec-mandated zero of byte 56 bits 0-6; sign bit doesn't
* matter here). */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,
/* S = 1 */
0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00
};
ed448_key key;
word32 i;
word32 num_keys = (word32)(sizeof(small_order_keys) / ED448_PUB_KEY_SIZE);
/* (1) Untrusted wc_ed448_import_public must reject every small-order
* encoding (it runs wc_ed448_check_key as part of the import). */
for (i = 0; i < num_keys; i++) {
int rc;
XMEMSET(&key, 0, sizeof(key));
ExpectIntEQ(wc_ed448_init(&key), 0);
rc = wc_ed448_import_public(small_order_keys[i],
ED448_PUB_KEY_SIZE, &key);
if (rc != WC_NO_ERR_TRACE(PUBLIC_KEY_E)) {
fprintf(stderr, "small_order_keys[%u]: import_public returned %d, "
"expected PUBLIC_KEY_E\n", (unsigned)i, rc);
}
ExpectIntEQ(rc, WC_NO_ERR_TRACE(PUBLIC_KEY_E));
wc_ed448_free(&key);
}
/* (2) wc_ed448_check_key called directly must also reject. Guards
* against a refactor that moves the small-order check out of
* check_key and into the import path: (1) would still pass, but the
* documented check_key contract would silently regress. */
for (i = 0; i < num_keys; i++) {
int rc;
XMEMSET(&key, 0, sizeof(key));
ExpectIntEQ(wc_ed448_init(&key), 0);
/* trusted = 1 bypasses the import-time check_key call so the
* direct check_key below is what's under test. */
ExpectIntEQ(wc_ed448_import_public_ex(small_order_keys[i],
ED448_PUB_KEY_SIZE, &key, 1), 0);
rc = wc_ed448_check_key(&key);
if (rc != WC_NO_ERR_TRACE(PUBLIC_KEY_E)) {
fprintf(stderr, "small_order_keys[%u]: check_key returned %d, "
"expected PUBLIC_KEY_E\n", (unsigned)i, rc);
}
ExpectIntEQ(rc, WC_NO_ERR_TRACE(PUBLIC_KEY_E));
wc_ed448_free(&key);
}
/* (3) Even a "trusted" import (which bypasses wc_ed448_check_key)
* must not let wc_ed448_verify_msg accept a forged signature against
* an identity public key. Test both the canonical encoding (y = 1,
* small_order_keys[0]) and the non-canonical encoding (y = p + 1,
* small_order_keys[8]) so the verify-side check is exercised against
* the canonical-form bypass route, not just the byte-for-byte
* identity. */
{
static const word32 identity_indices[] = { 0, 8 };
const char* msg = "forged message";
word32 j;
for (j = 0;
j < sizeof(identity_indices)/sizeof(identity_indices[0]);
j++) {
word32 idx = identity_indices[j];
int verify_result = 1;
int rc;
XMEMSET(&key, 0, sizeof(key));
ExpectIntEQ(wc_ed448_init(&key), 0);
ExpectIntEQ(wc_ed448_import_public_ex(small_order_keys[idx],
ED448_PUB_KEY_SIZE, &key, 1), 0);
rc = wc_ed448_verify_msg(forged_sig, sizeof(forged_sig),
(const byte*)msg, (word32)XSTRLEN(msg), &verify_result,
&key, NULL, 0);
if (rc != WC_NO_ERR_TRACE(BAD_FUNC_ARG) || verify_result != 0) {
fprintf(stderr, "verify_msg with identity-equiv "
"small_order_keys[%u]: rc=%d verify_result=%d "
"(expected BAD_FUNC_ARG and 0)\n",
(unsigned)idx, rc, verify_result);
}
ExpectIntEQ(rc, WC_NO_ERR_TRACE(BAD_FUNC_ARG));
ExpectIntEQ(verify_result, 0);
wc_ed448_free(&key);
}
}
#endif
return EXPECT_RESULT();
}
+3 -1
View File
@@ -37,6 +37,7 @@ int test_wc_Ed448PublicKeyToDer(void);
int test_wc_Ed448KeyToDer(void);
int test_wc_Ed448PrivateKeyToDer(void);
int test_wc_Ed448KeyToDer_oneasymkey_version(void);
int test_wc_ed448_reject_small_order_keys(void);
#define TEST_ED448_DECLS \
TEST_DECL_GROUP("ed448", test_wc_ed448_make_key), \
@@ -51,6 +52,7 @@ int test_wc_Ed448KeyToDer_oneasymkey_version(void);
TEST_DECL_GROUP("ed448", test_wc_Ed448PublicKeyToDer), \
TEST_DECL_GROUP("ed448", test_wc_Ed448KeyToDer), \
TEST_DECL_GROUP("ed448", test_wc_Ed448PrivateKeyToDer), \
TEST_DECL_GROUP("ed448", test_wc_Ed448KeyToDer_oneasymkey_version)
TEST_DECL_GROUP("ed448", test_wc_Ed448KeyToDer_oneasymkey_version), \
TEST_DECL_GROUP("ed448", test_wc_ed448_reject_small_order_keys)
#endif /* WOLFCRYPT_TEST_ED448_H */
+72 -3
View File
@@ -205,6 +205,64 @@ static int ed25519_hash(ed25519_key* key, const byte* in, word32 inLen,
return ret;
}
/* Reject small-order Ed25519 public keys: h*A vanishes during verification
* so any (R = [S]B, S) verifies for an arbitrary message. */
static int ed25519_is_small_order(const byte p[ED25519_PUB_KEY_SIZE])
{
/* y-coordinates of every order-1/2/4/8 point plus the two non-canonical
* encodings y = p / y = p+1. Sign bit masked before compare. Only
* {y, y + p} fits in 32 bytes (2p overflows the 255-bit y field), so
* listing y and y + p exhausts the reachable encodings for each
* small-order y. */
static const byte small_order_y[][ED25519_PUB_KEY_SIZE] = {
/* order 4: y = 0 */
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
/* order 1: y = 1 (identity) */
{0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
/* order 8 */
{0x26,0xe8,0x95,0x8f,0xc2,0xb2,0x27,0xb0,
0x45,0xc3,0xf4,0x89,0xf2,0xef,0x98,0xf0,
0xd5,0xdf,0xac,0x05,0xd3,0xc6,0x33,0x39,
0xb1,0x38,0x02,0x88,0x6d,0x53,0xfc,0x05},
/* order 8 */
{0xc7,0x17,0x6a,0x70,0x3d,0x4d,0xd8,0x4f,
0xba,0x3c,0x0b,0x76,0x0d,0x10,0x67,0x0f,
0x2a,0x20,0x53,0xfa,0x2c,0x39,0xcc,0xc6,
0x4e,0xc7,0xfd,0x77,0x92,0xac,0x03,0x7a},
/* order 2: y = p - 1 */
{0xec,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f},
/* non-canonical y = p (decodes to y = 0) */
{0xed,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f},
/* non-canonical y = p + 1 (decodes to y = 1) */
{0xee,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f},
};
byte y[ED25519_PUB_KEY_SIZE];
word32 i;
XMEMCPY(y, p, ED25519_PUB_KEY_SIZE);
y[ED25519_PUB_KEY_SIZE - 1] &= 0x7f;
for (i = 0; i < sizeof(small_order_y) / ED25519_PUB_KEY_SIZE; i++) {
if (XMEMCMP(y, small_order_y[i], ED25519_PUB_KEY_SIZE) == 0)
return 1;
}
return 0;
}
#ifdef HAVE_ED25519_MAKE_KEY
#if FIPS_VERSION3_GE(6,0,0)
/* Performs a Pairwise Consistency Test on an Ed25519 key pair.
@@ -808,6 +866,13 @@ static int ed25519_verify_msg_final_with_sha(const byte* sig, word32 sigLen,
if (i == -1)
return BAD_FUNC_ARG;
/* Defence in depth: also catch small-order keys imported with trusted=1. */
if (ed25519_is_small_order(key->p)) {
WOLFSSL_MSG("Ed25519 small-order public key rejected during "
"signature verification");
return BAD_FUNC_ARG;
}
/* uncompress A (public key), test if valid, and negate it */
#ifndef FREESCALE_LTC_ECC
if (ge_frombytes_negate_vartime(&A, key->p) != 0)
@@ -1502,6 +1567,13 @@ int wc_ed25519_check_key(ed25519_key* key)
ret = PUBLIC_KEY_E;
}
/* Reject small-order pub key before the priv-vs-pub compare so the
* diagnostic isn't masked by a "mismatch" error. */
if ((ret == 0) && ed25519_is_small_order(key->p)) {
WOLFSSL_MSG("Ed25519 small-order public key rejected during key check");
ret = PUBLIC_KEY_E;
}
#ifdef HAVE_ED25519_MAKE_KEY
/* If we have a private key just make the public key and compare. */
if ((ret == 0) && (key->privKeySet)) {
@@ -1521,9 +1593,6 @@ int wc_ed25519_check_key(ed25519_key* key)
&& (!key->privKeySet)
#endif
) {
/* Verify that Q is not identity element 0.
* 0 has no representation for Ed25519. */
/* Verify that xQ and yQ are integers in the interval [0, p - 1].
* Only have yQ so check that ordinate. p = 2^255 - 19 */
if ((key->p[ED25519_PUB_KEY_SIZE - 1] & 0x7f) == 0x7f) {
+83 -27
View File
@@ -238,6 +238,77 @@ static int ed448_pairwise_consistency_test(ed448_key* key, WC_RNG* rng)
}
#endif
/* Reject small-order Ed448 public keys: h*A vanishes during verification
* so any (R = [S]B, S) verifies for an arbitrary message. Cofactor is 4. */
static int ed448_is_small_order(const byte p[ED448_PUB_KEY_SIZE])
{
/* y-coordinates of every order-1/2/4 point plus the non-canonical
* encodings y = p / y = p+1. Byte 56 is cleared in both table and
* input before compare, masking the x-sign bit and the
* spec-mandated-zero (but decoder-ignored) bits 0-6. The decoder
* (fe448_from_bytes) reads bytes 0-55 modulo p with no canonical-form
* check, so y = p decodes to 0 and y = p+1 decodes to 1; both must
* be rejected here. Only {y, y + p} fits in 56 bytes (2p overflows),
* so listing y and y + p exhausts the reachable encodings. */
static const byte small_order_y[][ED448_PUB_KEY_SIZE] = {
/* order 1: identity y = 1, x = 0 */
{0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00},
/* order 4: y = 0 (x = +/-1; sign bit covered by mask) */
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00},
/* order 2: y = p - 1, x = 0; p = 2^448 - 2^224 - 1 */
{0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xfe,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0x00},
/* non-canonical y = p (decodes to y = 0) */
{0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xfe,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0x00},
/* non-canonical y = p + 1 (decodes to y = 1) */
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0x00},
};
byte y[ED448_PUB_KEY_SIZE];
word32 i;
XMEMCPY(y, p, ED448_PUB_KEY_SIZE);
y[ED448_PUB_KEY_SIZE - 1] = 0;
for (i = 0; i < sizeof(small_order_y) / ED448_PUB_KEY_SIZE; i++) {
if (XMEMCMP(y, small_order_y[i], ED448_PUB_KEY_SIZE) == 0)
return 1;
}
return 0;
}
/* Derive the public key for the private key.
*
* key [in] Ed448 key object.
@@ -731,16 +802,11 @@ static int ed448_verify_msg_final_with_sha(const byte* sig, word32 sigLen,
if (i == -1)
return BAD_FUNC_ARG;
/* Reject identity public key (0,1): 0x01 followed by 56 zero bytes. */
{
int isIdentity = (key->p[0] == 0x01);
int j;
for (j = 1; j < ED448_PUB_KEY_SIZE && isIdentity; j++) {
if (key->p[j] != 0x00)
isIdentity = 0;
}
if (isIdentity)
return BAD_FUNC_ARG;
/* Defence in depth: also catch small-order keys imported with trusted=1. */
if (ed448_is_small_order(key->p)) {
WOLFSSL_MSG("Ed448 small-order public key rejected during "
"signature verification");
return BAD_FUNC_ARG;
}
/* uncompress A (public key), test if valid, and negate it */
@@ -1412,6 +1478,13 @@ int wc_ed448_check_key(ed448_key* key)
ret = PUBLIC_KEY_E;
}
/* Reject small-order pub key before the priv-vs-pub compare so the
* diagnostic isn't masked by a "mismatch" error. */
if ((ret == 0) && ed448_is_small_order(key->p)) {
WOLFSSL_MSG("Ed448 small-order public key rejected during key check");
ret = PUBLIC_KEY_E;
}
/* If we have a private key just make the public key and compare. */
if ((ret == 0) && key->privKeySet) {
ret = wc_ed448_make_public(key, pubKey, sizeof(pubKey));
@@ -1421,23 +1494,6 @@ int wc_ed448_check_key(ed448_key* key)
}
/* No private key, check Y is valid. */
else if ((ret == 0) && (!key->privKeySet)) {
/* Reject the identity element (0, 1).
* Encoding: 0x01 followed by 56 zero bytes. */
{
int isIdentity = 1;
int i;
if (key->p[0] != 0x01)
isIdentity = 0;
for (i = 1; i < ED448_PUB_KEY_SIZE && isIdentity; i++) {
if (key->p[i] != 0x00)
isIdentity = 0;
}
if (isIdentity) {
WOLFSSL_MSG("Ed448 public key is the identity element");
ret = PUBLIC_KEY_E;
}
}
/* Verify that xQ and yQ are integers in the interval [0, p - 1].
* Only have yQ so check that ordinate.
* p = 2^448-2^224-1 = 0xff..fe..ff
+63 -4
View File
@@ -44767,7 +44767,9 @@ static wc_test_ret_t ed25519_test_check_key(void)
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,
};
/* Y-ordinate value equal to prime - 1. */
/* Y-ordinate value equal to prime - 1. Older FIPS modules accept
* this as a valid key; the current source rejects it as an order-2
* point. */
WOLFSSL_SMALL_STACK_STATIC const byte key_y_is_p_minus_1[] = {
0x40,
0xec,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
@@ -44775,6 +44777,15 @@ static wc_test_ret_t ed25519_test_check_key(void)
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,
};
/* RFC 8032 section 7.1 test-vector public key: a genuinely valid
* Ed25519 point used as a positive control. */
WOLFSSL_SMALL_STACK_STATIC const byte key_good[] = {
0x40,
0xd7,0x5a,0x98,0x01,0x82,0xb1,0x0a,0xb7,
0xd5,0x4b,0xfe,0xd3,0xc9,0x64,0x07,0x3a,
0x0e,0xe1,0x72,0xf3,0xda,0xa6,0x23,0x25,
0xaf,0x02,0x1a,0x68,0xf7,0x07,0x51,0x1a,
};
ed25519_key key;
int ret;
int res = 0;
@@ -44807,9 +44818,26 @@ static wc_test_ret_t ed25519_test_check_key(void)
}
}
if (res == 0) {
/* Load good public key only and perform checks. */
ret = wc_ed25519_import_public(key_y_is_p_minus_1,
ED25519_PUB_KEY_SIZE + 1, &key);
#if !defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)
/* y = p - 1 is an order-2 point; check_key rejects it because
* h*A is the neutral element for small-order public keys and
* forged signatures would otherwise verify. */
if (ret != WC_NO_ERR_TRACE(PUBLIC_KEY_E)) {
res = WC_TEST_RET_ENC_NC;
}
#else
/* Older FIPS modules accept this order-2 point. */
if (ret != 0) {
res = WC_TEST_RET_ENC_NC;
}
#endif
}
if (res == 0) {
/* Positive control: a real Ed25519 public key must be accepted. */
ret = wc_ed25519_import_public(key_good, ED25519_PUB_KEY_SIZE + 1,
&key);
if (ret != 0) {
res = WC_TEST_RET_ENC_NC;
}
@@ -46484,7 +46512,9 @@ static wc_test_ret_t ed448_test_check_key(void)
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff
};
/* Y-ordinate value equal to prime - 1. */
/* Y-ordinate value equal to prime - 1. Older FIPS modules accept
* this as a valid key; the current source rejects it as an order-2
* point. */
WOLFSSL_SMALL_STACK_STATIC const byte key_y_is_p_minus_1[] = {
0x40,
0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
@@ -46496,6 +46526,19 @@ static wc_test_ret_t ed448_test_check_key(void)
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff
};
/* RFC 8032 section 7.4 test-vector public key: a genuinely valid
* Ed448 point used as a positive control. */
WOLFSSL_SMALL_STACK_STATIC const byte key_good[] = {
0x40,
0x5f,0xd7,0x44,0x9b,0x59,0xb4,0x61,0xfd,
0x2c,0xe7,0x87,0xec,0x61,0x6a,0xd4,0x6a,
0x1d,0xa1,0x34,0x24,0x85,0xa7,0x0e,0x1f,
0x8a,0x0e,0xa7,0x5d,0x80,0xe9,0x67,0x78,
0xed,0xf1,0x24,0x76,0x9b,0x46,0xc7,0x06,
0x1b,0xd6,0x78,0x3d,0xf1,0xe5,0x0f,0x6c,
0xd1,0xfa,0x1a,0xbe,0xaf,0xe8,0x25,0x61,
0x80
};
ed448_key key;
int ret;
int res = 0;
@@ -46528,9 +46571,25 @@ static wc_test_ret_t ed448_test_check_key(void)
}
}
if (res == 0) {
/* Load good public key only and perform checks. */
ret = wc_ed448_import_public(key_y_is_p_minus_1, ED448_PUB_KEY_SIZE + 1,
&key);
#if !defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)
/* y = p - 1 is an order-2 point; check_key rejects it because
* h*A is the neutral element for small-order public keys and
* forged signatures would otherwise verify. */
if (ret != WC_NO_ERR_TRACE(PUBLIC_KEY_E)) {
res = WC_TEST_RET_ENC_NC;
}
#else
/* Older FIPS modules accept this order-2 point. */
if (ret != 0) {
res = WC_TEST_RET_ENC_NC;
}
#endif
}
if (res == 0) {
/* Positive control: a real Ed448 public key must be accepted. */
ret = wc_ed448_import_public(key_good, ED448_PUB_KEY_SIZE + 1, &key);
if (ret != 0) {
res = WC_TEST_RET_ENC_NC;
}