Skip to content

Commit addd5ea

Browse files
committed
Fix private scalar handling under ECC key blinding and add CI coverage
1 parent 9ab8a4b commit addd5ea

8 files changed

Lines changed: 119 additions & 32 deletions

File tree

.github/configs/os-check-linux.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@
2828
{"name": "all-dtls13-frag-ch-no-mlkem", "minutes": 8.2,
2929
"configure": ["--enable-all", "--enable-dtls13", "--enable-dtls-frag-ch",
3030
"--disable-mlkem"]},
31+
{"name": "all-ecc-blind-k", "minutes": 8.0,
32+
"comment": "Only entry that sets WOLFSSL_ECC_BLIND_K (the blind-private-key entry sets WOLFSSL_BLIND_PRIVATE_KEY, which does not imply it). Keeps the read-only wc_ecc_key_get_priv() contract exercised in CI. pkcs11 is on because wc_pkcs11.c is not compiled anywhere else in this matrix.",
33+
"configure": ["--enable-all", "--enable-pkcs11",
34+
"CPPFLAGS=-DWOLFSSL_ECC_BLIND_K"]},
3135
{"name": "all-check-mem-zero", "minutes": 7.9,
3236
"configure": ["--enable-all", "CPPFLAGS=-DWOLFSSL_CHECK_MEM_ZERO"]},
3337
{"name": "all-faultharden-pk-privkey", "minutes": 7.8,

src/pk_ec.c

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@
3737
#endif
3838
#ifndef WOLFSSL_HAVE_ECC_KEY_GET_PRIV
3939
/* FIPS build has replaced ecc.h. */
40-
#define wc_ecc_key_get_priv(key) (&((key)->k))
40+
#define wc_ecc_key_get_priv(key) (&((key)->k))
41+
#define ecc_get_k_raw(key) (&((key)->k))
42+
#define ecc_blind_k_rng(key, rng) 0
4143
#define WOLFSSL_HAVE_ECC_KEY_GET_PRIV
4244
#endif
4345

@@ -3161,9 +3163,15 @@ static int wolfssl_ec_key_int_copy(ecc_key* dst, const ecc_key* src)
31613163
}
31623164

31633165
if (ret == 0) {
3164-
/* Copy private key. */
3165-
ret = mp_copy(wc_ecc_key_get_priv((ecc_key*)src),
3166-
wc_ecc_key_get_priv(dst));
3166+
/* Copy the stored private scalar, and its blind where the build
3167+
* keeps one. The wc_ecc_key_get_priv() accessor cannot be used
3168+
* here: it is read-only, and reading needs dst->dp, not set yet. */
3169+
ret = mp_copy(ecc_get_k_raw((ecc_key*)src), ecc_get_k_raw(dst));
3170+
#ifdef WOLFSSL_ECC_BLIND_K
3171+
if (ret == MP_OKAY) {
3172+
ret = mp_copy(((ecc_key*)src)->kb, dst->kb);
3173+
}
3174+
#endif
31673175
if (ret != MP_OKAY) {
31683176
WOLFSSL_MSG("mp_copy error");
31693177
}
@@ -4446,11 +4454,17 @@ int SetECKeyInternal(WOLFSSL_EC_KEY* eckey)
44464454

44474455
/* set privkey */
44484456
if ((ret == 1) && (eckey->priv_key != NULL)) {
4457+
/* Write the stored scalar, then install a fresh blind so any
4458+
* blind left from a previous use of this key is replaced. */
44494459
if (wolfssl_bn_get_value(eckey->priv_key,
4450-
wc_ecc_key_get_priv(key)) != 1) {
4460+
ecc_get_k_raw(key)) != 1) {
44514461
WOLFSSL_MSG("ec key priv error");
44524462
ret = WOLFSSL_FATAL_ERROR;
44534463
}
4464+
if ((ret == 1) && (ecc_blind_k_rng(key, NULL) != 0)) {
4465+
WOLFSSL_MSG("ec key priv blind error");
4466+
ret = WOLFSSL_FATAL_ERROR;
4467+
}
44544468
/* private key */
44554469
if ((ret == 1) && (!mp_iszero(wc_ecc_key_get_priv(key)))) {
44564470
if (pubSet) {

wolfcrypt/src/ecc.c

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -378,15 +378,33 @@ ECC Curve Sizes:
378378
#endif
379379

380380
#ifdef WOLFSSL_ECC_BLIND_K
381+
/* Number of digits covered by the fixed-width XORs below. */
382+
#define ECC_BLIND_K_DIGITS(key) \
383+
((int)(((key)->dp->size + sizeof(mp_digit) - 1) / sizeof(mp_digit)))
384+
385+
/* The XORs read this many whole digits regardless of each operand's current
386+
* length, so operands written at partial width (e.g. by mp_copy()) must be
387+
* zero-extended first or stale digits fold into the value. mp_grow() cannot
388+
* fail for a curve-sized key; fail closed if it ever does. */
381389
mp_int* ecc_get_k(ecc_key* key)
382390
{
383-
mp_xor_ct(key->k, key->kb, key->dp->size, key->ku);
391+
if ((mp_grow(key->k, ECC_BLIND_K_DIGITS(key)) != MP_OKAY) ||
392+
(mp_grow(key->kb, ECC_BLIND_K_DIGITS(key)) != MP_OKAY)) {
393+
mp_forcezero(key->ku);
394+
}
395+
else {
396+
mp_xor_ct(key->k, key->kb, key->dp->size, key->ku);
397+
}
384398
return key->ku;
385399
}
386400
void ecc_blind_k(ecc_key* key, mp_int* b)
387401
{
388-
mp_xor_ct(key->k, b, key->dp->size, key->k);
389-
mp_xor_ct(key->kb, b, key->dp->size, key->kb);
402+
if ((mp_grow(key->k, ECC_BLIND_K_DIGITS(key)) == MP_OKAY) &&
403+
(mp_grow(key->kb, ECC_BLIND_K_DIGITS(key)) == MP_OKAY) &&
404+
(mp_grow(b, ECC_BLIND_K_DIGITS(key)) == MP_OKAY)) {
405+
mp_xor_ct(key->k, b, key->dp->size, key->k);
406+
mp_xor_ct(key->kb, b, key->dp->size, key->kb);
407+
}
390408
}
391409
int ecc_blind_k_rng(ecc_key* key, WC_RNG* rng)
392410
{
@@ -405,11 +423,17 @@ int ecc_blind_k_rng(ecc_key* key, WC_RNG* rng)
405423
}
406424
}
407425
if (ret == 0) {
408-
ret = mp_rand(key->kb, (key->dp->size + sizeof(mp_digit) - 1) /
409-
sizeof(mp_digit), rng);
426+
ret = mp_rand(key->kb, ECC_BLIND_K_DIGITS(key), rng);
427+
if (ret == 0) {
428+
ret = mp_grow(key->k, ECC_BLIND_K_DIGITS(key));
429+
}
410430
if (ret == 0) {
411431
mp_xor_ct(key->k, key->kb, key->dp->size, key->k);
412432
}
433+
else {
434+
/* No blind installed - keep the stored pair consistent. */
435+
mp_forcezero(key->kb);
436+
}
413437
}
414438

415439
if (rng == &local_rng) {
@@ -418,6 +442,13 @@ int ecc_blind_k_rng(ecc_key* key, WC_RNG* rng)
418442
return ret;
419443
}
420444

445+
void ecc_forcezero_k(ecc_key* key)
446+
{
447+
mp_forcezero(key->k);
448+
mp_forcezero(key->kb);
449+
mp_forcezero(key->ku);
450+
}
451+
421452
mp_int* wc_ecc_key_get_priv(ecc_key* key)
422453
{
423454
return ecc_get_k(key);

wolfcrypt/src/eccsi.c

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@
3838

3939
#ifndef WOLFSSL_HAVE_ECC_KEY_GET_PRIV
4040
/* FIPS build has replaced ecc.h. */
41-
#define wc_ecc_key_get_priv(key) (&((key)->k))
41+
#define wc_ecc_key_get_priv(key) (&((key)->k))
42+
#define ecc_get_k_raw(key) (&((key)->k))
43+
#define ecc_blind_k_rng(key, rng) 0
44+
#define ecc_forcezero_k(key) mp_forcezero(&((key)->k))
4245
#define WOLFSSL_HAVE_ECC_KEY_GET_PRIV
4346
#endif
4447

@@ -679,8 +682,11 @@ static int eccsi_decode_key(EccsiKey* key, const byte* data)
679682
int err;
680683

681684
/* Read the secret value from key size bytes. */
682-
err = mp_read_unsigned_bin(wc_ecc_key_get_priv(&key->ecc), data,
685+
err = mp_read_unsigned_bin(ecc_get_k_raw(&key->ecc), data,
683686
(word32)key->ecc.dp->size);
687+
if (err == 0) {
688+
err = ecc_blind_k_rng(&key->ecc, NULL);
689+
}
684690
if (err == 0) {
685691
data += key->ecc.dp->size;
686692
/* Read public key. */
@@ -809,9 +815,12 @@ int wc_ImportEccsiPrivateKey(EccsiKey* key, const byte* data, word32 sz)
809815
}
810816

811817
if (err == 0) {
812-
err = mp_read_unsigned_bin(wc_ecc_key_get_priv(&key->ecc), data,
818+
err = mp_read_unsigned_bin(ecc_get_k_raw(&key->ecc), data,
813819
(word32)key->ecc.dp->size);
814820
}
821+
if (err == 0) {
822+
err = ecc_blind_k_rng(&key->ecc, NULL);
823+
}
815824

816825
return err;
817826
}
@@ -926,7 +935,7 @@ static int eccsi_make_pair(EccsiKey* key, WC_RNG* rng,
926935
/* Step 5: ensure SSK and HS are non-zero (code lines above) */
927936

928937
/* Step 6: Copy out SSK (done during calc) and PVT. Erase v */
929-
mp_forcezero(wc_ecc_key_get_priv(&key->pubkey));
938+
ecc_forcezero_k(&key->pubkey);
930939

931940
return err;
932941
}
@@ -2010,10 +2019,10 @@ int wc_SignEccsiHash(EccsiKey* key, WC_RNG* rng, enum wc_HashType hashType,
20102019
if (err == 0) {
20112020
j = wc_ecc_key_get_priv(&key->pubkey);
20122021
err = mp_mulmod(s, j, &key->params.order, s);
2022+
/* Erase j on the failure path too. */
2023+
ecc_forcezero_k(&key->pubkey);
20132024
}
20142025
if (err == 0) {
2015-
mp_forcezero(j);
2016-
20172026
/* Step 6: s = s' fitted */
20182027
err = eccsi_fit_to_octets(s, &key->params.order, (int)sz, s);
20192028
}

wolfcrypt/src/port/silabs/silabs_ecc.c

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ static sl_se_key_descriptor_t private_device_key =
4040

4141
#ifndef WOLFSSL_HAVE_ECC_KEY_GET_PRIV
4242
/* FIPS build has replaced ecc.h. */
43-
#define wc_ecc_key_get_priv(key) (&((key)->k))
43+
#define wc_ecc_key_get_priv(key) (&((key)->k))
44+
#define ecc_get_k_raw(key) (&((key)->k))
45+
#define ecc_blind_k_rng(key, rng) 0
4446
#define WOLFSSL_HAVE_ECC_KEY_GET_PRIV
4547
#endif
4648

@@ -209,12 +211,18 @@ int silabs_ecc_make_key(ecc_key* key, int keysize)
209211
key->type = ECC_PRIVATEKEY;
210212

211213
/* copy key to mp components */
212-
mp_read_unsigned_bin(key->pubkey.x,
213-
key->key.storage.location.buffer.pointer, keysize);
214-
mp_read_unsigned_bin(key->pubkey.y,
215-
key->key.storage.location.buffer.pointer + keysize, keysize);
216-
mp_read_unsigned_bin(wc_ecc_key_get_priv(key),
217-
key->key.storage.location.buffer.pointer + (2 * keysize), keysize);
214+
if ((mp_read_unsigned_bin(key->pubkey.x,
215+
key->key.storage.location.buffer.pointer,
216+
keysize) != MP_OKAY) ||
217+
(mp_read_unsigned_bin(key->pubkey.y,
218+
key->key.storage.location.buffer.pointer + keysize,
219+
keysize) != MP_OKAY) ||
220+
(mp_read_unsigned_bin(ecc_get_k_raw(key),
221+
key->key.storage.location.buffer.pointer + (2 * keysize),
222+
keysize) != MP_OKAY) ||
223+
(ecc_blind_k_rng(key, NULL) != 0)) {
224+
return WC_HW_E;
225+
}
218226
}
219227

220228
return (sl_stat == SL_STATUS_OK) ? 0 : WC_HW_E;

wolfcrypt/src/sakke.c

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@
3939

4040
#ifndef WOLFSSL_HAVE_ECC_KEY_GET_PRIV
4141
/* FIPS build has replaced ecc.h. */
42-
#define wc_ecc_key_get_priv(key) (&((key)->k))
42+
#define wc_ecc_key_get_priv(key) (&((key)->k))
43+
#define ecc_get_k_raw(key) (&((key)->k))
44+
#define ecc_blind_k_rng(key, rng) 0
4345
#define WOLFSSL_HAVE_ECC_KEY_GET_PRIV
4446
#endif
4547

@@ -533,14 +535,18 @@ int wc_MakeSakkeKey(SakkeKey* key, WC_RNG* rng)
533535
err = RNG_FAILURE_E;
534536
}
535537
if (err == 0) {
536-
err = mp_rand(wc_ecc_key_get_priv(&key->ecc), digits, rng);
538+
err = mp_rand(ecc_get_k_raw(&key->ecc), digits, rng);
537539
}
538540
if (err == 0) {
539-
err = mp_mod(wc_ecc_key_get_priv(&key->ecc), &key->params.q,
540-
wc_ecc_key_get_priv(&key->ecc));
541+
err = mp_mod(ecc_get_k_raw(&key->ecc), &key->params.q,
542+
ecc_get_k_raw(&key->ecc));
541543
}
542544
}
543-
while ((err == 0) && mp_iszero(wc_ecc_key_get_priv(&key->ecc)));
545+
while ((err == 0) && mp_iszero(ecc_get_k_raw(&key->ecc)));
546+
547+
if (err == 0) {
548+
err = ecc_blind_k_rng(&key->ecc, rng);
549+
}
544550
}
545551
if (err == 0) {
546552
/* Calculate public key by multiply master secret by base point. */
@@ -672,9 +678,12 @@ int wc_ImportSakkeKey(SakkeKey* key, const byte* data, word32 sz)
672678

673679
if (err == 0) {
674680
/* Read the secret value from key size bytes. */
675-
err = mp_read_unsigned_bin(wc_ecc_key_get_priv(&key->ecc), data,
681+
err = mp_read_unsigned_bin(ecc_get_k_raw(&key->ecc), data,
676682
(word32)key->ecc.dp->size);
677683
}
684+
if (err == 0) {
685+
err = ecc_blind_k_rng(&key->ecc, NULL);
686+
}
678687
if (err == 0) {
679688
data += key->ecc.dp->size;
680689
/* Read the public key point's x value from key size bytes. */
@@ -770,9 +779,12 @@ int wc_ImportSakkePrivateKey(SakkeKey* key, const byte* data, word32 sz)
770779

771780
if (err == 0) {
772781
/* Read the secret value from key size bytes. */
773-
err = mp_read_unsigned_bin(wc_ecc_key_get_priv(&key->ecc), data,
782+
err = mp_read_unsigned_bin(ecc_get_k_raw(&key->ecc), data,
774783
(word32)key->ecc.dp->size);
775784
}
785+
if (err == 0) {
786+
err = ecc_blind_k_rng(&key->ecc, NULL);
787+
}
776788

777789
return err;
778790
}

wolfcrypt/src/wc_pkcs11.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@
4141

4242
#ifndef WOLFSSL_HAVE_ECC_KEY_GET_PRIV
4343
/* FIPS build has replaced ecc.h. */
44-
#define wc_ecc_key_get_priv(key) (&((key)->k))
44+
#define wc_ecc_key_get_priv(key) (&((key)->k))
45+
#define ecc_forcezero_k(key) mp_forcezero(&((key)->k))
4546
#define WOLFSSL_HAVE_ECC_KEY_GET_PRIV
4647
#endif
4748

@@ -2186,7 +2187,7 @@ int wc_Pkcs11StoreKey(Pkcs11Token* token, int type, int clear, void* key)
21862187
ret = ret2;
21872188
}
21882189
if (ret == 0 && clear)
2189-
mp_forcezero(wc_ecc_key_get_priv(eccKey));
2190+
ecc_forcezero_k(eccKey);
21902191
break;
21912192
}
21922193
#endif

wolfssl/wolfcrypt/ecc.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -687,15 +687,23 @@ struct ecc_key {
687687
#define ecc_get_k(key) (key)->k
688688
#define ecc_blind_k(key, b) (void)b
689689
#define ecc_blind_k_rng(key, rng) 0
690+
#define ecc_forcezero_k(key) mp_forcezero((key)->k)
690691

691692
#define wc_ecc_key_get_priv(key) (key)->k
692693
#else
693694
mp_int* ecc_get_k(ecc_key* key);
694695
void ecc_blind_k(ecc_key* key, mp_int* b);
695696
int ecc_blind_k_rng(ecc_key* key, WC_RNG* rng);
697+
WOLFSSL_LOCAL void ecc_forcezero_k(ecc_key* key);
696698

697699
WOLFSSL_API mp_int* wc_ecc_key_get_priv(ecc_key* key);
698700
#endif
701+
/* Writable handle on the stored private scalar. With blinding enabled,
702+
* wc_ecc_key_get_priv() returns a value regenerated into scratch, so it is
703+
* read-only: writes through it are discarded and erasing it leaves the
704+
* secret in place. Write a new scalar through this instead, then install a
705+
* fresh blind with ecc_blind_k_rng(); erase with ecc_forcezero_k(). */
706+
#define ecc_get_k_raw(key) (key)->k
699707

700708
#define WOLFSSL_HAVE_ECC_KEY_GET_PRIV
701709

0 commit comments

Comments
 (0)