Skip to content

Commit b9e69bd

Browse files
author
Emma Stensland
committed
Derive ECC public key on private-only SEC1 decode
1 parent ce4f855 commit b9e69bd

16 files changed

Lines changed: 480 additions & 28 deletions

File tree

‎.wolfssl_known_macro_extras‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -953,6 +953,7 @@ WOLFSSL_NO_DH186
953953
WOLFSSL_NO_DILITHIUM_LEGACY_GATES
954954
WOLFSSL_NO_DILITHIUM_LEGACY_NAMES
955955
WOLFSSL_NO_DTLS_SIZE_CHECK
956+
WOLFSSL_NO_ECC_DERIVE_PUB_ON_DECODE
956957
WOLFSSL_NO_ECDHX_SHARED_ZERO_CHECK
957958
WOLFSSL_NO_ETM_ALERT
958959
WOLFSSL_NO_FENCE

‎doc/dox_comments/header_files/asn_public.h‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2267,6 +2267,18 @@ int wc_DhPrivKeyToDer(DhKey* key, byte* out, word32* outSz);
22672267
input, parses the private key, and uses it to generate an ecc_key object,
22682268
which it stores in key.
22692269
2270+
When the encoding carries only the private scalar, the public point is
2271+
derived on a best-effort basis; a failed derivation is logged but does not
2272+
fail the decode. Observable effects of a successful derivation: key->type
2273+
becomes ECC_PRIVATEKEY rather than ECC_PRIVATEKEY_ONLY, key->pubkey is
2274+
populated, and the decode costs an additional base-point scalar multiply.
2275+
Under ECC_TIMING_RESISTANT that multiply is blinded with the key's rng when
2276+
one was set via wc_ecc_set_rng() beforehand; no rng is created for the
2277+
derivation, so with none set the projective-coordinate randomization is
2278+
simply skipped. No derivation is attempted for a key with a devId set.
2279+
Define WOLFSSL_NO_ECC_DERIVE_PUB_ON_DECODE to disable the derivation
2280+
entirely.
2281+
22702282
\return 0 On successfully decoding the private key and storing the result
22712283
in the ecc_key struct
22722284
\return ASN_PARSE_E: Returned if there is an error parsing the der file

‎src/internal.c‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33195,10 +33195,15 @@ static int DecodePrivateKey_ex(WOLFSSL *ssl, byte keyType, const DerBuffer* key,
3319533195

3319633196
/* Set start of data to beginning of buffer. */
3319733197
idx = 0;
33198-
/* Decode the key assuming it is an ECC private key. */
33199-
ret = wc_EccPrivateKeyDecode(key->buffer, &idx,
33198+
/* Decode the key assuming it is an ECC private key. Skip the
33199+
* best-effort public point derivation: the private scalar alone is
33200+
* enough for both uses of this key - signing (CertificateVerify /
33201+
* ServerKeyExchange) and, when static_ecdh is negotiated, static-ECDH
33202+
* shared-secret computation (wc_ecc_shared_secret() accepts an
33203+
* ECC_PRIVATEKEY_ONLY key) - and this runs once per handshake. */
33204+
ret = EccPrivateKeyDecodeEx(key->buffer, &idx,
3320033205
(ecc_key*)*hsKey,
33201-
key->length);
33206+
key->length, 0);
3320233207
#ifdef WOLF_PRIVATE_KEY_ID
3320333208
/* if using external key then allow using a public key */
3320433209
if (ret != 0 && (ssl->devId != INVALID_DEVID

‎src/ocsp.c‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2452,7 +2452,10 @@ int wc_OcspResponder_AddSigner(OcspResponder* responder,
24522452
if (ret == 0) {
24532453
word32 idx = 0;
24542454
ca->keyType = ECDSAk;
2455-
ret = wc_EccPrivateKeyDecode(keyDer, &idx, &ca->key.ecc, keyDerSz);
2455+
/* This key only ever signs OCSP responses (OcspResponseEncode);
2456+
* skip the best-effort public point derivation done on decode. */
2457+
ret = EccPrivateKeyDecodeEx(keyDer, &idx, &ca->key.ecc, keyDerSz,
2458+
0);
24562459
}
24572460
if (ret != 0)
24582461
goto out;

‎src/sniffer.c‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2836,8 +2836,12 @@ static int SetupKeys(const byte* input, int* sslBytes, SnifferSession* session,
28362836
#endif
28372837
if (ret == 0) {
28382838
idx = 0;
2839-
ret = wc_EccPrivateKeyDecode(args->keyBuf->buffer, &idx,
2840-
&args->key->priv.ecc, args->keyBuf->length);
2839+
/* Skip the best-effort public point derivation done on
2840+
* decode: this key only ever feeds wc_ecc_shared_secret(),
2841+
* which accepts an ECC_PRIVATEKEY_ONLY key, and this runs
2842+
* once per sniffed session. */
2843+
ret = EccPrivateKeyDecodeEx(args->keyBuf->buffer, &idx,
2844+
&args->key->priv.ecc, args->keyBuf->length, 0);
28412845
if (ret != 0) {
28422846
SetError(ECC_DECODE_STR, error, session, FATAL_ERROR_STATE);
28432847
}

‎src/ssl_api_hs.c‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1571,9 +1571,11 @@ void wolfSSL_set_accept_state(WOLFSSL* ssl)
15711571
* the check entirely. */
15721572
WOLFSSL_MSG("Unable to unmask private key");
15731573
}
1574-
/* Not an EC key, so withdraw the ECC capabilities. */
1575-
else if (wc_EccPrivateKeyDecode(privKey->buffer, &idx, key,
1576-
privKey->length) != 0) {
1574+
/* Not an EC key, so withdraw the ECC capabilities. Pure
1575+
* type/success probe - key is freed right below, so skip the
1576+
* best-effort public point derivation done on decode. */
1577+
else if (EccPrivateKeyDecodeEx(privKey->buffer, &idx, key,
1578+
privKey->length, 0) != 0) {
15771579
ssl->options.haveECDSAsig = 0;
15781580
ssl->options.haveECC = 0;
15791581
ssl->options.haveStaticECC = 0;

‎src/ssl_api_pk.c‎

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2291,8 +2291,19 @@ int wolfSSL_StaticEphemeralKeyLoad(WOLFSSL* ssl, int keyAlgo, void* keyPtr)
22912291
if (der != NULL) {
22922292
ecc_key* key = (ecc_key*)keyPtr;
22932293
WOLFSSL_MSG("Using static ECDH key");
2294-
ret = wc_EccPrivateKeyDecode(der->buffer, &idx, key,
2295-
der->length);
2294+
/* Keep the best-effort public point derivation: the caller
2295+
* (TLSX_KeyShare_GenEccKey) exports this key's public point
2296+
* with wc_ecc_export_x963(), which fails ECC_PRIVATEONLY_E on
2297+
* an ECC_PRIVATEKEY_ONLY key. Attach the RNG first so that
2298+
* derivation's base-point multiply gets projective-coordinate
2299+
* blinding on this long-lived static scalar, mirroring the
2300+
* X25519 branch below and SetupKeys() in sniffer.c. */
2301+
#ifdef ECC_TIMING_RESISTANT
2302+
ret = wc_ecc_set_rng(key, ssl->rng);
2303+
if (ret == 0)
2304+
#endif
2305+
ret = wc_EccPrivateKeyDecode(der->buffer, &idx, key,
2306+
der->length);
22962307
}
22972308
break;
22982309
#endif
@@ -2371,7 +2382,9 @@ static int DetectStaticEphemeralKeyType(const byte* keyBuf, unsigned int keySz,
23712382
ret = wc_ecc_init_ex(eccKey, heap, INVALID_DEVID);
23722383
}
23732384
if (ret == 0) {
2374-
ret = wc_EccPrivateKeyDecode(keyBuf, &idx, eccKey, keySz);
2385+
/* Pure type probe - key is freed right below, so skip the
2386+
* best-effort public point derivation done on decode. */
2387+
ret = EccPrivateKeyDecodeEx(keyBuf, &idx, eccKey, keySz, 0);
23752388
if (ret == 0) {
23762389
*keyAlgo = WC_PK_TYPE_ECDH;
23772390
}

‎src/ssl_load.c‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -568,9 +568,11 @@ static int ProcessBufferTryDecodeEcc(WOLFSSL_CTX* ctx, WOLFSSL* ssl,
568568

569569
/* Initialize ECC key. */
570570
if (wc_ecc_init_ex(key, heap, devId) == 0) {
571-
/* Decode as an ECC private key. */
571+
/* Decode as an ECC private key. Skip the best-effort public point
572+
* derivation - this only probes for the key format and size, and the
573+
* key is freed below. */
572574
idx = 0;
573-
ret = wc_EccPrivateKeyDecode(der->buffer, &idx, key, der->length);
575+
ret = EccPrivateKeyDecodeEx(der->buffer, &idx, key, der->length, 0);
574576
#ifdef WOLF_PRIVATE_KEY_ID
575577
/* If that didn't work then maybe a public key if device ID or callback.
576578
*/

‎tests/api/test_asn.c‎

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
#include <tests/api/api.h>
2525
#include <tests/api/test_asn.h>
26+
#include <tests/api/test_oom.h>
2627

2728
#include <wolfssl/wolfcrypt/asn.h>
2829
#include <wolfssl/wolfcrypt/asn_public.h>
@@ -3051,3 +3052,207 @@ int test_wc_AsnFeatureCoverage(void)
30513052
#endif /* !NO_ASN && HAVE_ECC && USE_CERT_BUFFERS_256 && !HAVE_FIPS */
30523053
return EXPECT_RESULT();
30533054
}
3055+
3056+
#if defined(USE_WOLFSSL_MEMORY) && !defined(WOLFSSL_NO_MALLOC) && \
3057+
!defined(WOLFSSL_STATIC_MEMORY) && !defined(WOLFSSL_MEM_FAIL_COUNT) && \
3058+
!defined(WOLFSSL_FORCE_MALLOC_FAIL_TEST) && !defined(NO_ASN) && \
3059+
defined(HAVE_ECC) && !defined(NO_ECC_MAKE_PUB) && !defined(WC_NO_RNG) && \
3060+
!defined(WOLFSSL_NO_ECC_DERIVE_PUB_ON_DECODE) && \
3061+
defined(HAVE_ECC_KEY_EXPORT) && \
3062+
defined(USE_CERT_BUFFERS_256) && !defined(HAVE_FIPS) && \
3063+
!defined(HAVE_SELFTEST) && !defined(WOLF_CRYPTO_CB_ONLY_ECC) && \
3064+
!defined(WOLFSSL_ATECC508A) && !defined(WOLFSSL_ATECC608A) && \
3065+
!defined(WOLFSSL_MICROCHIP_TA100) && !defined(WOLFSSL_CRYPTOCELL) && \
3066+
!defined(WOLFSSL_SILABS_SE_ACCEL) && !defined(WOLFSSL_KCAPI_ECC) && \
3067+
!defined(WOLFSSL_QNX_CAAM) && !defined(WOLFSSL_IMXRT1170_CAAM)
3068+
/* Fail Nth alloc to target public key derive. */
3069+
WOLFSSL_TEST_OOM_CALLBACKS(ecc_oom)
3070+
#endif /* USE_WOLFSSL_MEMORY && ... */
3071+
3072+
/* Decode should best-effort derive omitted SEC1 public point. */
3073+
int test_wc_EccPrivateKeyDecode_derive_pub(void)
3074+
{
3075+
EXPECT_DECLS;
3076+
#if !defined(NO_ASN) && defined(HAVE_ECC) && !defined(NO_ECC_MAKE_PUB) && \
3077+
!defined(WC_NO_RNG) && \
3078+
!defined(WOLFSSL_NO_ECC_DERIVE_PUB_ON_DECODE) && \
3079+
defined(HAVE_ECC_KEY_EXPORT) && \
3080+
defined(USE_CERT_BUFFERS_256) && !defined(HAVE_FIPS) && \
3081+
!defined(HAVE_SELFTEST) && !defined(WOLF_CRYPTO_CB_ONLY_ECC) && \
3082+
!defined(WOLFSSL_ATECC508A) && !defined(WOLFSSL_ATECC608A) && \
3083+
!defined(WOLFSSL_MICROCHIP_TA100) && !defined(WOLFSSL_CRYPTOCELL) && \
3084+
!defined(WOLFSSL_SILABS_SE_ACCEL) && !defined(WOLFSSL_KCAPI_ECC) && \
3085+
!defined(WOLFSSL_QNX_CAAM) && !defined(WOLFSSL_IMXRT1170_CAAM)
3086+
ecc_key fullKey;
3087+
ecc_key privOnlyKey;
3088+
WC_RNG rng;
3089+
word32 idx;
3090+
byte privOnlyDer[256];
3091+
int privOnlyDerSz = 0;
3092+
byte fullPub[256];
3093+
word32 fullPubSz = sizeof(fullPub);
3094+
byte derivedPub[256];
3095+
word32 derivedPubSz = sizeof(derivedPub);
3096+
3097+
XMEMSET(&fullKey, 0, sizeof(fullKey));
3098+
XMEMSET(&privOnlyKey, 0, sizeof(privOnlyKey));
3099+
/* wc_FreeRng() below runs unconditionally, so rng must be safe to free
3100+
* even if wc_InitRng() fails. */
3101+
XMEMSET(&rng, 0, sizeof(rng));
3102+
3103+
ExpectIntEQ(wc_InitRng(&rng), 0);
3104+
3105+
ExpectIntEQ(wc_ecc_init(&fullKey), 0);
3106+
idx = 0;
3107+
ExpectIntEQ(wc_EccPrivateKeyDecode(ecc_clikey_der_256, &idx, &fullKey,
3108+
sizeof_ecc_clikey_der_256), 0);
3109+
ExpectIntEQ(fullKey.type, ECC_PRIVATEKEY);
3110+
PRIVATE_KEY_UNLOCK();
3111+
ExpectIntEQ(wc_ecc_export_x963(&fullKey, fullPub, &fullPubSz), 0);
3112+
PRIVATE_KEY_LOCK();
3113+
3114+
/* Re-encode as private-key-only SEC1 DER. */
3115+
ExpectIntGT(privOnlyDerSz = wc_EccPrivateKeyToDer(&fullKey, privOnlyDer,
3116+
sizeof(privOnlyDer)), 0);
3117+
3118+
/* No RNG set: derivation still runs, but with key->rng NULL the
3119+
* projective-coordinate randomization is skipped even when
3120+
* ECC_TIMING_RESISTANT is on - EccDerivePubBestEffort() does not stand
3121+
* up a temporary RNG. */
3122+
ExpectIntEQ(wc_ecc_init(&privOnlyKey), 0);
3123+
idx = 0;
3124+
ExpectIntEQ(wc_EccPrivateKeyDecode(privOnlyDer, &idx, &privOnlyKey,
3125+
(word32)privOnlyDerSz), 0);
3126+
ExpectIntEQ(privOnlyKey.type, ECC_PRIVATEKEY);
3127+
PRIVATE_KEY_UNLOCK();
3128+
ExpectIntEQ(wc_ecc_export_x963(&privOnlyKey, derivedPub, &derivedPubSz),
3129+
0);
3130+
PRIVATE_KEY_LOCK();
3131+
ExpectIntEQ(derivedPubSz, fullPubSz);
3132+
ExpectBufEQ(derivedPub, fullPub, fullPubSz);
3133+
wc_ecc_free(&privOnlyKey);
3134+
3135+
/* Setting an RNG blinds the scalar mult; same derived point. */
3136+
derivedPubSz = sizeof(derivedPub);
3137+
ExpectIntEQ(wc_ecc_init(&privOnlyKey), 0);
3138+
ExpectIntEQ(wc_ecc_set_rng(&privOnlyKey, &rng), 0);
3139+
idx = 0;
3140+
ExpectIntEQ(wc_EccPrivateKeyDecode(privOnlyDer, &idx, &privOnlyKey,
3141+
(word32)privOnlyDerSz), 0);
3142+
3143+
/* Public point derived, key fully usable. */
3144+
ExpectIntEQ(privOnlyKey.type, ECC_PRIVATEKEY);
3145+
PRIVATE_KEY_UNLOCK();
3146+
ExpectIntEQ(wc_ecc_export_x963(&privOnlyKey, derivedPub, &derivedPubSz),
3147+
0);
3148+
PRIVATE_KEY_LOCK();
3149+
ExpectIntEQ(derivedPubSz, fullPubSz);
3150+
ExpectBufEQ(derivedPub, fullPub, fullPubSz);
3151+
3152+
wc_ecc_free(&privOnlyKey);
3153+
wc_ecc_free(&fullKey);
3154+
3155+
#if defined(PLUTON_CRYPTO_ECC) || defined(WOLF_CRYPTO_CB)
3156+
/* devId key left ECC_PRIVATEKEY_ONLY: device derives it. */
3157+
ExpectIntEQ(wc_ecc_init_ex(&privOnlyKey, NULL, 1), 0);
3158+
ExpectIntEQ(wc_ecc_set_rng(&privOnlyKey, &rng), 0);
3159+
idx = 0;
3160+
ExpectIntEQ(wc_EccPrivateKeyDecode(privOnlyDer, &idx, &privOnlyKey,
3161+
(word32)privOnlyDerSz), 0);
3162+
ExpectIntEQ(privOnlyKey.type, ECC_PRIVATEKEY_ONLY);
3163+
wc_ecc_free(&privOnlyKey);
3164+
#endif
3165+
3166+
#if defined(USE_WOLFSSL_MEMORY) && !defined(WOLFSSL_NO_MALLOC) && \
3167+
!defined(WOLFSSL_STATIC_MEMORY) && !defined(WOLFSSL_MEM_FAIL_COUNT) && \
3168+
!defined(WOLFSSL_FORCE_MALLOC_FAIL_TEST)
3169+
{
3170+
wolfSSL_Malloc_cb prevMalloc = NULL;
3171+
wolfSSL_Free_cb prevFree = NULL;
3172+
wolfSSL_Realloc_cb prevRealloc = NULL;
3173+
int allocatorsSet = 0;
3174+
int totalAllocCount = 0;
3175+
int i;
3176+
3177+
ExpectIntEQ(wolfSSL_GetAllocators(&prevMalloc, &prevFree, &prevRealloc),
3178+
0);
3179+
ExpectIntEQ(wolfSSL_SetAllocators(ecc_oom_malloc_cb, ecc_oom_free_cb,
3180+
ecc_oom_realloc_cb), 0);
3181+
if (EXPECT_SUCCESS()) {
3182+
allocatorsSet = 1;
3183+
}
3184+
3185+
/* Count the allocations one decode-with-derive makes. Injection is
3186+
* armed only around the decode so wc_ecc_init() is never starved. */
3187+
ecc_oom_count = 0;
3188+
ecc_oom_fail_at = 0;
3189+
ecc_oom_failed = 0;
3190+
ExpectIntEQ(wc_ecc_init(&privOnlyKey), 0);
3191+
idx = 0;
3192+
ecc_oom_active = 1;
3193+
ExpectIntEQ(wc_EccPrivateKeyDecode(privOnlyDer, &idx, &privOnlyKey,
3194+
(word32)privOnlyDerSz), 0);
3195+
ecc_oom_active = 0;
3196+
totalAllocCount = ecc_oom_count;
3197+
wc_ecc_free(&privOnlyKey);
3198+
/* The armed window must actually have allocated something, or the
3199+
* loop below passes vacuously without exercising any OOM path. */
3200+
ExpectIntGE(totalAllocCount, 1);
3201+
3202+
/* Fail each allocation in turn. Whatever fails, decode must never
3203+
* report a derived public key it does not have: the key comes back
3204+
* either fully derived and correct, or still ECC_PRIVATEKEY_ONLY. */
3205+
for (i = 1; EXPECT_SUCCESS() && (i <= totalAllocCount); i++) {
3206+
int decodeRet;
3207+
3208+
ecc_oom_count = 0;
3209+
ecc_oom_fail_at = i;
3210+
ecc_oom_failed = 0;
3211+
derivedPubSz = sizeof(derivedPub);
3212+
3213+
ExpectIntEQ(wc_ecc_init(&privOnlyKey), 0);
3214+
idx = 0;
3215+
ecc_oom_active = 1;
3216+
decodeRet = wc_EccPrivateKeyDecode(privOnlyDer, &idx, &privOnlyKey,
3217+
(word32)privOnlyDerSz);
3218+
ecc_oom_active = 0;
3219+
/* The injection must actually have fired, otherwise this
3220+
* iteration passes vacuously without exercising any OOM path. */
3221+
ExpectIntEQ(ecc_oom_failed, 1);
3222+
3223+
/* A failure inside the decode itself is fine; only the
3224+
* best-effort derivation is required to be non-fatal. */
3225+
if (decodeRet == 0) {
3226+
ExpectIntNE(privOnlyKey.type, ECC_PUBLICKEY);
3227+
if (privOnlyKey.type == ECC_PRIVATEKEY) {
3228+
PRIVATE_KEY_UNLOCK();
3229+
ExpectIntEQ(wc_ecc_export_x963(&privOnlyKey, derivedPub,
3230+
&derivedPubSz), 0);
3231+
PRIVATE_KEY_LOCK();
3232+
ExpectIntEQ(derivedPubSz, fullPubSz);
3233+
ExpectBufEQ(derivedPub, fullPub, fullPubSz);
3234+
}
3235+
else {
3236+
ExpectIntEQ(privOnlyKey.type, ECC_PRIVATEKEY_ONLY);
3237+
}
3238+
}
3239+
3240+
wc_ecc_free(&privOnlyKey);
3241+
}
3242+
3243+
ecc_oom_active = 0;
3244+
ecc_oom_fail_at = 0;
3245+
3246+
if (allocatorsSet) {
3247+
(void)wolfSSL_SetAllocators(prevMalloc, prevFree, prevRealloc);
3248+
}
3249+
}
3250+
#endif /* USE_WOLFSSL_MEMORY */
3251+
3252+
wc_FreeRng(&rng);
3253+
#endif /* !NO_ASN && HAVE_ECC && !NO_ECC_MAKE_PUB &&
3254+
* !WOLFSSL_NO_ECC_DERIVE_PUB_ON_DECODE && HAVE_ECC_KEY_EXPORT &&
3255+
* USE_CERT_BUFFERS_256 && !HAVE_FIPS && !HAVE_SELFTEST &&
3256+
* !WOLF_CRYPTO_CB_ONLY_ECC */
3257+
return EXPECT_RESULT();
3258+
}

‎tests/api/test_asn.h‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ int test_ToTraditional_ex_mldsa_bad_params(void);
4646
int test_wc_SignCert_buffer_bounds(void);
4747
int test_wc_AsnDecisionCoverage(void);
4848
int test_wc_AsnFeatureCoverage(void);
49+
int test_wc_EccPrivateKeyDecode_derive_pub(void);
4950

5051
#define TEST_ASN_DECLS \
5152
TEST_DECL_GROUP("asn", test_SetAsymKeyDer), \
@@ -69,6 +70,7 @@ int test_wc_AsnFeatureCoverage(void);
6970
TEST_DECL_GROUP("asn", test_ToTraditional_ex_mldsa_bad_params), \
7071
TEST_DECL_GROUP("asn", test_wc_SignCert_buffer_bounds), \
7172
TEST_DECL_GROUP("asn", test_wc_AsnDecisionCoverage), \
72-
TEST_DECL_GROUP("asn", test_wc_AsnFeatureCoverage)
73+
TEST_DECL_GROUP("asn", test_wc_AsnFeatureCoverage), \
74+
TEST_DECL_GROUP("asn", test_wc_EccPrivateKeyDecode_derive_pub)
7375

7476
#endif /* WOLFCRYPT_TEST_ASN_H */

0 commit comments

Comments
 (0)