Skip to content

Commit 58a858a

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 79805af commit 58a858a

7 files changed

Lines changed: 309 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: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,43 @@ 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+
ecc_key pubOnly;
210+
ret = wc_ecc_init_ex(&pubOnly, key->heap, INVALID_DEVID);
211+
if (ret == 0) {
212+
ret = wc_ecc_import_x963_ex(info->pk.ecc_check_pub.pubKey,
213+
info->pk.ecc_check_pub.pubKeySz, &pubOnly, key->dp->id);
214+
if (ret == 0 &&
215+
wc_ecc_cmp_point(&pubOnly.pubkey, &key->pubkey) != MP_EQ) {
216+
/* wire bytes disagree with key->pubkey */
217+
ret = BAD_STATE_E;
218+
}
219+
if (ret == 0)
220+
ret = wc_ecc_check_key(&pubOnly);
221+
wc_ecc_free(&pubOnly);
222+
}
223+
validatedFromWire = 1;
224+
}
225+
#endif
226+
if (ret == 0 && (!validatedFromWire ||
227+
(info->pk.ecc_check_pub.checkPriv &&
228+
key->type == ECC_PRIVATEKEY))) {
229+
ret = wc_ecc_check_key(key);
230+
}
231+
return ret;
232+
}
233+
#endif /* HAVE_ECC_CHECK_KEY */
197234
#endif /* HAVE_ECC */
198235

199236
#ifndef NO_SHA256
@@ -755,6 +792,10 @@ WC_SWDEV_EXPORT int wc_SwDev_Callback(int devId, wc_CryptoInfo* info,
755792
return swdev_ecc_get_sig_size(info);
756793
case WC_PK_TYPE_EC_MAKE_PUB:
757794
return swdev_ecc_make_pub(info);
795+
#ifdef HAVE_ECC_CHECK_KEY
796+
case WC_PK_TYPE_EC_CHECK_PUB_KEY:
797+
return swdev_ecc_check_pub(info);
798+
#endif
758799
#endif /* HAVE_ECC */
759800
default:
760801
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: 126 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71839,11 +71839,14 @@ typedef struct {
7183971839
int exampleVar; /* flag for testing if only crypt is enabled. */
7184071840
#ifdef HAVE_ECC
7184171841
int eccMakePubCount; /* EC make-pub callback invocations */
71842+
int eccCheckPubCount; /* EC check-pubkey callback invocations */
7184271843
int eccMakePubBadFormat; /* when set, return a malformed (non-uncompressed)
7184371844
* point to exercise the wrapper's BUFFER_E path */
7184471845
int eccMakePubBadLen; /* when set, claim a result size different from the
7184571846
* curve's X9.63 length to exercise the wrapper's
7184671847
* size check */
71848+
int eccCheckPubExpectZeroPoint; /* require serialized 0,0 input */
71849+
int eccCheckPubSawZeroPoint; /* EC check-pubkey saw X9.63 0,0 */
7184771850
ecc_key* eccResidentKey; /* when set, the make-pub callback emits this key's
7184871851
* public point, simulating a private scalar
7184971852
* resident in the device (input key->k empty) */
@@ -72754,6 +72757,77 @@ static int myCryptoDevCb(int devIdArg, wc_CryptoInfo* info, void* ctx)
7275472757
ret = CRYPTOCB_UNAVAILABLE; /* no software emulation available */
7275572758
#endif
7275672759
}
72760+
#ifdef HAVE_ECC_CHECK_KEY
72761+
else if (info->pk.type == WC_PK_TYPE_EC_CHECK_PUB_KEY) {
72762+
ecc_key* k = info->pk.ecc_check_pub.key;
72763+
int validatedFromWire = 0;
72764+
myCtx->eccCheckPubCount++;
72765+
if (info->pk.ecc_check_pub.pubKeySz == 0) {
72766+
/* no host-side public point and this software device holds no
72767+
* resident key, so there is nothing to validate (matches the
72768+
* software answer for a missing public point) */
72769+
ret = ECC_INF_E;
72770+
}
72771+
else {
72772+
ret = 0;
72773+
if (myCtx != NULL && myCtx->eccCheckPubExpectZeroPoint) {
72774+
const byte* pub = info->pk.ecc_check_pub.pubKey;
72775+
word32 curveSz = (word32)k->dp->size;
72776+
word32 ptSz = 1 + 2 * curveSz;
72777+
word32 i;
72778+
72779+
if (info->pk.ecc_check_pub.pubKeySz != ptSz ||
72780+
pub[0] != ECC_POINT_UNCOMP) {
72781+
ret = BAD_STATE_E;
72782+
}
72783+
for (i = 1; ret == 0 && i < ptSz; i++) {
72784+
if (pub[i] != 0)
72785+
ret = BAD_STATE_E;
72786+
}
72787+
if (ret == 0)
72788+
myCtx->eccCheckPubSawZeroPoint = 1;
72789+
}
72790+
#ifdef HAVE_ECC_KEY_IMPORT
72791+
/* vault-style consumption: rebuild the public key from the
72792+
* wire bytes and validate the rebuilt key, proving the
72793+
* serialized point is sufficient and consistent with
72794+
* key->pubkey. The named-curve import does not apply to
72795+
* custom-curve keys (idx == ECC_CUSTOM_IDX). */
72796+
if (k->idx >= 0) {
72797+
ecc_key pubOnly;
72798+
ret = wc_ecc_init_ex(&pubOnly, HEAP_HINT, INVALID_DEVID);
72799+
if (ret == 0) {
72800+
ret = wc_ecc_import_x963_ex(
72801+
info->pk.ecc_check_pub.pubKey,
72802+
info->pk.ecc_check_pub.pubKeySz, &pubOnly,
72803+
k->dp->id);
72804+
if (ret == 0 && wc_ecc_cmp_point(&pubOnly.pubkey,
72805+
&k->pubkey) != MP_EQ) {
72806+
/* wire bytes disagree with key->pubkey */
72807+
ret = BAD_STATE_E;
72808+
}
72809+
if (ret == 0)
72810+
ret = wc_ecc_check_key(&pubOnly);
72811+
wc_ecc_free(&pubOnly);
72812+
}
72813+
validatedFromWire = 1;
72814+
}
72815+
#endif
72816+
/* private/resident part (and custom-curve keys, where the
72817+
* named-curve import above is unavailable): validate via the
72818+
* key handle */
72819+
if (ret == 0 && (!validatedFromWire ||
72820+
(info->pk.ecc_check_pub.checkPriv &&
72821+
k->type == ECC_PRIVATEKEY))) {
72822+
/* set devId to invalid, so software is used */
72823+
k->devId = INVALID_DEVID;
72824+
ret = wc_ecc_check_key(k);
72825+
/* reset devId */
72826+
k->devId = devIdArg;
72827+
}
72828+
}
72829+
}
72830+
#endif
7275772831
#endif /* HAVE_ECC */
7275872832
#ifdef HAVE_CURVE25519
7275972833
if (info->pk.type == WC_PK_TYPE_CURVE25519_KEYGEN) {
@@ -74487,8 +74561,11 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t cryptocb_test(void)
7448774561
myCtx.exampleVar = 1;
7448874562
#ifdef HAVE_ECC
7448974563
myCtx.eccMakePubCount = 0;
74564+
myCtx.eccCheckPubCount = 0;
7449074565
myCtx.eccMakePubBadFormat = 0;
7449174566
myCtx.eccMakePubBadLen = 0;
74567+
myCtx.eccCheckPubExpectZeroPoint = 0;
74568+
myCtx.eccCheckPubSawZeroPoint = 0;
7449274569
myCtx.eccResidentKey = NULL;
7449374570
#endif
7449474571

@@ -74522,8 +74599,55 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t cryptocb_test(void)
7452274599
if (ret == 0)
7452374600
ret = ecc_test();
7452474601
PRIVATE_KEY_LOCK();
74525-
/* Confirm the new ECC make-pub callback was routed through the device and
74526-
* not silently handled in software. */
74602+
/* Confirm the new ECC pubkey callbacks were routed through the device and
74603+
* not silently handled in software. The check-pubkey callback is only
74604+
* exercised when wc_ecc_check_key is built (HAVE_ECC_CHECK_KEY) and the
74605+
* ecc_test calls to it are not skipped (WC_TEST_SKIP_ECC_CHECK_KEY); the
74606+
* counter legitimately stays 0 otherwise. CAAM is excluded because the
74607+
* check-pubkey dispatch in _ecc_validate_public_key is compiled out there
74608+
* (CAAM uses software validation failure to detect black keys). */
74609+
#if !defined(WOLF_CRYPTO_CB_ONLY_ECC) && defined(HAVE_ECC_CHECK_KEY) && \
74610+
!defined(WC_TEST_SKIP_ECC_CHECK_KEY) && !defined(WOLFSSL_CAAM)
74611+
if (ret == 0 && myCtx.eccCheckPubCount == 0)
74612+
ret = WC_TEST_RET_ENC_NC;
74613+
#endif
74614+
/* Regression: an explicit public point with zero coordinates must cross
74615+
* the callback boundary as X9.63 0x04||0||0, not as pubKey = NULL. */
74616+
#if !defined(WOLFSSL_SWDEV) && defined(HAVE_ECC_CHECK_KEY) && \
74617+
!defined(WOLFSSL_CAAM) && !defined(NO_ECC256)
74618+
if (ret == 0) {
74619+
ecc_key zeroKey;
74620+
int haveZeroKey = 0;
74621+
74622+
PRIVATE_KEY_UNLOCK();
74623+
ret = wc_ecc_init_ex(&zeroKey, HEAP_HINT, devId);
74624+
if (ret == 0) {
74625+
haveZeroKey = 1;
74626+
ret = wc_ecc_set_curve(&zeroKey, 32, ECC_SECP256R1);
74627+
}
74628+
if (ret == 0) {
74629+
zeroKey.type = ECC_PUBLICKEY;
74630+
myCtx.eccCheckPubExpectZeroPoint = 1;
74631+
myCtx.eccCheckPubSawZeroPoint = 0;
74632+
ret = wc_ecc_check_key(&zeroKey);
74633+
myCtx.eccCheckPubExpectZeroPoint = 0;
74634+
74635+
if (ret == 0) {
74636+
ret = WC_TEST_RET_ENC_NC; /* invalid point was accepted */
74637+
}
74638+
else if (!myCtx.eccCheckPubSawZeroPoint) {
74639+
ret = WC_TEST_RET_ENC_NC; /* callback saw NULL/other point */
74640+
}
74641+
else {
74642+
ret = 0; /* expected validation failure after serialization */
74643+
}
74644+
}
74645+
myCtx.eccCheckPubExpectZeroPoint = 0;
74646+
if (haveZeroKey)
74647+
wc_ecc_free(&zeroKey);
74648+
PRIVATE_KEY_LOCK();
74649+
}
74650+
#endif
7452774651
#if !defined(WOLFSSL_ATECC508A) && !defined(WOLFSSL_ATECC608A) && \
7452874652
!defined(WOLFSSL_MICROCHIP_TA100) && !defined(WOLFSSL_STM32_PKA) && \
7452974653
!defined(WOLFSSL_SILABS_SE_ACCEL) && !defined(WOLF_CRYPTO_CB_ONLY_ECC) && \

0 commit comments

Comments
 (0)