diff --git a/tests/api/test_ecc.c b/tests/api/test_ecc.c index 5849a6e48aa..7afb4cbb28c 100644 --- a/tests/api/test_ecc.c +++ b/tests/api/test_ecc.c @@ -3487,3 +3487,176 @@ int test_wc_EccDecisionCoverage4(void) #endif /* HAVE_ECC && !WC_NO_RNG && !WOLF_CRYPTO_CB_ONLY_ECC */ return EXPECT_RESULT(); } /* END test_wc_EccDecisionCoverage4 */ + + +/* + * Regression test for the explicit EC-domain buffer overflow (issue 11288). + * + * EccSpecifiedECDomainDecode() takes the prime, coordinate, A, B and order + * lengths straight from DER and converts each to a hex string. In the + * WOLFSSL_ECC_CURVE_STATIC build those strings go into fixed + * char[MAX_ECC_STRING] (== 2*MAX_ECC_BYTES+2) fields, and DataToHexString() + * writes 2*len+1 bytes, so any field longer than MAX_ECC_BYTES overflowed the + * heap-allocated ecc_set_type before key validation. The fix rejects such + * fields with ASN_PARSE_E. + * + * The SubjectPublicKeyInfo is assembled at run time so the field lengths are + * relative to this build's MAX_ECC_BYTES: MAX_ECC_BYTES+8 must be rejected, + * MAX_ECC_BYTES (the largest representable size) must not be rejected by the + * new length check. + */ +#if defined(HAVE_ECC) && defined(WOLFSSL_CUSTOM_CURVES) && \ + defined(WOLFSSL_ASN_TEMPLATE) && !defined(WOLFSSL_NO_MALLOC) +/* Append a DER definite length. */ +static word32 ecc11288_len(byte* out, word32 n) +{ + if (n < 0x80U) { + out[0] = (byte)n; + return 1; + } + if (n < 0x100U) { + out[0] = 0x81U; + out[1] = (byte)n; + return 2; + } + out[0] = 0x82U; + out[1] = (byte)(n >> 8); + out[2] = (byte)(n & 0xffU); + return 3; +} +/* Append tag || length || value at off; return the new offset. */ +static word32 ecc11288_tlv(byte* out, word32 off, byte tag, const byte* val, + word32 vlen) +{ + out[off++] = tag; + off += ecc11288_len(out + off, vlen); + if (vlen > 0) { + XMEMCPY(out + off, val, vlen); + } + return off + vlen; +} +/* Build an explicit-parameter EC SubjectPublicKeyInfo with the requested + * field byte lengths (prime length is also the curve size). out must hold at + * least 2048 bytes. Returns the encoded length. */ +static word32 ecc11288_build(byte* out, word32 primeLen, word32 aLen, + word32 bLen, word32 orderLen) +{ + static const byte primeFieldOid[] = + { 0x2a,0x86,0x48,0xce,0x3d,0x01,0x01 }; /* 1.2.840.10045.1.1 */ + static const byte ecPubKeyOid[] = + { 0x2a,0x86,0x48,0xce,0x3d,0x02,0x01 }; /* 1.2.840.10045.2.1 */ + static const byte ver2[1] = { 0x02 }; + static const byte cof1[1] = { 0x01 }; + byte num[512]; + byte inner[1024]; + byte body[2048]; + word32 io, bo, i; + + /* fieldID ::= SEQ { OID prime-field, INTEGER prime } */ + num[0] = 0x01; /* positive: MSB clear, no pad */ + for (i = 1; i < primeLen; i++) num[i] = 0x11; + io = ecc11288_tlv(inner, 0, 0x06, primeFieldOid, sizeof(primeFieldOid)); + io = ecc11288_tlv(inner, io, 0x02, num, primeLen); + bo = ecc11288_tlv(body, 0, 0x02, ver2, 1); /* version 2 */ + bo = ecc11288_tlv(body, bo, 0x30, inner, io); + + /* curve ::= SEQ { OCTET a, OCTET b } */ + XMEMSET(num, 0x00, sizeof(num)); + io = ecc11288_tlv(inner, 0, 0x04, num, aLen); + io = ecc11288_tlv(inner, io, 0x04, num, bLen); + bo = ecc11288_tlv(body, bo, 0x30, inner, io); + + /* base ::= OCTET { 0x04 || X(size) || Y(size) }, size == primeLen */ + inner[0] = 0x04; + for (i = 0; i < primeLen; i++) inner[1 + i] = 0x22; + for (i = 0; i < primeLen; i++) inner[1 + primeLen + i] = 0x33; + bo = ecc11288_tlv(body, bo, 0x04, inner, 1 + 2 * primeLen); + + /* order INTEGER, cofactor INTEGER */ + num[0] = 0x01; + for (i = 1; i < orderLen; i++) num[i] = 0x11; + bo = ecc11288_tlv(body, bo, 0x02, num, orderLen); + bo = ecc11288_tlv(body, bo, 0x02, cof1, 1); /* cofactor 1 */ + + /* ECParameters SEQ -> AlgorithmIdentifier SEQ { OID ecPublicKey, params } */ + io = ecc11288_tlv(inner, 0, 0x06, ecPubKeyOid, sizeof(ecPubKeyOid)); + io = ecc11288_tlv(inner, io, 0x30, body, bo); /* wrap params content */ + + /* SubjectPublicKeyInfo SEQ { AlgId, BIT STRING pubkey } */ + bo = ecc11288_tlv(body, 0, 0x30, inner, io); + { + /* Point is 0x04 || X || Y with each ordinate the curve size + * (primeLen), so the encoded point length tracks MAX_ECC_BYTES rather + * than a fixed 32 bytes and the at-bound control decodes a + * correctly-sized point. */ + byte pub[2 + 2 * (MAX_ECC_BYTES + 8)]; + word32 pl = 0; + pub[pl++] = 0x00; /* BIT STRING unused-bits */ + pub[pl++] = 0x04; /* uncompressed point */ + for (i = 0; i < primeLen; i++) pub[pl++] = 0x44; + for (i = 0; i < primeLen; i++) pub[pl++] = 0x55; + bo = ecc11288_tlv(body, bo, 0x03, pub, pl); + } + return ecc11288_tlv(out, 0, 0x30, body, bo); +} +#endif /* HAVE_ECC && WOLFSSL_CUSTOM_CURVES && WOLFSSL_ASN_TEMPLATE && + * !WOLFSSL_NO_MALLOC */ + +int test_wc_EccPublicKeyDecode_specifiedOverflow(void) +{ + EXPECT_DECLS; +#if defined(HAVE_ECC) && defined(WOLFSSL_CUSTOM_CURVES) && \ + defined(WOLFSSL_ASN_TEMPLATE) && !defined(WOLFSSL_NO_MALLOC) + const word32 mb = (word32)MAX_ECC_BYTES; /* largest representable field */ + const word32 ov = (word32)MAX_ECC_BYTES + 8; /* one field, over the limit */ + byte* der = (byte*)XMALLOC(2048, NULL, DYNAMIC_TYPE_TMP_BUFFER); + word32 len; + word32 idx; + ecc_key key; + + ExpectNotNull(der); + + /* Each oversized field, in turn, must be rejected (not overflow). */ + if (der != NULL) { + len = ecc11288_build(der, ov, mb, mb, mb); /* prime / base X,Y */ + idx = 0; + ExpectIntEQ(wc_ecc_init(&key), 0); + ExpectIntEQ(wc_EccPublicKeyDecode(der, &idx, &key, len), + WC_NO_ERR_TRACE(ASN_PARSE_E)); + wc_ecc_free(&key); + + len = ecc11288_build(der, mb, ov, mb, mb); /* parameter A */ + idx = 0; + ExpectIntEQ(wc_ecc_init(&key), 0); + ExpectIntEQ(wc_EccPublicKeyDecode(der, &idx, &key, len), + WC_NO_ERR_TRACE(ASN_PARSE_E)); + wc_ecc_free(&key); + + len = ecc11288_build(der, mb, mb, ov, mb); /* parameter B */ + idx = 0; + ExpectIntEQ(wc_ecc_init(&key), 0); + ExpectIntEQ(wc_EccPublicKeyDecode(der, &idx, &key, len), + WC_NO_ERR_TRACE(ASN_PARSE_E)); + wc_ecc_free(&key); + + len = ecc11288_build(der, mb, mb, mb, ov); /* order */ + idx = 0; + ExpectIntEQ(wc_ecc_init(&key), 0); + ExpectIntEQ(wc_EccPublicKeyDecode(der, &idx, &key, len), + WC_NO_ERR_TRACE(ASN_PARSE_E)); + wc_ecc_free(&key); + + /* Boundary: every field exactly MAX_ECC_BYTES must NOT be rejected by + * the length check (the largest size the fixed buffers can hold). */ + len = ecc11288_build(der, mb, mb, mb, mb); + idx = 0; + ExpectIntEQ(wc_ecc_init(&key), 0); + ExpectIntNE(wc_EccPublicKeyDecode(der, &idx, &key, len), + WC_NO_ERR_TRACE(ASN_PARSE_E)); + wc_ecc_free(&key); + } + + XFREE(der, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return EXPECT_RESULT(); +} /* END test_wc_EccPublicKeyDecode_specifiedOverflow */ diff --git a/tests/api/test_ecc.h b/tests/api/test_ecc.h index 61fd06dd002..63e382d4183 100644 --- a/tests/api/test_ecc.h +++ b/tests/api/test_ecc.h @@ -67,6 +67,7 @@ int test_wc_ecc_mulmod(void); int test_wc_ecc_is_valid_idx(void); int test_wc_ecc_get_curve_id_from_oid(void); int test_wc_ecc_sig_size_calc(void); +int test_wc_EccPublicKeyDecode_specifiedOverflow(void); int test_wc_EccPrivateKeyToDer(void); int test_wc_EccDecisionCoverage(void); int test_wc_EccDecisionCoverage2(void); @@ -121,6 +122,7 @@ 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_specifiedOverflow) #endif /* WOLFCRYPT_TEST_ECC_H */ diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index 6e21244f0f6..1a99054d17d 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -33970,6 +33970,23 @@ static int EccSpecifiedECDomainDecode(const byte* input, word32 inSz, /* Store optional co-factor. */ curve->cofactor = cofactor; } + if (ret == 0) { + /* Reject any explicit-parameter field that would overflow the fixed + * ecc_set_type hex buffers (WOLFSSL_ECC_CURVE_STATIC: each field is + * char[MAX_ECC_STRING] == 2*MAX_ECC_BYTES+2) or wrap the 2*len+1 + * allocation size (dynamic build). prime length also becomes + * curve->size, which bounds the base X/Y ordinates below. */ + if ((dataASN[ECCSPECIFIEDASN_IDX_PRIME_P].data.ref.length > + (word32)MAX_ECC_BYTES) || + (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)) { + ret = ASN_PARSE_E; + } + } if (ret == 0) { /* Length of the prime in bytes is the curve size. */ curve->size = diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index efdfc5b68df..84b8f0b403c 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -46341,6 +46341,152 @@ static wc_test_ret_t ecc_se050_onlykeyid_test(WC_RNG* rng) #endif /* WOLFSSL_SE050 && WOLFSSL_SE050_ONLY_KEY_ID && sign && verify && * key export */ + +#if defined(WOLFSSL_CUSTOM_CURVES) && defined(WOLFSSL_ASN_TEMPLATE) && \ + !defined(WOLFSSL_NO_MALLOC) && !defined(WOLFSSL_SMALL_STACK) +/* Append a DER definite length. */ +static word32 ecc_ssdd_len(byte* out, word32 n) +{ + if (n < 0x80U) { + out[0] = (byte)n; + return 1; + } + if (n < 0x100U) { + out[0] = 0x81U; + out[1] = (byte)n; + return 2; + } + out[0] = 0x82U; + out[1] = (byte)(n >> 8); + out[2] = (byte)(n & 0xffU); + return 3; +} +/* Append tag || length || value at off; return the new offset. */ +static word32 ecc_ssdd_tlv(byte* out, word32 off, byte tag, const byte* val, + word32 vlen) +{ + out[off++] = tag; + off += ecc_ssdd_len(out + off, vlen); + if (vlen > 0) { + XMEMCPY(out + off, val, vlen); + } + return off + vlen; +} +/* Build an explicit-parameter EC SubjectPublicKeyInfo with the requested + * field byte lengths (prime length is also the curve size). out must hold at + * least 2048 bytes. Returns the encoded length. */ +static word32 ecc_ssdd_build(byte* out, word32 primeLen, word32 aLen, + word32 bLen, word32 orderLen) +{ + static const byte primeFieldOid[] = + { 0x2a,0x86,0x48,0xce,0x3d,0x01,0x01 }; /* 1.2.840.10045.1.1 */ + static const byte ecPubKeyOid[] = + { 0x2a,0x86,0x48,0xce,0x3d,0x02,0x01 }; /* 1.2.840.10045.2.1 */ + static const byte ver2[1] = { 0x02 }; + static const byte cof1[1] = { 0x01 }; + byte num[512]; + byte inner[1024]; + byte body[2048]; + word32 io, bo, i; + + num[0] = 0x01; /* positive: MSB clear, no pad */ + for (i = 1; i < primeLen; i++) num[i] = 0x11; + io = ecc_ssdd_tlv(inner, 0, 0x06, primeFieldOid, sizeof(primeFieldOid)); + io = ecc_ssdd_tlv(inner, io, 0x02, num, primeLen); + bo = ecc_ssdd_tlv(body, 0, 0x02, ver2, 1); /* version 2 */ + bo = ecc_ssdd_tlv(body, bo, 0x30, inner, io); /* fieldID SEQ */ + + XMEMSET(num, 0x00, sizeof(num)); + io = ecc_ssdd_tlv(inner, 0, 0x04, num, aLen); + io = ecc_ssdd_tlv(inner, io, 0x04, num, bLen); + bo = ecc_ssdd_tlv(body, bo, 0x30, inner, io); /* curve SEQ { a, b } */ + + inner[0] = 0x04; /* base 0x04 || X(size) || Y(size) */ + for (i = 0; i < primeLen; i++) inner[1 + i] = 0x22; + for (i = 0; i < primeLen; i++) inner[1 + primeLen + i] = 0x33; + bo = ecc_ssdd_tlv(body, bo, 0x04, inner, 1 + 2 * primeLen); + + num[0] = 0x01; + for (i = 1; i < orderLen; i++) num[i] = 0x11; + bo = ecc_ssdd_tlv(body, bo, 0x02, num, orderLen); + bo = ecc_ssdd_tlv(body, bo, 0x02, cof1, 1); /* cofactor 1 */ + + io = ecc_ssdd_tlv(inner, 0, 0x06, ecPubKeyOid, sizeof(ecPubKeyOid)); + io = ecc_ssdd_tlv(inner, io, 0x30, body, bo); /* AlgId params SEQ */ + + bo = ecc_ssdd_tlv(body, 0, 0x30, inner, io); /* AlgorithmIdentifier */ + { + /* Point 0x04 || X || Y, each ordinate the curve size (primeLen). */ + byte pub[2 + 2 * (MAX_ECC_BYTES + 8)]; + word32 pl = 0; + pub[pl++] = 0x00; + pub[pl++] = 0x04; + for (i = 0; i < primeLen; i++) pub[pl++] = 0x44; + for (i = 0; i < primeLen; i++) pub[pl++] = 0x55; + bo = ecc_ssdd_tlv(body, bo, 0x03, pub, pl); /* pubkey BIT STRING */ + } + return ecc_ssdd_tlv(out, 0, 0x30, body, bo); /* SPKI SEQ */ +} + +/* Regression test for issue 11288: explicit EC-domain field lengths taken + * from DER must be bounded before conversion to the fixed hex-string buffers, + * or an oversized prime/coordinate/A/B/order overflows the ecc_set_type. + * The SPKI is built at run time so lengths are relative to MAX_ECC_BYTES. */ +static wc_test_ret_t ecc_ssdd_overflow_test(void) +{ + wc_test_ret_t ret = 0; + word32 mb = (word32)MAX_ECC_BYTES; + word32 ov = (word32)MAX_ECC_BYTES + 8; + byte* der; + word32 len, idx, c; + ecc_key key; + /* {prime, A, B, order} oversized in turn; then the all-at-bound control. */ + word32 lens[5][4]; + int expectReject[5]; + + lens[0][0]=ov; lens[0][1]=mb; lens[0][2]=mb; lens[0][3]=mb; expectReject[0]=1; + lens[1][0]=mb; lens[1][1]=ov; lens[1][2]=mb; lens[1][3]=mb; expectReject[1]=1; + lens[2][0]=mb; lens[2][1]=mb; lens[2][2]=ov; lens[2][3]=mb; expectReject[2]=1; + lens[3][0]=mb; lens[3][1]=mb; lens[3][2]=mb; lens[3][3]=ov; expectReject[3]=1; + lens[4][0]=mb; lens[4][1]=mb; lens[4][2]=mb; lens[4][3]=mb; expectReject[4]=0; + + der = (byte*)XMALLOC(2048, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + if (der == NULL) + return WC_TEST_RET_ENC_NC; + + for (c = 0; c < 5; c++) { + int r; + len = ecc_ssdd_build(der, lens[c][0], lens[c][1], lens[c][2], + lens[c][3]); + idx = 0; + ret = wc_ecc_init(&key); + if (ret != 0) + break; + r = wc_EccPublicKeyDecode(der, &idx, &key, len); + wc_ecc_free(&key); + if (expectReject[c]) { + /* Oversized field must be rejected, not overflow the buffers. */ + if (r != WC_NO_ERR_TRACE(ASN_PARSE_E)) { + ret = WC_TEST_RET_ENC_I((int)c); + break; + } + } + else { + /* Exactly MAX_ECC_BYTES must not be rejected by the length check. */ + if (r == WC_NO_ERR_TRACE(ASN_PARSE_E)) { + ret = WC_TEST_RET_ENC_I((int)c); + break; + } + } + ret = 0; + } + + XFREE(der, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + return ret; +} +#endif /* WOLFSSL_CUSTOM_CURVES && WOLFSSL_ASN_TEMPLATE && + * !WOLFSSL_NO_MALLOC && !WOLFSSL_SMALL_STACK */ + WOLFSSL_TEST_SUBROUTINE wc_test_ret_t ecc_test(void) { wc_test_ret_t ret; @@ -46394,6 +46540,15 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t ecc_test(void) } #endif +#if defined(WOLFSSL_CUSTOM_CURVES) && defined(WOLFSSL_ASN_TEMPLATE) && \ + !defined(WOLFSSL_NO_MALLOC) && !defined(WOLFSSL_SMALL_STACK) + ret = ecc_ssdd_overflow_test(); + if (ret != 0) { + printf("SpecifiedECDomain overflow\n"); + goto done; + } +#endif + #if defined(WOLFSSL_SM2) ret = test_sm2_verify(); if (ret != 0) {