From 2aacc18788f8633ea937efca257ea676128e4e0f Mon Sep 17 00:00:00 2001 From: Kareem Date: Thu, 30 Apr 2026 16:23:56 -0700 Subject: [PATCH] Enable all-zero shared secret check for curve448/25519 by default. Change macro to opt-out macro WOLFSSL_NO_ECDHX_SHARED_ZERO_CHECK. Add unit test. Thanks to Xiangdong Li for the report. --- .wolfssl_known_macro_extras | 2 +- tests/api/test_curve25519.c | 45 +++++++++++++++++++++++++++++++++++++ tests/api/test_curve25519.h | 2 ++ tests/api/test_curve448.c | 42 ++++++++++++++++++++++++++++++++++ tests/api/test_curve448.h | 2 ++ wolfcrypt/src/curve25519.c | 10 ++++----- wolfcrypt/src/curve448.c | 5 +++-- 7 files changed, 100 insertions(+), 8 deletions(-) diff --git a/.wolfssl_known_macro_extras b/.wolfssl_known_macro_extras index 583422bea4..67c0fa3983 100644 --- a/.wolfssl_known_macro_extras +++ b/.wolfssl_known_macro_extras @@ -755,7 +755,6 @@ WOLFSSL_ECC_BLIND_K WOLFSSL_ECC_GEN_REJECT_SAMPLING WOLFSSL_ECC_NO_SMALL_STACK WOLFSSL_ECC_SIGALG_PARAMS_NULL_ALLOWED -WOLFSSL_ECDHX_SHARED_NOT_ZERO WOLFSSL_ECDSA_MATCH_HASH WOLFSSL_ECDSA_SET_K_ONE_LOOP WOLFSSL_EC_POINT_CMP_JACOBIAN @@ -832,6 +831,7 @@ WOLFSSL_NO_DEL_HANDLE WOLFSSL_NO_DER_TO_PEM WOLFSSL_NO_DH186 WOLFSSL_NO_DTLS_SIZE_CHECK +WOLFSSL_NO_ECDHX_SHARED_ZERO_CHECK WOLFSSL_NO_ETM_ALERT WOLFSSL_NO_FENCE WOLFSSL_NO_ISSUERHASH_TDPEER diff --git a/tests/api/test_curve25519.c b/tests/api/test_curve25519.c index e6521c89db..4108ad0d34 100644 --- a/tests/api/test_curve25519.c +++ b/tests/api/test_curve25519.c @@ -353,6 +353,51 @@ int test_wc_curve25519_shared_secret_ex(void) return EXPECT_RESULT(); } /* END test_wc_curve25519_shared_secret_ex */ +/* + * Testing that wc_curve25519_shared_secret_ex rejects an all-zero shared + * secret (RFC 7748 section 6.1). This is the default behavior; users that + * need the legacy behavior can opt out with WOLFSSL_NO_ECDHX_SHARED_ZERO_CHECK. + */ +int test_wc_curve25519_shared_secret_zero_check(void) +{ + EXPECT_DECLS; +#if defined(HAVE_CURVE25519) && defined(HAVE_CURVE25519_KEY_IMPORT) && \ + !defined(WOLFSSL_NO_ECDHX_SHARED_ZERO_CHECK) + curve25519_key private_key; + curve25519_key public_key; + WC_RNG rng; + byte out[CURVE25519_KEYSIZE]; + word32 outLen = sizeof(out); + /* All-zero public key is a low-order point that yields an all-zero + * shared secret for any private key. */ + byte zero_pub[CURVE25519_KEYSIZE]; + + XMEMSET(&rng, 0, sizeof(WC_RNG)); + XMEMSET(zero_pub, 0, sizeof(zero_pub)); + + ExpectIntEQ(wc_curve25519_init(&private_key), 0); + ExpectIntEQ(wc_curve25519_init(&public_key), 0); + ExpectIntEQ(wc_InitRng(&rng), 0); +#ifdef WOLFSSL_CURVE25519_BLINDING + ExpectIntEQ(wc_curve25519_set_rng(&private_key, &rng), 0); +#endif + + ExpectIntEQ(wc_curve25519_make_key(&rng, CURVE25519_KEYSIZE, &private_key), + 0); + ExpectIntEQ(wc_curve25519_import_public_ex(zero_pub, sizeof(zero_pub), + &public_key, EC25519_LITTLE_ENDIAN), 0); + + ExpectIntEQ(wc_curve25519_shared_secret_ex(&private_key, &public_key, out, + &outLen, EC25519_BIG_ENDIAN), + WC_NO_ERR_TRACE(ECC_OUT_OF_RANGE_E)); + + DoExpectIntEQ(wc_FreeRng(&rng), 0); + wc_curve25519_free(&private_key); + wc_curve25519_free(&public_key); +#endif + return EXPECT_RESULT(); +} /* END test_wc_curve25519_shared_secret_zero_check */ + /* * Testing wc_curve25519_make_pub */ diff --git a/tests/api/test_curve25519.h b/tests/api/test_curve25519.h index 3d14bb53e3..c65ccb34f8 100644 --- a/tests/api/test_curve25519.h +++ b/tests/api/test_curve25519.h @@ -30,6 +30,7 @@ int test_wc_curve25519_export_key_raw(void); int test_wc_curve25519_export_key_raw_ex(void); int test_wc_curve25519_make_key(void); int test_wc_curve25519_shared_secret_ex(void); +int test_wc_curve25519_shared_secret_zero_check(void); int test_wc_curve25519_make_pub(void); int test_wc_curve25519_export_public_ex(void); int test_wc_curve25519_export_private_raw_ex(void); @@ -45,6 +46,7 @@ int test_wc_Curve25519KeyToDer_oneasymkey_version(void); TEST_DECL_GROUP("curve25519", test_wc_curve25519_export_key_raw_ex), \ TEST_DECL_GROUP("curve25519", test_wc_curve25519_make_key), \ TEST_DECL_GROUP("curve25519", test_wc_curve25519_shared_secret_ex), \ + TEST_DECL_GROUP("curve25519", test_wc_curve25519_shared_secret_zero_check),\ TEST_DECL_GROUP("curve25519", test_wc_curve25519_make_pub), \ TEST_DECL_GROUP("curve25519", test_wc_curve25519_export_public_ex), \ TEST_DECL_GROUP("curve25519", test_wc_curve25519_export_private_raw_ex), \ diff --git a/tests/api/test_curve448.c b/tests/api/test_curve448.c index bfc025d29b..2f0f652701 100644 --- a/tests/api/test_curve448.c +++ b/tests/api/test_curve448.c @@ -116,6 +116,48 @@ int test_wc_curve448_shared_secret_ex(void) return EXPECT_RESULT(); } /* END test_wc_curve448_shared_secret_ex */ +/* + * Testing that wc_curve448_shared_secret_ex rejects an all-zero shared + * secret (RFC 7748 section 6.2). This is the default behavior; users that + * need the legacy behavior can opt out with WOLFSSL_NO_ECDHX_SHARED_ZERO_CHECK. + */ +int test_wc_curve448_shared_secret_zero_check(void) +{ + EXPECT_DECLS; +#if defined(HAVE_CURVE448) && defined(HAVE_CURVE448_KEY_IMPORT) && \ + defined(HAVE_CURVE448_SHARED_SECRET) && \ + !defined(WOLFSSL_NO_ECDHX_SHARED_ZERO_CHECK) + curve448_key private_key; + curve448_key public_key; + WC_RNG rng; + byte out[CURVE448_KEY_SIZE]; + word32 outLen = sizeof(out); + /* All-zero public key is a low-order point that yields an all-zero + * shared secret for any private key. */ + byte zero_pub[CURVE448_PUB_KEY_SIZE]; + + XMEMSET(&rng, 0, sizeof(WC_RNG)); + XMEMSET(zero_pub, 0, sizeof(zero_pub)); + + ExpectIntEQ(wc_curve448_init(&private_key), 0); + ExpectIntEQ(wc_curve448_init(&public_key), 0); + ExpectIntEQ(wc_InitRng(&rng), 0); + + ExpectIntEQ(wc_curve448_make_key(&rng, CURVE448_KEY_SIZE, &private_key), 0); + ExpectIntEQ(wc_curve448_import_public_ex(zero_pub, sizeof(zero_pub), + &public_key, EC448_LITTLE_ENDIAN), 0); + + ExpectIntEQ(wc_curve448_shared_secret_ex(&private_key, &public_key, out, + &outLen, EC448_BIG_ENDIAN), + WC_NO_ERR_TRACE(ECC_OUT_OF_RANGE_E)); + + DoExpectIntEQ(wc_FreeRng(&rng), 0); + wc_curve448_free(&private_key); + wc_curve448_free(&public_key); +#endif + return EXPECT_RESULT(); +} /* END test_wc_curve448_shared_secret_zero_check */ + /* * Testing test_wc_curve448_export_public_ex */ diff --git a/tests/api/test_curve448.h b/tests/api/test_curve448.h index 93af7809fb..3ff1b4eed7 100644 --- a/tests/api/test_curve448.h +++ b/tests/api/test_curve448.h @@ -26,6 +26,7 @@ int test_wc_curve448_make_key(void); int test_wc_curve448_shared_secret_ex(void); +int test_wc_curve448_shared_secret_zero_check(void); int test_wc_curve448_export_public_ex(void); int test_wc_curve448_export_private_raw_ex(void); int test_wc_curve448_export_key_raw(void); @@ -39,6 +40,7 @@ int test_wc_Curve448PrivateKeyToDer_oneasymkey_version(void); #define TEST_CURVE448_DECLS \ TEST_DECL_GROUP("curve448", test_wc_curve448_make_key), \ TEST_DECL_GROUP("curve448", test_wc_curve448_shared_secret_ex), \ + TEST_DECL_GROUP("curve448", test_wc_curve448_shared_secret_zero_check), \ TEST_DECL_GROUP("curve448", test_wc_curve448_export_public_ex), \ TEST_DECL_GROUP("curve448", test_wc_curve448_export_private_raw_ex), \ TEST_DECL_GROUP("curve448", test_wc_curve448_export_key_raw), \ diff --git a/wolfcrypt/src/curve25519.c b/wolfcrypt/src/curve25519.c index a12ad9ee99..9ec92db147 100644 --- a/wolfcrypt/src/curve25519.c +++ b/wolfcrypt/src/curve25519.c @@ -637,7 +637,7 @@ static int wc_curve25519_shared_secret_nb(curve25519_key* privKey, } break; case 2: - #ifdef WOLFSSL_ECDHX_SHARED_NOT_ZERO + #ifndef WOLFSSL_NO_ECDHX_SHARED_ZERO_CHECK { int i; byte t = 0; @@ -649,13 +649,13 @@ static int wc_curve25519_shared_secret_nb(curve25519_key* privKey, ret = ECC_OUT_OF_RANGE_E; } else - #endif /* WOLFSSL_ECDHX_SHARED_NOT_ZERO */ + #endif /* !WOLFSSL_NO_ECDHX_SHARED_ZERO_CHECK */ { curve25519_copy_point(out, privKey->nb_ctx->o.point, endian); *outlen = CURVE25519_KEYSIZE; ret = 0; } - #ifdef WOLFSSL_ECDHX_SHARED_NOT_ZERO + #ifndef WOLFSSL_NO_ECDHX_SHARED_ZERO_CHECK } #endif break; @@ -761,7 +761,7 @@ int wc_curve25519_shared_secret_ex(curve25519_key* private_key, #endif } #endif /* FREESCALE_LTC_ECC */ -#ifdef WOLFSSL_ECDHX_SHARED_NOT_ZERO +#ifndef WOLFSSL_NO_ECDHX_SHARED_ZERO_CHECK if (ret == 0) { int i; byte t = 0; @@ -772,7 +772,7 @@ int wc_curve25519_shared_secret_ex(curve25519_key* private_key, ret = ECC_OUT_OF_RANGE_E; } } -#endif /* WOLFSSL_ECDHX_SHARED_NOT_ZERO */ +#endif /* !WOLFSSL_NO_ECDHX_SHARED_ZERO_CHECK */ if (ret == 0) { curve25519_copy_point(out, o.point, endian); *outlen = CURVE25519_KEYSIZE; diff --git a/wolfcrypt/src/curve448.c b/wolfcrypt/src/curve448.c index 8f1ded41dc..63c0f68089 100644 --- a/wolfcrypt/src/curve448.c +++ b/wolfcrypt/src/curve448.c @@ -33,7 +33,8 @@ * (when HAVE_CURVE448 is enabled) * HAVE_CURVE448_KEY_EXPORT: Enable Curve448 key export default: on * HAVE_CURVE448_KEY_IMPORT: Enable Curve448 key import default: on - * WOLFSSL_ECDHX_SHARED_NOT_ZERO: Check ECDH shared secret != 0 default: off + * WOLFSSL_NO_ECDHX_SHARED_ZERO_CHECK: Skip ECDH shared secret != 0 check + * default: off */ #include @@ -176,7 +177,7 @@ int wc_curve448_shared_secret_ex(curve448_key* private_key, if (ret == 0) { ret = curve448(o, private_key->k, public_key->p); } -#ifdef WOLFSSL_ECDHX_SHARED_NOT_ZERO +#ifndef WOLFSSL_NO_ECDHX_SHARED_ZERO_CHECK if (ret == 0) { byte t = 0; for (i = 0; i < CURVE448_PUB_KEY_SIZE; i++) {