Skip to content

Commit 36b66aa

Browse files
committed
cryptocb: add WC_PK_TYPE_EC_CHECK_PUB_KEY for ECC key validation offload
Add a crypto-callback operation for validating an ECC key. Under WOLF_CRYPTO_CB_ONLY_ECC validation now fails closed with NO_VALID_DEVID when no device handles the operation; previously such keys were accepted unvalidated. This is a deliberate compatibility break, documented at the dispatch site.
1 parent 0d98a65 commit 36b66aa

7 files changed

Lines changed: 324 additions & 6 deletions

File tree

doc/dox_comments/header_files/cryptocb.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,3 +280,34 @@ int wc_CryptoCb_AesSetKey(Aes* aes, const byte* key, word32 keySz);
280280
\sa wc_ecc_make_pub
281281
*/
282282
int wc_CryptoCb_EccMakePub(ecc_key* key, ecc_point* pubOut);
283+
284+
/*!
285+
\ingroup CryptoCb
286+
287+
\brief Offload validating an ECC key to a CryptoCB device.
288+
289+
Used by wc_ecc_check_key and the key generation / import validation paths.
290+
The public point crosses the (math-free) callback boundary as X9.63
291+
uncompressed bytes in wc_CryptoInfo.pk.ecc_check_pub (\c pubKey /
292+
\c pubKeySz); this wrapper serializes key->pubkey so a device handler only
293+
deals with byte arrays. When the key state is \c ECC_PRIVATEKEY_ONLY,
294+
\c pubKey is NULL and \c pubKeySz is 0 so the handler can distinguish "no
295+
public point" from an invalid zero-coordinate public point that must be
296+
validated and rejected. The caller's intent crosses in \c checkOrder and
297+
\c checkPriv.
298+
299+
\param key ECC key to validate (curve identity from key->dp)
300+
\param checkOrder when 1 the caller requested validation that the point
301+
has the curve order (point * order == infinity)
302+
\param checkPriv when 1 the caller also requested validation of the
303+
private part (scalar range, consistency with the public
304+
point)
305+
306+
\return 0 if the key is valid
307+
\return CRYPTOCB_UNAVAILABLE if no device handles the operation (wolfCrypt
308+
falls back to software)
309+
310+
\sa wc_CryptoCb_RegisterDevice
311+
\sa wc_ecc_check_key
312+
*/
313+
int wc_CryptoCb_EccCheckPubKey(ecc_key* key, int checkOrder, int checkPriv);

tests/swdev/swdev.c

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,48 @@ static int swdev_ecc_make_pub(wc_CryptoInfo* info)
194194
return ret;
195195
}
196196

197+
#ifdef HAVE_ECC_CHECK_KEY
198+
static int swdev_ecc_check_pub(wc_CryptoInfo* info)
199+
{
200+
ecc_key* key = info->pk.ecc_check_pub.key;
201+
int ret = 0;
202+
int validatedFromWire = 0;
203+
204+
if (info->pk.ecc_check_pub.pubKeySz == 0) {
205+
return ECC_INF_E;
206+
}
207+
#ifdef HAVE_ECC_KEY_IMPORT
208+
if (key->idx >= 0) {
209+
WC_DECLARE_VAR(pubOnly, ecc_key, 1, key->heap);
210+
WC_ALLOC_VAR(pubOnly, ecc_key, 1, key->heap);
211+
if (!WC_VAR_OK(pubOnly))
212+
ret = MEMORY_E;
213+
else
214+
ret = wc_ecc_init_ex(pubOnly, key->heap, INVALID_DEVID);
215+
if (ret == 0) {
216+
ret = wc_ecc_import_x963_ex(info->pk.ecc_check_pub.pubKey,
217+
info->pk.ecc_check_pub.pubKeySz, pubOnly, key->dp->id);
218+
if (ret == 0 &&
219+
wc_ecc_cmp_point(&pubOnly->pubkey, &key->pubkey) != MP_EQ) {
220+
/* wire bytes disagree with key->pubkey */
221+
ret = BAD_STATE_E;
222+
}
223+
if (ret == 0)
224+
ret = wc_ecc_check_key(pubOnly);
225+
wc_ecc_free(pubOnly);
226+
}
227+
WC_FREE_VAR(pubOnly, key->heap);
228+
validatedFromWire = 1;
229+
}
230+
#endif
231+
if (ret == 0 && (!validatedFromWire ||
232+
(info->pk.ecc_check_pub.checkPriv &&
233+
key->type == ECC_PRIVATEKEY))) {
234+
ret = wc_ecc_check_key(key);
235+
}
236+
return ret;
237+
}
238+
#endif /* HAVE_ECC_CHECK_KEY */
197239
#endif /* HAVE_ECC */
198240

199241
#ifndef NO_SHA256
@@ -755,6 +797,10 @@ WC_SWDEV_EXPORT int wc_SwDev_Callback(int devId, wc_CryptoInfo* info,
755797
return swdev_ecc_get_sig_size(info);
756798
case WC_PK_TYPE_EC_MAKE_PUB:
757799
return swdev_ecc_make_pub(info);
800+
#ifdef HAVE_ECC_CHECK_KEY
801+
case WC_PK_TYPE_EC_CHECK_PUB_KEY:
802+
return swdev_ecc_check_pub(info);
803+
#endif
758804
#endif /* HAVE_ECC */
759805
default:
760806
return CRYPTOCB_UNAVAILABLE;

wolfcrypt/src/cryptocb.c

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ static const char* GetPkTypeStr(int pk)
158158
case WC_PK_TYPE_EC_GET_SIZE: return "ECC GetSize";
159159
case WC_PK_TYPE_EC_GET_SIG_SIZE: return "ECC GetSigSize";
160160
case WC_PK_TYPE_EC_MAKE_PUB: return "ECC MakePub";
161+
case WC_PK_TYPE_EC_CHECK_PUB_KEY: return "ECC CheckPubKey";
161162
}
162163
return NULL;
163164
}
@@ -938,6 +939,66 @@ int wc_CryptoCb_EccMakePub(ecc_key* key, ecc_point* pubOut)
938939
return wc_CryptoCb_TranslateErrorCode(ret);
939940
}
940941

942+
#ifdef HAVE_ECC_CHECK_KEY
943+
int wc_CryptoCb_EccCheckPubKey(ecc_key* key, int checkOrder, int checkPriv)
944+
{
945+
int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE);
946+
CryptoCb* dev;
947+
948+
if (key == NULL || key->dp == NULL)
949+
return ret;
950+
951+
if (key->dp->size > MAX_ECC_BYTES)
952+
return ret;
953+
954+
/* locate registered callback */
955+
dev = wc_CryptoCb_FindDevice(key->devId, WC_ALGO_TYPE_PK);
956+
if (dev && dev->cb) {
957+
wc_CryptoInfo cryptoInfo;
958+
word32 curveSz = (word32)key->dp->size;
959+
word32 ptSz = 1 + 2 * curveSz;
960+
word32 xSz = curveSz;
961+
word32 ySz = curveSz;
962+
/* a key with no host-side public point is represented by
963+
* ECC_PRIVATEKEY_ONLY and crosses as pubKey = NULL / pubKeySz = 0.
964+
* If the key state says a public point exists, serialize it even when
965+
* the coordinates are invalid (e.g. 0,0) so the device can reject the
966+
* actual input instead of seeing "no public point". */
967+
int havePub = (key->type != ECC_PRIVATEKEY_ONLY);
968+
WC_DECLARE_VAR(buf, byte, (1 + 2 * MAX_ECC_BYTES), key->heap);
969+
WC_ALLOC_VAR_EX(buf, byte, (1 + 2 * MAX_ECC_BYTES), key->heap,
970+
DYNAMIC_TYPE_ECC_BUFFER, return MEMORY_E);
971+
972+
ret = MP_OKAY;
973+
if (havePub) {
974+
/* serialize key->pubkey to X9.63 uncompressed (0x04 || X || Y) */
975+
buf[0] = ECC_POINT_UNCOMP;
976+
ret = wc_export_int(key->pubkey.x, buf + 1, &xSz, curveSz,
977+
WC_TYPE_UNSIGNED_BIN);
978+
if (ret == MP_OKAY)
979+
ret = wc_export_int(key->pubkey.y, buf + 1 + curveSz, &ySz,
980+
curveSz, WC_TYPE_UNSIGNED_BIN);
981+
}
982+
983+
if (ret == MP_OKAY) {
984+
XMEMSET(&cryptoInfo, 0, sizeof(cryptoInfo));
985+
cryptoInfo.algo_type = WC_ALGO_TYPE_PK;
986+
cryptoInfo.pk.type = WC_PK_TYPE_EC_CHECK_PUB_KEY;
987+
cryptoInfo.pk.ecc_check_pub.key = key;
988+
cryptoInfo.pk.ecc_check_pub.pubKey = havePub ? buf : NULL;
989+
cryptoInfo.pk.ecc_check_pub.pubKeySz = havePub ? ptSz : 0;
990+
cryptoInfo.pk.ecc_check_pub.checkOrder = checkOrder;
991+
cryptoInfo.pk.ecc_check_pub.checkPriv = checkPriv;
992+
993+
ret = dev->cb(dev->devId, &cryptoInfo, dev->ctx);
994+
}
995+
996+
WC_FREE_VAR_EX(buf, key->heap, DYNAMIC_TYPE_ECC_BUFFER);
997+
}
998+
999+
return wc_CryptoCb_TranslateErrorCode(ret);
1000+
}
1001+
#endif /* HAVE_ECC_CHECK_KEY */
9411002
#endif /* HAVE_ECC */
9421003

9431004
#ifdef HAVE_CURVE25519

wolfcrypt/src/ecc.c

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10792,13 +10792,37 @@ static int _ecc_validate_public_key(ecc_key* key, int partial, int priv)
1079210792
if (key == NULL)
1079310793
return BAD_FUNC_ARG;
1079410794

10795+
#if defined(WOLF_CRYPTO_CB) && defined(HAVE_ECC_CHECK_KEY) && \
10796+
!defined(WOLFSSL_CAAM)
10797+
/* Device-first: when a device is configured, let it validate the public
10798+
* key. Fall through to the software/HW path below only when the
10799+
* device reports the operation unavailable.
10800+
* CAAM is excluded: it relies on the software order-check below to detect
10801+
* whether an imported private key is an encrypted black key. */
10802+
#ifndef WOLF_CRYPTO_CB_FIND
10803+
if (key->devId != INVALID_DEVID)
10804+
#endif
10805+
{
10806+
err = wc_CryptoCb_EccCheckPubKey(key, !partial, priv);
10807+
if (err != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE))
10808+
return err;
10809+
err = MP_OKAY; /* device declined; fall through to software/HW */
10810+
}
10811+
#endif
10812+
1079510813
#ifndef HAVE_ECC_CHECK_PUBKEY_ORDER
10814+
#ifdef WOLF_CRYPTO_CB_ONLY_ECC
10815+
/* Software validation is stripped; the device-first check above either
10816+
* handled the key or reported the op unavailable, so fail closed rather
10817+
* than accept an unvalidated key. */
10818+
err = NO_VALID_DEVID;
10819+
#else
1079610820
/* consider key check success on HW crypto
10797-
* ex: ATECC508/608A, CryptoCell and Silabs
10798-
*
10799-
* consider key check success on most Crypt Cb only builds
10821+
* ex: ATECC508/608A, CryptoCell and Silabs which validate the key
10822+
* internally
1080010823
*/
1080110824
err = MP_OKAY;
10825+
#endif
1080210826

1080310827
#else
1080410828

@@ -10940,6 +10964,10 @@ WOLFSSL_ABI
1094010964
int wc_ecc_check_key(ecc_key* key)
1094110965
{
1094210966
int ret;
10967+
/* _ecc_validate_public_key is the single validation entry point: it is
10968+
* device-first (offloads to the crypto callback when a device is
10969+
* configured) and otherwise runs the software/HW checks, or fails closed
10970+
* under WOLF_CRYPTO_CB_ONLY_ECC. */
1094310971
ret = _ecc_validate_public_key(key, 0, 1);
1094410972
return ret;
1094510973
}

wolfcrypt/test/test.c

Lines changed: 136 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71998,11 +71998,14 @@ typedef struct {
7199871998
int exampleVar; /* flag for testing if only crypt is enabled. */
7199971999
#ifdef HAVE_ECC
7200072000
int eccMakePubCount; /* EC make-pub callback invocations */
72001+
int eccCheckPubCount; /* EC check-pubkey callback invocations */
7200172002
int eccMakePubBadFormat; /* when set, return a malformed (non-uncompressed)
7200272003
* point to exercise the wrapper's BUFFER_E path */
7200372004
int eccMakePubBadLen; /* when set, claim a result size different from the
7200472005
* curve's X9.63 length to exercise the wrapper's
7200572006
* size check */
72007+
int eccCheckPubExpectZeroPoint; /* require serialized 0,0 input */
72008+
int eccCheckPubSawZeroPoint; /* EC check-pubkey saw X9.63 0,0 */
7200672009
ecc_key* eccResidentKey; /* when set, the make-pub callback emits this key's
7200772010
* public point, simulating a private scalar
7200872011
* resident in the device (input key->k empty) */
@@ -72913,6 +72916,82 @@ static int myCryptoDevCb(int devIdArg, wc_CryptoInfo* info, void* ctx)
7291372916
ret = CRYPTOCB_UNAVAILABLE; /* no software emulation available */
7291472917
#endif
7291572918
}
72919+
#ifdef HAVE_ECC_CHECK_KEY
72920+
else if (info->pk.type == WC_PK_TYPE_EC_CHECK_PUB_KEY) {
72921+
ecc_key* k = info->pk.ecc_check_pub.key;
72922+
int validatedFromWire = 0;
72923+
myCtx->eccCheckPubCount++;
72924+
if (info->pk.ecc_check_pub.pubKeySz == 0) {
72925+
/* no host-side public point and this software device holds no
72926+
* resident key, so there is nothing to validate (matches the
72927+
* software answer for a missing public point) */
72928+
ret = ECC_INF_E;
72929+
}
72930+
else {
72931+
ret = 0;
72932+
if (myCtx != NULL && myCtx->eccCheckPubExpectZeroPoint) {
72933+
const byte* pub = info->pk.ecc_check_pub.pubKey;
72934+
word32 curveSz = (word32)k->dp->size;
72935+
word32 ptSz = 1 + 2 * curveSz;
72936+
word32 i;
72937+
72938+
if (info->pk.ecc_check_pub.pubKeySz != ptSz ||
72939+
pub[0] != ECC_POINT_UNCOMP) {
72940+
ret = BAD_STATE_E;
72941+
}
72942+
for (i = 1; ret == 0 && i < ptSz; i++) {
72943+
if (pub[i] != 0)
72944+
ret = BAD_STATE_E;
72945+
}
72946+
if (ret == 0)
72947+
myCtx->eccCheckPubSawZeroPoint = 1;
72948+
}
72949+
#ifdef HAVE_ECC_KEY_IMPORT
72950+
/* vault-style consumption: rebuild the public key from the
72951+
* wire bytes and validate the rebuilt key, proving the
72952+
* serialized point is sufficient and consistent with
72953+
* key->pubkey. The named-curve import does not apply to
72954+
* custom-curve keys (idx == ECC_CUSTOM_IDX). */
72955+
if (k->idx >= 0) {
72956+
WC_DECLARE_VAR(pubOnly, ecc_key, 1, HEAP_HINT);
72957+
WC_ALLOC_VAR(pubOnly, ecc_key, 1, HEAP_HINT);
72958+
if (!WC_VAR_OK(pubOnly))
72959+
ret = MEMORY_E;
72960+
else
72961+
ret = wc_ecc_init_ex(pubOnly, HEAP_HINT, INVALID_DEVID);
72962+
if (ret == 0) {
72963+
ret = wc_ecc_import_x963_ex(
72964+
info->pk.ecc_check_pub.pubKey,
72965+
info->pk.ecc_check_pub.pubKeySz, pubOnly,
72966+
k->dp->id);
72967+
if (ret == 0 && wc_ecc_cmp_point(&pubOnly->pubkey,
72968+
&k->pubkey) != MP_EQ) {
72969+
/* wire bytes disagree with key->pubkey */
72970+
ret = BAD_STATE_E;
72971+
}
72972+
if (ret == 0)
72973+
ret = wc_ecc_check_key(pubOnly);
72974+
wc_ecc_free(pubOnly);
72975+
}
72976+
WC_FREE_VAR(pubOnly, HEAP_HINT);
72977+
validatedFromWire = 1;
72978+
}
72979+
#endif
72980+
/* private/resident part (and custom-curve keys, where the
72981+
* named-curve import above is unavailable): validate via the
72982+
* key handle */
72983+
if (ret == 0 && (!validatedFromWire ||
72984+
(info->pk.ecc_check_pub.checkPriv &&
72985+
k->type == ECC_PRIVATEKEY))) {
72986+
/* set devId to invalid, so software is used */
72987+
k->devId = INVALID_DEVID;
72988+
ret = wc_ecc_check_key(k);
72989+
/* reset devId */
72990+
k->devId = devIdArg;
72991+
}
72992+
}
72993+
}
72994+
#endif
7291672995
#endif /* HAVE_ECC */
7291772996
#ifdef HAVE_CURVE25519
7291872997
if (info->pk.type == WC_PK_TYPE_CURVE25519_KEYGEN) {
@@ -74646,8 +74725,11 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t cryptocb_test(void)
7464674725
myCtx.exampleVar = 1;
7464774726
#ifdef HAVE_ECC
7464874727
myCtx.eccMakePubCount = 0;
74728+
myCtx.eccCheckPubCount = 0;
7464974729
myCtx.eccMakePubBadFormat = 0;
7465074730
myCtx.eccMakePubBadLen = 0;
74731+
myCtx.eccCheckPubExpectZeroPoint = 0;
74732+
myCtx.eccCheckPubSawZeroPoint = 0;
7465174733
myCtx.eccResidentKey = NULL;
7465274734
#endif
7465374735

@@ -74681,8 +74763,60 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t cryptocb_test(void)
7468174763
if (ret == 0)
7468274764
ret = ecc_test();
7468374765
PRIVATE_KEY_LOCK();
74684-
/* Confirm the new ECC make-pub callback was routed through the device and
74685-
* not silently handled in software. */
74766+
/* Confirm the new ECC pubkey callbacks were routed through the device and
74767+
* not silently handled in software. The check-pubkey callback is only
74768+
* exercised when wc_ecc_check_key is built (HAVE_ECC_CHECK_KEY) and the
74769+
* ecc_test calls to it are not skipped (WC_TEST_SKIP_ECC_CHECK_KEY); the
74770+
* counter legitimately stays 0 otherwise. CAAM is excluded because the
74771+
* check-pubkey dispatch in _ecc_validate_public_key is compiled out there
74772+
* (CAAM uses software validation failure to detect black keys). */
74773+
#if !defined(WOLF_CRYPTO_CB_ONLY_ECC) && defined(HAVE_ECC_CHECK_KEY) && \
74774+
!defined(WC_TEST_SKIP_ECC_CHECK_KEY) && !defined(WOLFSSL_CAAM)
74775+
if (ret == 0 && myCtx.eccCheckPubCount == 0)
74776+
ret = WC_TEST_RET_ENC_NC;
74777+
#endif
74778+
/* Regression: an explicit public point with zero coordinates must cross
74779+
* the callback boundary as X9.63 0x04||0||0, not as pubKey = NULL. */
74780+
#if !defined(WOLFSSL_SWDEV) && defined(HAVE_ECC_CHECK_KEY) && \
74781+
!defined(WOLFSSL_CAAM) && !defined(NO_ECC256)
74782+
if (ret == 0) {
74783+
WC_DECLARE_VAR(zeroKey, ecc_key, 1, HEAP_HINT);
74784+
int haveZeroKey = 0;
74785+
74786+
WC_ALLOC_VAR(zeroKey, ecc_key, 1, HEAP_HINT);
74787+
PRIVATE_KEY_UNLOCK();
74788+
if (!WC_VAR_OK(zeroKey))
74789+
ret = MEMORY_E;
74790+
else
74791+
ret = wc_ecc_init_ex(zeroKey, HEAP_HINT, devId);
74792+
if (ret == 0) {
74793+
haveZeroKey = 1;
74794+
ret = wc_ecc_set_curve(zeroKey, 32, ECC_SECP256R1);
74795+
}
74796+
if (ret == 0) {
74797+
zeroKey->type = ECC_PUBLICKEY;
74798+
myCtx.eccCheckPubExpectZeroPoint = 1;
74799+
myCtx.eccCheckPubSawZeroPoint = 0;
74800+
ret = wc_ecc_check_key(zeroKey);
74801+
myCtx.eccCheckPubExpectZeroPoint = 0;
74802+
74803+
if (ret == 0) {
74804+
ret = WC_TEST_RET_ENC_NC; /* invalid point was accepted */
74805+
}
74806+
else if (!myCtx.eccCheckPubSawZeroPoint) {
74807+
ret = WC_TEST_RET_ENC_NC; /* callback saw NULL/other point */
74808+
}
74809+
else {
74810+
ret = 0; /* expected validation failure after serialization */
74811+
}
74812+
}
74813+
myCtx.eccCheckPubExpectZeroPoint = 0;
74814+
if (haveZeroKey)
74815+
wc_ecc_free(zeroKey);
74816+
WC_FREE_VAR(zeroKey, HEAP_HINT);
74817+
PRIVATE_KEY_LOCK();
74818+
}
74819+
#endif
7468674820
#if !defined(WOLFSSL_ATECC508A) && !defined(WOLFSSL_ATECC608A) && \
7468774821
!defined(WOLFSSL_MICROCHIP_TA100) && !defined(WOLFSSL_STM32_PKA) && \
7468874822
!defined(WOLFSSL_SILABS_SE_ACCEL) && !defined(WOLF_CRYPTO_CB_ONLY_ECC) && \

0 commit comments

Comments
 (0)