Skip to content

Commit d6dafb4

Browse files
committed
asn: add a helper to complete a private-only ECC key
SEC1 makes the public point optional. Derive it in one place that blinds the multiply, waits on async and fails closed. In asn.c, not ecc.c, so FIPS builds with a tagged ecc.c have it too.
1 parent 0120bdd commit d6dafb4

4 files changed

Lines changed: 230 additions & 1 deletion

File tree

‎doc/dox_comments/header_files/asn_public.h‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2282,7 +2282,11 @@ int wc_DhPrivKeyToDer(DhKey* key, byte* out, word32* outSz);
22822282
22832283
\brief This function reads in an ECC private key from the input buffer,
22842284
input, parses the private key, and uses it to generate an ecc_key object,
2285-
which it stores in key.
2285+
which it stores in key. If the input RFC 5915 DER encoding omits the
2286+
optional publicKey field, the key is left with type ECC_PRIVATEKEY_ONLY.
2287+
Callers requiring the public point (e.g. for export or verification)
2288+
can complete the key with wc_ecc_make_pub_ex(), passing an RNG so the
2289+
scalar multiply is blinded.
22862290
22872291
\return 0 On successfully decoding the private key and storing the result
22882292
in the ecc_key struct
@@ -2333,6 +2337,7 @@ int wc_DhPrivKeyToDer(DhKey* key, byte* out, word32* outSz);
23332337
\endcode
23342338
23352339
\sa wc_RSA_PrivateKeyDecode
2340+
\sa wc_ecc_make_pub_ex
23362341
*/
23372342
int wc_EccPrivateKeyDecode(const byte* input, word32* inOutIdx,
23382343
ecc_key* key, word32 inSz);

‎tests/unit-mcdc/test_asn_keys_whitebox.c‎

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2141,6 +2141,93 @@ static void wb_build_ecc_key_der(void) { WB_NOTE("HAVE_ECC/template off; wc_Buil
21412141
static void wb_ecc_to_pkcs8(void) { WB_NOTE("HAVE_ECC/template off; eccToPKCS8 skipped"); }
21422142
#endif
21432143

2144+
/* ========================================================================
2145+
* Section B4b: wc_ecc_derive_pub_if_needed(), the contract
2146+
* wc_PKCS7_ImportECC(), the static ephemeral key loader and the EC_KEY
2147+
* loaders rely on. It is WOLFSSL_LOCAL, so tests/api cannot reach it. The
2148+
* NOT_COMPILED_IN / NO_VALID_DEVID / WC_KEY_SIZE_E tolerance needs a
2149+
* hardware, CB-only or SP-only build and is not reachable from here.
2150+
* ===================================================================== */
2151+
#if defined(HAVE_ECC) && !defined(WC_NO_RNG)
2152+
static void wb_ecc_derive_pub_if_needed(void)
2153+
{
2154+
WC_RNG rng;
2155+
ecc_key key;
2156+
int ret;
2157+
2158+
WB_NOTE("wc_ecc_derive_pub_if_needed(): NULL / full / private-only / "
2159+
"failed derivation");
2160+
ret = wc_ecc_derive_pub_if_needed(NULL, NULL);
2161+
WB_CHECK(ret == WC_NO_ERR_TRACE(BAD_FUNC_ARG), "key==NULL");
2162+
2163+
if (wc_InitRng(&rng) != 0) {
2164+
WB_NOTE("wc_InitRng failed; skipped");
2165+
return;
2166+
}
2167+
if (wc_ecc_init(&key) != 0) {
2168+
WB_NOTE("wc_ecc_init failed; skipped");
2169+
wc_FreeRng(&rng);
2170+
return;
2171+
}
2172+
if (wc_ecc_make_key_ex(&rng, 0, &key, ECC_SECP256R1) != 0) {
2173+
WB_NOTE("wc_ecc_make_key_ex failed; skipped");
2174+
goto out;
2175+
}
2176+
2177+
/* A key that already holds its point is left untouched. A marker in
2178+
* pubkey.x tells an early return apart from a silent recompute. */
2179+
if (mp_set(key.pubkey.x, 1) == MP_OKAY) {
2180+
ret = wc_ecc_derive_pub_if_needed(&key, &rng);
2181+
WB_CHECK((ret == 0) && (key.type == ECC_PRIVATEKEY) &&
2182+
(mp_isone(key.pubkey.x) == MP_YES), "full key early-returns");
2183+
}
2184+
2185+
/* A private-only key is completed, replacing the marker. */
2186+
key.type = ECC_PRIVATEKEY_ONLY;
2187+
ret = wc_ecc_derive_pub_if_needed(&key, &rng);
2188+
WB_CHECK((ret == 0) && (key.type == ECC_PRIVATEKEY) &&
2189+
(mp_isone(key.pubkey.x) != MP_YES), "private-only key promoted");
2190+
2191+
/* So is an ECC_PRIVATEKEY with an all-zero point, as SetECKeyInternal()
2192+
* leaves when only the private key was set. */
2193+
mp_zero(key.pubkey.x);
2194+
mp_zero(key.pubkey.y);
2195+
ret = wc_ecc_derive_pub_if_needed(&key, &rng);
2196+
WB_CHECK((ret == 0) && (key.type == ECC_PRIVATEKEY) &&
2197+
!mp_iszero(key.pubkey.x), "zero point completed");
2198+
2199+
#if defined(ECC_TIMING_RESISTANT) && !defined(WC_NO_RNG) && \
2200+
!defined(WOLFSSL_SP_MATH) && !defined(HAVE_SELFTEST) && \
2201+
!(defined(HAVE_FIPS) && FIPS_VERSION_LT(5,0))
2202+
/* No local RNG for a curve the SP fixed-base multiply handles. */
2203+
#if defined(WOLFSSL_HAVE_SP_ECC) && !defined(WOLFSSL_SP_NO_256)
2204+
WB_CHECK(ecc_derive_pub_uses_rng(&key) == 0, "SP P-256 takes no RNG");
2205+
#else
2206+
WB_CHECK(ecc_derive_pub_uses_rng(&key) == 1, "MP P-256 takes an RNG");
2207+
#endif
2208+
#endif
2209+
2210+
/* A failed derivation leaves the key private-only. */
2211+
key.type = ECC_PRIVATEKEY_ONLY;
2212+
mp_zero(key.k);
2213+
#ifdef WOLFSSL_ECC_BLIND_K
2214+
mp_zero(key.kb);
2215+
#endif
2216+
ret = wc_ecc_derive_pub_if_needed(&key, &rng);
2217+
WB_CHECK((ret != 0) && (key.type == ECC_PRIVATEKEY_ONLY),
2218+
"zero scalar fails closed");
2219+
2220+
out:
2221+
wc_ecc_free(&key);
2222+
wc_FreeRng(&rng);
2223+
}
2224+
#else
2225+
static void wb_ecc_derive_pub_if_needed(void)
2226+
{
2227+
WB_NOTE("HAVE_ECC off or WC_NO_RNG; skipped");
2228+
}
2229+
#endif
2230+
21442231
/* ========================================================================
21452232
* Section B5: DecodeAsymKey_Assign()/DecodeAsymKey()/DecodeAsymKeyPublic_
21462233
* Assign()/DecodeAsymKeyPublic(), driven directly plus via SetAsymKeyDer()/
@@ -2778,6 +2865,7 @@ int main(void)
27782865
wb_ecc_public_key_decode();
27792866
wb_build_ecc_key_der();
27802867
wb_ecc_to_pkcs8();
2868+
wb_ecc_derive_pub_if_needed();
27812869
wb_decode_asym_key_assign_guard();
27822870
wb_decode_asym_key_seed_arms();
27832871
wb_decode_asym_key_roundtrip();

‎wolfcrypt/src/asn.c‎

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35136,6 +35136,139 @@ int wc_EccPublicKeyDecode(const byte* input, word32* inOutIdx,
3513635136
return ret;
3513735137
}
3513835138
#endif /* WOLFSSL_ASN_TEMPLATE */
35139+
35140+
#if defined(ECC_TIMING_RESISTANT) && !defined(WC_NO_RNG) && \
35141+
!defined(WOLFSSL_SP_MATH) && !defined(HAVE_SELFTEST) && \
35142+
!(defined(HAVE_FIPS) && FIPS_VERSION_LT(5,0))
35143+
#define ECC_DERIVE_PUB_LOCAL_RNG
35144+
35145+
/* Whether deriving this key's point takes an RNG. The SP curves use a
35146+
* fixed-base multiply that takes none; this mirrors ecc_make_pub_sw(). */
35147+
static int ecc_derive_pub_uses_rng(const ecc_key* key)
35148+
{
35149+
int sp = 0;
35150+
35151+
#ifdef WOLFSSL_HAVE_SP_ECC
35152+
if ((key->idx != ECC_CUSTOM_IDX) && (key->dp != NULL)) {
35153+
int id = key->dp->id;
35154+
#ifndef WOLFSSL_SP_NO_256
35155+
sp |= (id == ECC_SECP256R1);
35156+
#endif
35157+
#if defined(WOLFSSL_SM2) && defined(WOLFSSL_SP_SM2)
35158+
sp |= (id == ECC_SM2P256V1);
35159+
#endif
35160+
#ifdef WOLFSSL_SP_384
35161+
sp |= (id == ECC_SECP384R1);
35162+
#endif
35163+
#ifdef WOLFSSL_SP_521
35164+
sp |= (id == ECC_SECP521R1);
35165+
#endif
35166+
(void)id;
35167+
}
35168+
#else
35169+
(void)key;
35170+
#endif
35171+
35172+
return !sp;
35173+
}
35174+
#endif
35175+
35176+
/* Derive and cache the public point of a key that lacks one.
35177+
*
35178+
* SEC1/RFC 5915 makes the publicKey field optional, so
35179+
* wc_EccPrivateKeyDecode() can leave key->type == ECC_PRIVATEKEY_ONLY. The
35180+
* compatibility layer can also mark a key ECC_PRIVATEKEY with an all-zero
35181+
* point when only the private key was set.
35182+
* Lives here rather than in ecc.c so FIPS builds, which swap ecc.c for a
35183+
* tagged copy, have it too.
35184+
*
35185+
* key private key to complete, cannot be NULL
35186+
* rng RNG for a timing-resistant multiply, may be NULL: key->rng or a local
35187+
* RNG is used instead
35188+
*
35189+
* returns 0 when the key holds a public point, or when the build or device
35190+
* has no derivation and the key was left private-only. Callers that
35191+
* need the point check key->type.
35192+
*/
35193+
int wc_ecc_derive_pub_if_needed(ecc_key* key, WC_RNG* rng)
35194+
{
35195+
int ret = 0;
35196+
#ifdef ECC_DERIVE_PUB_LOCAL_RNG
35197+
WC_DECLARE_VAR(tmpRng, WC_RNG, 1, NULL);
35198+
int initTmpRng = 0;
35199+
#endif
35200+
35201+
if (key == NULL) {
35202+
return BAD_FUNC_ARG;
35203+
}
35204+
if ((key->type != ECC_PRIVATEKEY_ONLY) &&
35205+
!((key->type == ECC_PRIVATEKEY) && mp_iszero(key->pubkey.x) &&
35206+
mp_iszero(key->pubkey.y))) {
35207+
return 0;
35208+
}
35209+
#ifdef WOLFSSL_CAAM
35210+
/* A black key holds an encrypted blob, not a scalar. */
35211+
if (key->blackKey != 0) {
35212+
return 0;
35213+
}
35214+
#endif
35215+
35216+
#ifdef ECC_DERIVE_PUB_LOCAL_RNG
35217+
/* The multi-precision multiply is only blinded when given an RNG. */
35218+
if (rng == NULL) {
35219+
rng = key->rng;
35220+
}
35221+
if ((rng == NULL) && ecc_derive_pub_uses_rng(key)) {
35222+
WC_ALLOC_VAR_EX(tmpRng, WC_RNG, 1, key->heap, DYNAMIC_TYPE_RNG,
35223+
ret = MEMORY_E);
35224+
if (ret == 0) {
35225+
ret = wc_InitRng_ex(tmpRng, key->heap, INVALID_DEVID);
35226+
}
35227+
if (ret == 0) {
35228+
initTmpRng = 1;
35229+
rng = tmpRng;
35230+
}
35231+
}
35232+
#endif
35233+
35234+
if (ret == 0) {
35235+
#if defined(HAVE_SELFTEST) || \
35236+
(defined(HAVE_FIPS) && FIPS_VERSION_LT(5,0))
35237+
(void)rng;
35238+
ret = wc_ecc_make_pub(key, NULL);
35239+
#else
35240+
ret = wc_ecc_make_pub_ex(key, NULL, rng);
35241+
#endif
35242+
#ifdef WOLFSSL_ASYNC_CRYPT
35243+
/* Callers read key->type and key->pubkey as soon as this returns. */
35244+
ret = wc_AsyncWait(ret, &key->asyncDev, WC_ASYNC_FLAG_NONE);
35245+
#endif
35246+
if (ret != 0) {
35247+
/* FIPS copies of ecc.c promote the key even when make-pub fails,
35248+
* and a pending derivation is promoted before the device has a
35249+
* point. */
35250+
key->type = ECC_PRIVATEKEY_ONLY;
35251+
}
35252+
/* No derivation in this build or device. WC_KEY_SIZE_E is an
35253+
* SP-only build without this curve. */
35254+
if ((ret == WC_NO_ERR_TRACE(NOT_COMPILED_IN)) ||
35255+
(ret == WC_NO_ERR_TRACE(NO_VALID_DEVID)) ||
35256+
(ret == WC_NO_ERR_TRACE(WC_KEY_SIZE_E))) {
35257+
ret = 0;
35258+
}
35259+
}
35260+
35261+
#ifdef ECC_DERIVE_PUB_LOCAL_RNG
35262+
if (initTmpRng) {
35263+
wc_FreeRng(tmpRng);
35264+
}
35265+
WC_FREE_VAR_EX(tmpRng, key->heap, DYNAMIC_TYPE_RNG);
35266+
#undef ECC_DERIVE_PUB_LOCAL_RNG
35267+
#endif
35268+
35269+
return ret;
35270+
}
35271+
3513935272
#ifdef HAVE_ECC_KEY_EXPORT
3514035273
/* build DER formatted ECC key, include optional public key if requested,
3514135274
* return length on success, negative on error */

‎wolfssl/wolfcrypt/asn_public.h‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -858,9 +858,12 @@ WOLFSSL_API int wc_DhPrivKeyToDer(DhKey* key, byte* out, word32* outSz);
858858

859859
#ifdef HAVE_ECC
860860
/* private key helpers */
861+
/* Note: if RFC 5915 DER omits publicKey, key->type is ECC_PRIVATEKEY_ONLY;
862+
* call wc_ecc_make_pub_ex() with an RNG if the public point is needed. */
861863
WOLFSSL_ABI
862864
WOLFSSL_API int wc_EccPrivateKeyDecode(const byte* input, word32* inOutIdx,
863865
ecc_key* key, word32 inSz);
866+
WOLFSSL_LOCAL int wc_ecc_derive_pub_if_needed(ecc_key* key, WC_RNG* rng);
864867
WOLFSSL_LOCAL int wc_BuildEccKeyDer(ecc_key* key, byte* output, word32 *outLen,
865868
int pubIn, int curveIn);
866869
WOLFSSL_ABI

0 commit comments

Comments
 (0)