1
0
mirror of https://github.com/wolfSSL/wolfssl.git synced 2026-10-24 04:09:07 +03:00

ecc: bound explicit EC domain parameters to MAX_ECC_BYTES

EccSpecifiedECDomainDecode() took the curve size straight from the DER
length of the prime INTEGER with no upper bound, and
wc_EccPublicKeyDecode() then installed that curve on the caller's key.
Every fixed buffer in ecc.c is dimensioned for MAX_ECC_BYTES, so a curve
larger than that left the export and encode paths padding a coordinate
outside its buffer, with both the offset and the bytes written taken
from the key. One of those paths reaches it through the library's own
allocation, with no output buffer from the caller at all.

Reject the parameters where the size is taken from input, which is where
the static ecc_sets[] table establishes the same invariant for every
named curve. wc_ecc_set_custom_curve() applies the bound too, so a curve
installed through the API cannot exceed the fixed buffers either, and
the WOLFSSL_ECC_CURVE_STATIC branch bounds the a/b/order lengths, which
are hex-encoded into MAX_ECC_STRING arrays from their own unbounded DER
lengths. wc_ecc_set_custom_curve() rejects a non-positive size as well,
matching the decoder: size 0 let a one byte 0x04 satisfy the importer's
length check and yield a degenerate public key. Both reject paths log.

Behaviour change: explicit-parameter EC keys and certificates whose
prime is longer than MAX_ECC_BYTES (66 bytes without SAKKE, 128 with it)
now fail to decode with ASN_PARSE_E instead of being accepted with a dp
the rest of the library cannot handle safely. Every standard prime curve
up to P-521 still decodes.

Known limitation in WOLFSSL_ECC_CURVE_STATIC builds: the order bound is
MAX_ECC_BYTES, but the Koblitz curves carry an order one byte wider than
their prime, so in a build whose MAX_ECC_BYTES equals that prime size an
explicit parameter key for one of them is now refused. MAX_ECC_STRING
cannot hold its hex form, which is what the unbounded DataToHexString()
overflowed before, so this fails closed where it used to corrupt the
struct.

Test: test_wc_EccPublicKeyDecode_explicit_curve_size, which builds the
SubjectPublicKeyInfo at run time so the control prime tracks
MAX_ECC_BYTES in whatever configuration it is built for.
This commit is contained in:
Tobias Frauenschläger
2026-09-16 14:10:32 +02:00
parent dd9f0af9f6
commit f608a41c56
4 changed files with 190 additions and 1 deletions

View File

@@ -4136,3 +4136,165 @@ int test_wc_EccDecisionCoverage4(void)
#endif /* HAVE_ECC && !WC_NO_RNG && !WOLF_CRYPTO_CB_ONLY_ECC */
return EXPECT_RESULT();
} /* END test_wc_EccDecisionCoverage4 */
#if defined(HAVE_ECC) && defined(WOLFSSL_CUSTOM_CURVES) && \
defined(WOLFSSL_ASN_TEMPLATE) && defined(HAVE_ECC_KEY_IMPORT) && \
defined(HAVE_ECC_KEY_EXPORT) && !defined(NO_ASN)
#define ECC_SPEC_COORD_SZ MAX_ECC_BYTES
#define ECC_SPEC_MAX_PRIME 300
#define ECC_SPEC_BUF_SZ ((ECC_SPEC_MAX_PRIME * 7) + 512)
static void EccSpecFill(byte* buf, word32* idx, byte val, word32 len)
{
*idx -= len;
XMEMSET(buf + *idx, val, len);
}
static void EccSpecHdr(byte* buf, word32* idx, byte tag, word32 len)
{
if (len < 128) {
buf[--(*idx)] = (byte)len;
}
else if (len < 256) {
buf[--(*idx)] = (byte)len;
buf[--(*idx)] = 0x81;
}
else {
buf[--(*idx)] = (byte)len;
buf[--(*idx)] = (byte)(len >> 8);
buf[--(*idx)] = 0x82;
}
buf[--(*idx)] = tag;
}
/* Build an ecPublicKey SubjectPublicKeyInfo carrying explicit
* SpecifiedECDomain parameters with a prime of primeSz bytes. Encoded back to
* front, so each item is wrapped once its content is in place. */
static word32 EccSpecifiedSpki(byte* buf, word32 primeSz)
{
static const byte ecPubKeyOid[] = {
0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01
};
static const byte primeFldOid[] = {
0x2a, 0x86, 0x48, 0xce, 0x3d, 0x01, 0x01
};
word32 idx = ECC_SPEC_BUF_SZ;
word32 end = ECC_SPEC_BUF_SZ;
word32 domEnd;
/* Subject public key: BIT STRING of 0x04 <x> <y>. */
EccSpecFill(buf, &idx, 0x42, ECC_SPEC_COORD_SZ);
EccSpecFill(buf, &idx, 0x41, ECC_SPEC_COORD_SZ);
buf[--idx] = 0x04;
buf[--idx] = 0x00;
EccSpecHdr(buf, &idx, ASN_BIT_STRING, end - idx);
domEnd = idx;
/* SpecifiedECDomain: version, field, curve, base point, order. */
EccSpecFill(buf, &idx, 0x7f, primeSz);
EccSpecHdr(buf, &idx, ASN_INTEGER, primeSz);
end = idx;
EccSpecFill(buf, &idx, 0x03, primeSz * 2);
buf[--idx] = 0x04;
EccSpecHdr(buf, &idx, ASN_OCTET_STRING, end - idx);
end = idx;
EccSpecFill(buf, &idx, 0x02, primeSz);
EccSpecHdr(buf, &idx, ASN_OCTET_STRING, primeSz);
EccSpecFill(buf, &idx, 0x01, primeSz);
EccSpecHdr(buf, &idx, ASN_OCTET_STRING, primeSz);
EccSpecHdr(buf, &idx, ASN_SEQUENCE | ASN_CONSTRUCTED, end - idx);
end = idx;
EccSpecFill(buf, &idx, 0x7f, primeSz);
EccSpecHdr(buf, &idx, ASN_INTEGER, primeSz);
idx -= sizeof(primeFldOid);
XMEMCPY(buf + idx, primeFldOid, sizeof(primeFldOid));
EccSpecHdr(buf, &idx, ASN_OBJECT_ID, sizeof(primeFldOid));
EccSpecHdr(buf, &idx, ASN_SEQUENCE | ASN_CONSTRUCTED, end - idx);
buf[--idx] = 0x01;
EccSpecHdr(buf, &idx, ASN_INTEGER, 1);
EccSpecHdr(buf, &idx, ASN_SEQUENCE | ASN_CONSTRUCTED, domEnd - idx);
/* AlgorithmIdentifier and the enclosing SubjectPublicKeyInfo. */
idx -= sizeof(ecPubKeyOid);
XMEMCPY(buf + idx, ecPubKeyOid, sizeof(ecPubKeyOid));
EccSpecHdr(buf, &idx, ASN_OBJECT_ID, sizeof(ecPubKeyOid));
EccSpecHdr(buf, &idx, ASN_SEQUENCE | ASN_CONSTRUCTED, domEnd - idx);
EccSpecHdr(buf, &idx, ASN_SEQUENCE | ASN_CONSTRUCTED,
ECC_SPEC_BUF_SZ - idx);
XMEMMOVE(buf, buf + idx, ECC_SPEC_BUF_SZ - idx);
return ECC_SPEC_BUF_SZ - idx;
}
#endif
int test_wc_EccPublicKeyDecode_explicit_curve_size(void)
{
EXPECT_DECLS;
#if defined(HAVE_ECC) && defined(WOLFSSL_CUSTOM_CURVES) && \
defined(WOLFSSL_ASN_TEMPLATE) && defined(HAVE_ECC_KEY_IMPORT) && \
defined(HAVE_ECC_KEY_EXPORT) && !defined(NO_ASN)
ecc_key key;
byte* der = NULL;
byte* out = NULL;
word32 derSz;
word32 idx;
#ifndef WOLFSSL_VALIDATE_ECC_IMPORT
word32 outSz;
#endif
ExpectNotNull(der = (byte*)XMALLOC(ECC_SPEC_BUF_SZ, NULL,
DYNAMIC_TYPE_TMP_BUFFER));
ExpectNotNull(out = (byte*)XMALLOC((ECC_SPEC_MAX_PRIME * 2) + 1, NULL,
DYNAMIC_TYPE_TMP_BUFFER));
#ifndef WOLFSSL_VALIDATE_ECC_IMPORT
/* A prime the ECC code is dimensioned for still decodes and exports. The
* fabricated point is not on the fabricated curve, so import validation
* would reject it. */
if (EXPECT_SUCCESS()) {
XMEMSET(&key, 0, sizeof(key));
derSz = EccSpecifiedSpki(der, MAX_ECC_BYTES);
idx = 0;
ExpectIntEQ(wc_ecc_init(&key), 0);
ExpectIntEQ(wc_EccPublicKeyDecode(der, &idx, &key, derSz), 0);
ExpectNotNull(key.dp);
ExpectIntEQ(key.dp != NULL ? key.dp->size : 0, MAX_ECC_BYTES);
outSz = (MAX_ECC_BYTES * 2) + 1;
ExpectIntEQ(wc_ecc_export_x963(&key, out, &outSz), 0);
ExpectIntEQ(outSz, (MAX_ECC_BYTES * 2) + 1);
wc_ecc_free(&key);
}
#endif
/* A prime past MAX_ECC_BYTES must be rejected at decode: the curve size
* drives the padding offset into a fixed ECC_BUFSIZE stack buffer in
* _ecc_export_x963(). */
if (EXPECT_SUCCESS()) {
XMEMSET(&key, 0, sizeof(key));
derSz = EccSpecifiedSpki(der, ECC_SPEC_MAX_PRIME);
idx = 0;
ExpectIntEQ(wc_ecc_init(&key), 0);
ExpectIntEQ(wc_EccPublicKeyDecode(der, &idx, &key, derSz),
WC_NO_ERR_TRACE(ASN_PARSE_E));
wc_ecc_free(&key);
}
/* The same bound applies to a custom curve set through the API. */
if (EXPECT_SUCCESS()) {
ecc_set_type dp;
XMEMSET(&key, 0, sizeof(key));
XMEMSET(&dp, 0, sizeof(dp));
dp.size = ECC_SPEC_MAX_PRIME;
ExpectIntEQ(wc_ecc_init(&key), 0);
ExpectIntEQ(wc_ecc_set_custom_curve(&key, &dp),
WC_NO_ERR_TRACE(ECC_BAD_ARG_E));
wc_ecc_free(&key);
}
XFREE(out, NULL, DYNAMIC_TYPE_TMP_BUFFER);
XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER);
#endif
return EXPECT_RESULT();
} /* END test_wc_EccPublicKeyDecode_explicit_curve_size */

View File

@@ -75,6 +75,7 @@ int test_wc_EccDecisionCoverage(void);
int test_wc_EccDecisionCoverage2(void);
int test_wc_EccDecisionCoverage3(void);
int test_wc_EccDecisionCoverage4(void);
int test_wc_EccPublicKeyDecode_explicit_curve_size(void);
#define TEST_ECC_DECLS \
TEST_DECL_GROUP("ecc", test_wc_ecc_get_curve_size_from_name), \
@@ -127,6 +128,8 @@ int test_wc_EccDecisionCoverage4(void);
TEST_DECL_GROUP("ecc", test_wc_EccDecisionCoverage), \
TEST_DECL_GROUP("ecc", test_wc_EccDecisionCoverage2), \
TEST_DECL_GROUP("ecc", test_wc_EccDecisionCoverage3), \
TEST_DECL_GROUP("ecc", test_wc_EccDecisionCoverage4)
TEST_DECL_GROUP("ecc", test_wc_EccDecisionCoverage4), \
TEST_DECL_GROUP("ecc", \
test_wc_EccPublicKeyDecode_explicit_curve_size)
#endif /* WOLFCRYPT_TEST_ECC_H */

View File

@@ -34691,6 +34691,14 @@ static int EccSpecifiedECDomainDecode(const byte* input, word32 inSz,
/* Length of the prime in bytes is the curve size. */
curve->size =
(int)dataASN[ECCSPECIFIEDASN_IDX_PRIME_P].data.ref.length;
/* Curve size must fit the fixed size buffers in the ECC code. */
if ((curve->size <= 0) || (curve->size > MAX_ECC_BYTES) ||
(curve->size > ECC_MAXSIZE)) {
WOLFSSL_MSG("ECC explicit domain prime outside MAX_ECC_BYTES");
ret = ASN_PARSE_E;
}
}
if (ret == 0) {
/* Base point: 0x04 <x> <y> (must be uncompressed). */
GetASN_GetConstRef(&dataASN[ECCSPECIFIEDASN_IDX_BASE], &base,
&baseLen);
@@ -34756,6 +34764,17 @@ static int EccSpecifiedECDomainDecode(const byte* input, word32 inSz,
curve->order = curve_order;
}
#else
/* Hex strings are held in fixed size MAX_ECC_STRING buffers. */
if ((ret == 0) &&
((dataASN[ECCSPECIFIEDASN_IDX_PARAM_A].data.ref.length >
(word32)MAX_ECC_BYTES) ||
(dataASN[ECCSPECIFIEDASN_IDX_PARAM_B].data.ref.length >
(word32)MAX_ECC_BYTES) ||
(dataASN[ECCSPECIFIEDASN_IDX_ORDER].data.ref.length >
(word32)MAX_ECC_BYTES))) {
WOLFSSL_MSG("ECC explicit domain parameter too long for hex buffer");
ret = ASN_PARSE_E;
}
if (ret == 0) {
/* Base X-ordinate */
wc_DataToHexString(base + 1, (word32)curve->size, (char *)curve->Gx);

View File

@@ -17409,6 +17409,11 @@ int wc_ecc_set_custom_curve(ecc_key* key, const ecc_set_type* dp)
if (key == NULL || dp == NULL) {
return BAD_FUNC_ARG;
}
/* Curve size must fit the fixed size buffers in the ECC code. */
if ((dp->size <= 0) || (dp->size > MAX_ECC_BYTES) ||
(dp->size > ECC_MAXSIZE)) {
return ECC_BAD_ARG_E;
}
key->idx = ECC_CUSTOM_IDX;
key->dp = dp;