From 456fe339b3249dbe9e12f4a532fafb9d558fed8b Mon Sep 17 00:00:00 2001 From: Mark Atwood Date: Mon, 16 Mar 2026 11:18:32 -0700 Subject: [PATCH 1/2] ecc: reject compressed EC points with incorrect length wc_ecc_import_point_der_ex accepts a single byte 0x02 or 0x03 as a valid compressed EC point. It treats the missing X coordinate as zero, decompresses it (producing a valid on-curve point), and wc_ecc_check_key passes. This allows ECDH key agreement with a crafted 1-byte peer public key. Add length validation for compressed points: after identifying 0x02/0x03 format byte, verify that inLen == ecc_sets[curve_idx].size + 1 using unsigned comparison to avoid underflow. Only set compressed = 1 after the length check passes, keeping state consistent on the error path. Reproducer: call EC_POINT_oct2point with a 1-byte buffer containing 0x02 for any NIST curve. Before this fix it succeeds; after, it returns ECC_BAD_ARG_E. --- wolfcrypt/src/ecc.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/wolfcrypt/src/ecc.c b/wolfcrypt/src/ecc.c index 11276ce6e9..7515659846 100644 --- a/wolfcrypt/src/ecc.c +++ b/wolfcrypt/src/ecc.c @@ -9628,7 +9628,14 @@ int wc_ecc_import_point_der_ex(const byte* in, word32 inLen, if (pointType == ECC_POINT_COMP_EVEN || pointType == ECC_POINT_COMP_ODD) { #ifdef HAVE_COMP_KEY - compressed = 1; + /* Compressed point must be exactly 1 + field_element_size bytes. + * Reject truncated inputs (e.g. a bare 0x02/0x03 byte). */ + if (inLen == (word32)ecc_sets[curve_idx].size + 1) { + compressed = 1; + } + else { + err = ECC_BAD_ARG_E; + } #else err = NOT_COMPILED_IN; #endif From cf5a34dff84fed8acfed9b01045be03b154d9928 Mon Sep 17 00:00:00 2001 From: Mark Atwood Date: Thu, 4 Jun 2026 17:13:48 -0700 Subject: [PATCH 2/2] ecc: harden compressed-point length checks - Account for shortKeySize mode in wc_ecc_import_point_der_ex() - Add compressed-point length validation to _ecc_import_x963_ex2() - Add truncated compressed-point rejection tests for both import paths - Fix } else { formatting Co-Authored-By: Claude Opus 4.6 --- tests/api/test_ecc.c | 21 +++++++++++++++++++++ wolfcrypt/src/ecc.c | 23 ++++++++++++++++++++--- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/tests/api/test_ecc.c b/tests/api/test_ecc.c index 90d5ced882..848d4fb23a 100644 --- a/tests/api/test_ecc.c +++ b/tests/api/test_ecc.c @@ -760,6 +760,16 @@ int test_wc_ecc_import_x963(void) WC_NO_ERR_TRACE(BAD_FUNC_ARG)); ExpectIntEQ(wc_ecc_import_x963(x963, x963Len + 1, &pubKey), WC_NO_ERR_TRACE(ECC_BAD_ARG_E)); +#ifdef HAVE_COMP_KEY + /* A bare compressed format byte (0x02) with no x coordinate must be + * rejected. inLen == 1 is odd, so the early even-length check does not + * catch this -- the compressed-point length validation must. */ + { + byte trunc[1] = { 0x02 }; + ExpectIntEQ(wc_ecc_import_x963(trunc, 1, &pubKey), + WC_NO_ERR_TRACE(ECC_BAD_ARG_E)); + } +#endif DoExpectIntEQ(wc_FreeRng(&rng), 0); wc_ecc_free(&key); @@ -1481,6 +1491,17 @@ int test_wc_ecc_pointFns(void) WC_NO_ERR_TRACE(ECC_BAD_ARG_E)); ExpectIntEQ(wc_ecc_import_point_der(der, derSz + 1, idx, point), WC_NO_ERR_TRACE(ECC_BAD_ARG_E)); +#ifdef HAVE_COMP_KEY + /* A bare format byte (0x02) with no x coordinate must be rejected. + * The failing import zeroes point's coordinates, so re-import the + * valid DER afterward to restore state for subsequent tests. */ + { + byte trunc[1] = { 0x02 }; + ExpectIntEQ(wc_ecc_import_point_der(trunc, 1, idx, point), + WC_NO_ERR_TRACE(ECC_BAD_ARG_E)); + } + ExpectIntEQ(wc_ecc_import_point_der(der, derSz, idx, point), 0); +#endif /* Copy */ ExpectIntEQ(wc_ecc_copy_point(point, cpypt), 0); diff --git a/wolfcrypt/src/ecc.c b/wolfcrypt/src/ecc.c index 7515659846..3e0a371e38 100644 --- a/wolfcrypt/src/ecc.c +++ b/wolfcrypt/src/ecc.c @@ -9629,11 +9629,15 @@ int wc_ecc_import_point_der_ex(const byte* in, word32 inLen, if (pointType == ECC_POINT_COMP_EVEN || pointType == ECC_POINT_COMP_ODD) { #ifdef HAVE_COMP_KEY /* Compressed point must be exactly 1 + field_element_size bytes. + * When shortKeySize is set, callers may pass the uncompressed + * length (2*size + 1) even for compressed data. * Reject truncated inputs (e.g. a bare 0x02/0x03 byte). */ if (inLen == (word32)ecc_sets[curve_idx].size + 1) { compressed = 1; - } - else { + } else if (shortKeySize && + inLen == (word32)ecc_sets[curve_idx].size * 2 + 1) { + compressed = 1; + } else { err = ECC_BAD_ARG_E; } #else @@ -10909,7 +10913,20 @@ static int _ecc_import_x963_ex2(const byte* in, word32 inLen, ecc_key* key, if (pointType == ECC_POINT_COMP_EVEN || pointType == ECC_POINT_COMP_ODD) { #ifdef HAVE_COMP_KEY - compressed = 1; + /* Compressed point must be at least 2 bytes (format + x coordinate). + * When curve_id is known, verify exact expected length. */ + if (inLen < 2) { + err = ECC_BAD_ARG_E; + } else if (curve_id > ECC_CURVE_DEF) { + int expSz = wc_ecc_get_curve_size_from_id(curve_id); + if (expSz <= 0 || inLen != (word32)expSz + 1) { + err = ECC_BAD_ARG_E; + } else { + compressed = 1; + } + } else { + compressed = 1; + } #else err = NOT_COMPILED_IN; #endif