Skip to content

ecc: reject compressed EC points with incorrect length#9985

Closed
MarkAtwood wants to merge 1 commit intowolfSSL:masterfrom
MarkAtwood:fix/ecc-compressed-point-length-check
Closed

ecc: reject compressed EC points with incorrect length#9985
MarkAtwood wants to merge 1 commit intowolfSSL:masterfrom
MarkAtwood:fix/ecc-compressed-point-length-check

Conversation

@MarkAtwood
Copy link

Summary

wc_ecc_import_point_der_ex accepts a single byte 0x02 or 0x03 as a valid compressed EC point for all NIST curves (P-256, P-384, P-521). It treats the missing X coordinate bytes as zeros, successfully decompresses the point (which lands on the curve), and wc_ecc_check_key passes the result. This allows ECDH key agreement with a crafted 1-byte peer public key.

Root Cause

The function validates the format byte (0x02/0x03/0x04) but does not validate that the input length matches the expected size for the curve. A compressed point should be exactly 1 + field_element_size bytes (33 for P-256, 49 for P-384, 67 for P-521). The existing inLen & 1 == 0 check (must be odd) passes for a single byte.

Reproducer

// P-256: single-byte compressed point
unsigned char bad_point[] = { 0x02 };
ecc_point *pt = wc_ecc_new_point();
// This succeeds before the fix, returns ECC_BAD_ARG_E after
int ret = wc_ecc_import_point_der_ex(bad_point, 1, curve_idx, pt, 0);

Also reproducible via the OpenSSL compat layer:

unsigned char bad_point[] = { 0x02 };
EC_POINT *pt = EC_POINT_new(group);
// Returns 1 (success) before fix
EC_POINT_oct2point(group, pt, bad_point, 1, NULL);

Security Impact

An attacker can supply a 1-byte "public key" (0x02 or 0x03) to an ECDH key agreement. wolfSSL will decompress this to the curve point with X=0, compute a shared secret, and return it to the caller. This is a peer public key validation bypass. In protocols that rely on the crypto library to reject malformed keys (e.g., TLS), this could lead to key compromise via a small number of crafted handshakes.

Fix

Added a length check in wc_ecc_import_point_der_ex immediately after identifying a compressed point type. Verifies that inLen - 1 == ecc_sets[curve_idx].size. Returns ECC_BAD_ARG_E if the length doesn't match.

The non-_ex wrapper wc_ecc_import_point_der delegates to _ex, so it's automatically fixed.

Test Plan

  • Verify compressed point import with correct length still works for P-256, P-384, P-521
  • Verify 1-byte 0x02 / 0x03 is rejected for all curves
  • Verify BoringSSL/Wycheproof ECDH test vectors pass (these include single-byte error cases)
  • Run existing wolfSSL test suite

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings March 16, 2026 18:19
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds stricter validation when importing compressed EC points so truncated inputs (e.g., a single 0x02/0x03 byte) are rejected instead of being decompressed as a valid point.

Changes:

  • Add a length check for compressed point encodings in wc_ecc_import_point_der_ex.
  • Return ECC_BAD_ARG_E when the compressed point length doesn’t match the curve’s field element size.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +9476 to +9477
int numlen = ecc_sets[curve_idx].size;
if ((int)(inLen - 1) != numlen) {
Comment on lines +9472 to +9480
compressed = 1;
/* Compressed point must be exactly 1 + field_element_size bytes.
* Reject truncated inputs (e.g. a bare 0x02/0x03 byte). */
{
int numlen = ecc_sets[curve_idx].size;
if ((int)(inLen - 1) != numlen) {
err = ECC_BAD_ARG_E;
}
}
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.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@MarkAtwood MarkAtwood force-pushed the fix/ecc-compressed-point-length-check branch from f525e68 to dc9c45e Compare March 16, 2026 18:26
@JacobBarthelmeh
Copy link
Contributor

Added a check on the key size found and a regression test case here #9989

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants