diff --git a/doc/dox_comments/header_files/cryptocb.h b/doc/dox_comments/header_files/cryptocb.h index e236e1271f..c0cd035eda 100644 --- a/doc/dox_comments/header_files/cryptocb.h +++ b/doc/dox_comments/header_files/cryptocb.h @@ -251,3 +251,63 @@ void wc_CryptoCb_InfoString(wc_CryptoInfo* info); \sa wc_AesInit */ int wc_CryptoCb_AesSetKey(Aes* aes, const byte* key, word32 keySz); + +/*! + \ingroup CryptoCb + + \brief Offload deriving an ECC public key Q = d*G from its private key to a + CryptoCB device. + + Used by wc_ecc_make_pub / wc_ecc_make_pub_ex. The callback boundary is + math-free: the resulting public point crosses as X9.63 uncompressed bytes + (0x04 || X || Y, each ordinate zero-padded to the curve size) in + wc_CryptoInfo.pk.ecc_make_pub (\c pubOut / \c pubOutSz). This wrapper performs + all bignum (de)serialization, so a device handler only deals with byte arrays + and never with wolfCrypt's internal mp_int representation. The private scalar + is taken from the ecc_key (resident in a secure element, or key->k); curve + identity comes from key->dp. + + \param key ECC key providing the device id, curve identity, heap hint and + the private scalar + \param pubOut [out] resulting affine public point Q = d*G + + \return 0 on success + \return CRYPTOCB_UNAVAILABLE if no device handles the operation, or key or + pubOut is NULL (wolfCrypt falls back to software, which reports + the argument error) + + \sa wc_CryptoCb_RegisterDevice + \sa wc_ecc_make_pub +*/ +int wc_CryptoCb_EccMakePub(ecc_key* key, ecc_point* pubOut); + +/*! + \ingroup CryptoCb + + \brief Offload validating an ECC key to a CryptoCB device. + + Used by wc_ecc_check_key and the key generation / import validation paths. + The public point crosses the (math-free) callback boundary as X9.63 + uncompressed bytes in wc_CryptoInfo.pk.ecc_check_pub (\c pubKey / + \c pubKeySz); this wrapper serializes key->pubkey so a device handler only + deals with byte arrays. When the key state is \c ECC_PRIVATEKEY_ONLY, + \c pubKey is NULL and \c pubKeySz is 0 so the handler can distinguish "no + public point" from an invalid zero-coordinate public point that must be + validated and rejected. The caller's intent crosses in \c checkOrder and + \c checkPriv. + + \param key ECC key to validate (curve identity from key->dp) + \param checkOrder when 1 the caller requested validation that the point + has the curve order (point * order == infinity) + \param checkPriv when 1 the caller also requested validation of the + private part (scalar range, consistency with the public + point) + + \return 0 if the key is valid + \return CRYPTOCB_UNAVAILABLE if no device handles the operation (wolfCrypt + falls back to software) + + \sa wc_CryptoCb_RegisterDevice + \sa wc_ecc_check_key +*/ +int wc_CryptoCb_EccCheckPubKey(ecc_key* key, int checkOrder, int checkPriv); diff --git a/tests/swdev/swdev.c b/tests/swdev/swdev.c index 24271eda1e..92ae08cec1 100644 --- a/tests/swdev/swdev.c +++ b/tests/swdev/swdev.c @@ -153,6 +153,89 @@ static int swdev_ecc_get_sig_size(wc_CryptoInfo* info) *info->pk.ecc_get_sig_size.sigSize = sz; return 0; } + +static int swdev_ecc_make_pub(wc_CryptoInfo* info) +{ + int ret; + ecc_key* key = info->pk.ecc_make_pub.key; + ecc_point* pub = wc_ecc_new_point_h(key->heap); + + if (pub == NULL) + return MEMORY_E; + + /* derive Q = d*G in software, then emit X9.63 uncompressed bytes. The + * point is serialized with the curve size from key->dp so custom-curve + * keys (idx == ECC_CUSTOM_IDX) work too; wc_ecc_export_point_der rejects + * negative curve indices. */ + ret = wc_ecc_make_pub(key, pub); + if (ret == 0) { + byte* out = info->pk.ecc_make_pub.pubOut; + word32 curveSz = (word32)key->dp->size; + word32 ptSz = 1 + 2 * curveSz; + word32 xSz = curveSz; + word32 ySz = curveSz; + + if (*info->pk.ecc_make_pub.pubOutSz < ptSz) { + ret = BUFFER_E; + } + else { + out[0] = ECC_POINT_UNCOMP; + ret = wc_export_int(pub->x, out + 1, &xSz, curveSz, + WC_TYPE_UNSIGNED_BIN); + if (ret == MP_OKAY) + ret = wc_export_int(pub->y, out + 1 + curveSz, &ySz, curveSz, + WC_TYPE_UNSIGNED_BIN); + if (ret == MP_OKAY) + *info->pk.ecc_make_pub.pubOutSz = ptSz; + } + } + + wc_ecc_del_point_h(pub, key->heap); + return ret; +} + +#ifdef HAVE_ECC_CHECK_KEY +static int swdev_ecc_check_pub(wc_CryptoInfo* info) +{ + ecc_key* key = info->pk.ecc_check_pub.key; + int ret = 0; + int validatedFromWire = 0; + + if (info->pk.ecc_check_pub.pubKeySz == 0) { + return ECC_INF_E; + } +#ifdef HAVE_ECC_KEY_IMPORT + if (key->idx >= 0) { + WC_DECLARE_VAR(pubOnly, ecc_key, 1, key->heap); + WC_ALLOC_VAR(pubOnly, ecc_key, 1, key->heap); + if (!WC_VAR_OK(pubOnly)) + ret = MEMORY_E; + else + ret = wc_ecc_init_ex(pubOnly, key->heap, INVALID_DEVID); + if (ret == 0) { + ret = wc_ecc_import_x963_ex(info->pk.ecc_check_pub.pubKey, + info->pk.ecc_check_pub.pubKeySz, pubOnly, key->dp->id); + if (ret == 0 && + wc_ecc_cmp_point(&pubOnly->pubkey, &key->pubkey) != MP_EQ) { + /* wire bytes disagree with key->pubkey */ + ret = BAD_STATE_E; + } + if (ret == 0) + ret = wc_ecc_check_key(pubOnly); + wc_ecc_free(pubOnly); + } + WC_FREE_VAR(pubOnly, key->heap); + validatedFromWire = 1; + } +#endif + if (ret == 0 && (!validatedFromWire || + (info->pk.ecc_check_pub.checkPriv && + key->type == ECC_PRIVATEKEY))) { + ret = wc_ecc_check_key(key); + } + return ret; +} +#endif /* HAVE_ECC_CHECK_KEY */ #endif /* HAVE_ECC */ #ifndef NO_SHA256 @@ -712,6 +795,12 @@ WC_SWDEV_EXPORT int wc_SwDev_Callback(int devId, wc_CryptoInfo* info, return swdev_ecc_get_size(info); case WC_PK_TYPE_EC_GET_SIG_SIZE: return swdev_ecc_get_sig_size(info); + case WC_PK_TYPE_EC_MAKE_PUB: + return swdev_ecc_make_pub(info); + #ifdef HAVE_ECC_CHECK_KEY + case WC_PK_TYPE_EC_CHECK_PUB_KEY: + return swdev_ecc_check_pub(info); + #endif #endif /* HAVE_ECC */ default: return CRYPTOCB_UNAVAILABLE; diff --git a/wolfcrypt/src/cryptocb.c b/wolfcrypt/src/cryptocb.c index ff03a9f948..9a27fab482 100644 --- a/wolfcrypt/src/cryptocb.c +++ b/wolfcrypt/src/cryptocb.c @@ -157,6 +157,8 @@ static const char* GetPkTypeStr(int pk) case WC_PK_TYPE_EC_KEYGEN: return "ECC KeyGen"; case WC_PK_TYPE_EC_GET_SIZE: return "ECC GetSize"; case WC_PK_TYPE_EC_GET_SIG_SIZE: return "ECC GetSigSize"; + case WC_PK_TYPE_EC_MAKE_PUB: return "ECC MakePub"; + case WC_PK_TYPE_EC_CHECK_PUB_KEY: return "ECC CheckPubKey"; } return NULL; } @@ -882,6 +884,127 @@ int wc_CryptoCb_EccGetSigSize(const ecc_key* key, int* sigSize) return wc_CryptoCb_TranslateErrorCode(ret); } + +int wc_CryptoCb_EccMakePub(ecc_key* key, ecc_point* pubOut) +{ + int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + CryptoCb* dev; + + if (key == NULL || pubOut == NULL || key->dp == NULL) + return ret; + + if (key->dp->size > MAX_ECC_BYTES) + return ret; + + dev = wc_CryptoCb_FindDevice(key->devId, WC_ALGO_TYPE_PK); + if (dev && dev->cb) { + wc_CryptoInfo cryptoInfo; + word32 curveSz = (word32)key->dp->size; + word32 ptSz = 1 + 2 * curveSz; /* X9.63 uncompressed length */ + word32 outSz = 1 + 2 * MAX_ECC_BYTES; /* buffer size on input */ + WC_DECLARE_VAR(buf, byte, (1 + 2 * MAX_ECC_BYTES), key->heap); + WC_ALLOC_VAR_EX(buf, byte, (1 + 2 * MAX_ECC_BYTES), key->heap, + DYNAMIC_TYPE_ECC_BUFFER, return MEMORY_E); + + /* zero the result buffer so a handler that returns success without + * writing output is rejected deterministically by the tag check */ + XMEMSET(buf, 0, ptSz); + + XMEMSET(&cryptoInfo, 0, sizeof(cryptoInfo)); + cryptoInfo.algo_type = WC_ALGO_TYPE_PK; + cryptoInfo.pk.type = WC_PK_TYPE_EC_MAKE_PUB; + cryptoInfo.pk.ecc_make_pub.key = key; + cryptoInfo.pk.ecc_make_pub.pubOut = buf; + cryptoInfo.pk.ecc_make_pub.pubOutSz = &outSz; + + ret = dev->cb(dev->devId, &cryptoInfo, dev->ctx); + + /* deserialize X9.63 uncompressed result into pubOut; reject a result + * whose size is not exactly the curve's X9.63 length */ + if (ret == 0) { + if (outSz != ptSz || buf[0] != ECC_POINT_UNCOMP) { + ret = BUFFER_E; + } + else { + int err = mp_read_unsigned_bin(pubOut->x, buf + 1, curveSz); + if (err == MP_OKAY) + err = mp_read_unsigned_bin(pubOut->y, buf + 1 + curveSz, + curveSz); + if (err == MP_OKAY) + err = mp_set(pubOut->z, 1); + if (err != MP_OKAY) + ret = err; + } + } + + WC_FREE_VAR_EX(buf, key->heap, DYNAMIC_TYPE_ECC_BUFFER); + } + + return wc_CryptoCb_TranslateErrorCode(ret); +} + +#ifdef HAVE_ECC_CHECK_KEY +int wc_CryptoCb_EccCheckPubKey(ecc_key* key, int checkOrder, int checkPriv) +{ + int ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + CryptoCb* dev; + + if (key == NULL || key->dp == NULL) + return ret; + + if (key->dp->size > MAX_ECC_BYTES) + return ret; + + /* locate registered callback */ + dev = wc_CryptoCb_FindDevice(key->devId, WC_ALGO_TYPE_PK); + if (dev && dev->cb) { + wc_CryptoInfo cryptoInfo; + word32 curveSz = (word32)key->dp->size; + word32 ptSz = 1 + 2 * curveSz; + word32 xSz = curveSz; + word32 ySz = curveSz; + /* a key with no host-side public point is represented by + * ECC_PRIVATEKEY_ONLY and crosses as pubKey = NULL / pubKeySz = 0. + * If the key state says a public point exists, serialize it even when + * the coordinates are invalid (e.g. 0,0) so the device can reject the + * actual input instead of seeing "no public point". */ + int havePub = (key->type != ECC_PRIVATEKEY_ONLY); + WC_DECLARE_VAR(buf, byte, (1 + 2 * MAX_ECC_BYTES), key->heap); + + ret = MP_OKAY; + if (havePub) { + WC_ALLOC_VAR_EX(buf, byte, (1 + 2 * MAX_ECC_BYTES), key->heap, + DYNAMIC_TYPE_ECC_BUFFER, return MEMORY_E); + /* serialize key->pubkey to X9.63 uncompressed (0x04 || X || Y) */ + buf[0] = ECC_POINT_UNCOMP; + ret = wc_export_int(key->pubkey.x, buf + 1, &xSz, curveSz, + WC_TYPE_UNSIGNED_BIN); + if (ret == MP_OKAY) + ret = wc_export_int(key->pubkey.y, buf + 1 + curveSz, &ySz, + curveSz, WC_TYPE_UNSIGNED_BIN); + } + + if (ret == MP_OKAY) { + XMEMSET(&cryptoInfo, 0, sizeof(cryptoInfo)); + cryptoInfo.algo_type = WC_ALGO_TYPE_PK; + cryptoInfo.pk.type = WC_PK_TYPE_EC_CHECK_PUB_KEY; + cryptoInfo.pk.ecc_check_pub.key = key; + cryptoInfo.pk.ecc_check_pub.pubKey = havePub ? buf : NULL; + cryptoInfo.pk.ecc_check_pub.pubKeySz = havePub ? ptSz : 0; + cryptoInfo.pk.ecc_check_pub.checkOrder = checkOrder; + cryptoInfo.pk.ecc_check_pub.checkPriv = checkPriv; + + ret = dev->cb(dev->devId, &cryptoInfo, dev->ctx); + } + + if (havePub) { + WC_FREE_VAR_EX(buf, key->heap, DYNAMIC_TYPE_ECC_BUFFER); + } + } + + return wc_CryptoCb_TranslateErrorCode(ret); +} +#endif /* HAVE_ECC_CHECK_KEY */ #endif /* HAVE_ECC */ #ifdef HAVE_CURVE25519 diff --git a/wolfcrypt/src/ecc.c b/wolfcrypt/src/ecc.c index a3e708e989..a0ea4cc49a 100644 --- a/wolfcrypt/src/ecc.c +++ b/wolfcrypt/src/ecc.c @@ -293,8 +293,7 @@ ECC Curve Sizes: #if !defined(WOLFSSL_ATECC508A) && !defined(WOLFSSL_ATECC608A) && \ !defined(WOLFSSL_MICROCHIP_TA100) && \ !defined(WOLFSSL_CRYPTOCELL) && !defined(WOLFSSL_SILABS_SE_ACCEL) && \ - !defined(WOLFSSL_KCAPI_ECC) && !defined(NO_ECC_MAKE_PUB) && \ - !defined(WOLF_CRYPTO_CB_ONLY_ECC) + !defined(WOLFSSL_KCAPI_ECC) && !defined(NO_ECC_MAKE_PUB) #undef HAVE_ECC_MAKE_PUB #define HAVE_ECC_MAKE_PUB #endif @@ -5435,66 +5434,30 @@ static WC_INLINE void wc_ecc_reset(ecc_key* key) } -/* create the public ECC key from a private key +#if defined(HAVE_ECC_MAKE_PUB) && !defined(WOLF_CRYPTO_CB_ONLY_ECC) +/* compute the public key Q = d*G in software. * - * key an initialized private key to generate public part from - * curve [in]curve for key, cannot be NULL - * pubOut [out]ecc_point holding the public key, if NULL then public key part - * is cached in key instead. - * - * Note this function is local to the file because of the argument type - * ecc_curve_spec. Having this argument allows for not having to load the - * curve type multiple times when generating a key with wc_ecc_make_key(). - * For async the results are placed directly into pubOut, so this function - * does not need to be called again + * key private key holding the scalar d, must be present and in range + * curve [in]curve for key, cannot be NULL + * pub [out]initialized ecc_point receiving the public key + * rng optional RNG for a timing-resistant point multiply, may be NULL * * returns MP_OKAY on success */ -static int ecc_make_pub_ex(ecc_key* key, ecc_curve_spec* curve, - ecc_point* pubOut, WC_RNG* rng) +static int ecc_make_pub_sw(ecc_key* key, ecc_curve_spec* curve, + ecc_point* pub, WC_RNG* rng) { int err = MP_OKAY; -#ifdef HAVE_ECC_MAKE_PUB - ecc_point* pub; -#endif /* HAVE_ECC_MAKE_PUB */ (void)rng; - if (key == NULL) { - return BAD_FUNC_ARG; - } - -#ifdef HAVE_ECC_MAKE_PUB - /* if ecc_point passed in then use it as output for public key point */ - if (pubOut != NULL) { - pub = pubOut; - } - else { - /* caching public key making it a ECC_PRIVATEKEY instead of - ECC_PRIVATEKEY_ONLY */ - pub = &key->pubkey; - key->type = ECC_PRIVATEKEY_ONLY; - } - - if ((err == MP_OKAY) && (mp_iszero(ecc_get_k(key)) || - mp_isneg(ecc_get_k(key)) || - (mp_cmp(ecc_get_k(key), curve->order) != MP_LT))) { + /* The private scalar must be in range for the base-point multiply + * below. */ + if (mp_iszero(ecc_get_k(key)) || mp_isneg(ecc_get_k(key)) || + (mp_cmp(ecc_get_k(key), curve->order) != MP_LT)) { err = ECC_PRIV_KEY_E; } - if (err == MP_OKAY) { - #ifndef ALT_ECC_SIZE - err = mp_init_multi(pub->x, pub->y, pub->z, NULL, NULL, NULL); - #else - pub->x = (mp_int*)&pub->xyz[0]; - pub->y = (mp_int*)&pub->xyz[1]; - pub->z = (mp_int*)&pub->xyz[2]; - alt_fp_init(pub->x); - alt_fp_init(pub->y); - alt_fp_init(pub->z); - #endif - } - #if defined(WOLFSSL_ASYNC_CRYPT) && defined(WC_ASYNC_ENABLE_ECC_KEYGEN) && \ defined(HAVE_INTEL_QA) if (err == MP_OKAY && key->asyncDev.marker == WOLFSSL_ASYNC_MARKER_ECC) { @@ -5585,6 +5548,106 @@ static int ecc_make_pub_ex(ecc_key* key, ecc_curve_spec* curve, #endif /* WOLFSSL_SP_MATH */ } /* END: Software Crypto */ + return err; +} +#endif /* HAVE_ECC_MAKE_PUB && !WOLF_CRYPTO_CB_ONLY_ECC */ + +/* create the public ECC key from a private key + * + * key an initialized private key to generate public part from + * curve [in]curve for key, cannot be NULL + * pubOut [out]ecc_point holding the public key, if NULL then public key part + * is cached in key instead. + * + * Note this function is local to the file because of the argument type + * ecc_curve_spec. Having this argument allows for not having to load the + * curve type multiple times when generating a key with wc_ecc_make_key(). + * For async the results are placed directly into pubOut, so this function + * does not need to be called again + * + * returns MP_OKAY on success + */ +static int ecc_make_pub_ex(ecc_key* key, ecc_curve_spec* curve, + ecc_point* pubOut, WC_RNG* rng) +{ + int err = MP_OKAY; +#ifdef HAVE_ECC_MAKE_PUB + ecc_point* pub; + int doneInCb = 0; +#endif /* HAVE_ECC_MAKE_PUB */ + + (void)rng; + + if (key == NULL) { + return BAD_FUNC_ARG; + } + +#ifdef HAVE_ECC_MAKE_PUB + /* if ecc_point passed in then use it as output for public key point */ + if (pubOut != NULL) { + pub = pubOut; + } + else { + /* caching public key making it a ECC_PRIVATEKEY instead of + ECC_PRIVATEKEY_ONLY */ + pub = &key->pubkey; + key->type = ECC_PRIVATEKEY_ONLY; + } + + if (err == MP_OKAY) { + #ifndef ALT_ECC_SIZE + err = mp_init_multi(pub->x, pub->y, pub->z, NULL, NULL, NULL); + #else + pub->x = (mp_int*)&pub->xyz[0]; + pub->y = (mp_int*)&pub->xyz[1]; + pub->z = (mp_int*)&pub->xyz[2]; + alt_fp_init(pub->x); + alt_fp_init(pub->y); + alt_fp_init(pub->z); + #endif + } + +#ifdef WOLF_CRYPTO_CB + /* offload Q = d*G to the device; fall through to software only when the + * device reports the operation unavailable. + * + * Under WOLF_CRYPTO_CB_FIND the devId gate is dropped so a find callback + * can route a key initialized with INVALID_DEVID. This helper is also + * shared by internal callers that compute a point from a transient scalar + * in key->k - the ECDSA software-sign nonce R = k*G (ecc_sign_hash_sw), + * keygen public-part derivation, and verify-time public-key recovery. + * Reaching the software signer means the device already declined to sign + * (the whole-sign offload wc_CryptoCb_EccSign ran first), so a find + * callback that hands this make-pub to a device notwithstanding the + * INVALID_DEVID is expected to likewise decline it. If it does not decline + * *and* the device ignores key->k (e.g. relying on an internal key + * representation), it returns d*G instead of the requested k*G and the + * resulting signature is wrong - but such a scenario is very unlikely. */ + #ifndef WOLF_CRYPTO_CB_FIND + if ((err == MP_OKAY) && (key->devId != INVALID_DEVID)) + #else + if (err == MP_OKAY) + #endif + { + err = wc_CryptoCb_EccMakePub(key, pub); + if (err != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) + doneInCb = 1; + else + err = MP_OKAY; /* device declined the offload; fall back */ + } +#endif + + if ((err == MP_OKAY) && !doneInCb) { +#ifdef WOLF_CRYPTO_CB_ONLY_ECC + /* software derivation is stripped and no device handled the op; + * fail closed */ + (void)curve; + err = NO_VALID_DEVID; +#else + err = ecc_make_pub_sw(key, curve, pub, rng); +#endif + } + if (err != MP_OKAY #ifdef WOLFSSL_ASYNC_CRYPT && err != WC_NO_ERR_TRACE(WC_PENDING_E) @@ -10731,13 +10794,38 @@ static int _ecc_validate_public_key(ecc_key* key, int partial, int priv) if (key == NULL) return BAD_FUNC_ARG; +#if defined(WOLF_CRYPTO_CB) && defined(HAVE_ECC_CHECK_KEY) && \ + !defined(WOLFSSL_CAAM) + /* Device-first: when a device is configured, let it validate the public + * key. Fall through to the software/HW path below only when the + * device reports the operation unavailable. + * CAAM is excluded: it relies on the software order-check below to detect + * whether an imported private key is an encrypted black key. */ + #ifndef WOLF_CRYPTO_CB_FIND + if (key->devId != INVALID_DEVID) + #endif + { + err = wc_CryptoCb_EccCheckPubKey(key, !partial, priv); + if (err != WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) + return err; + /* device declined; fall through. Every software/HW path below + * re-initializes err before reading it, so no reset is needed here. */ + } +#endif + #ifndef HAVE_ECC_CHECK_PUBKEY_ORDER +#ifdef WOLF_CRYPTO_CB_ONLY_ECC + /* Software validation is stripped; the device-first check above either + * handled the key or reported the op unavailable, so fail closed rather + * than accept an unvalidated key. */ + err = NO_VALID_DEVID; +#else /* consider key check success on HW crypto - * ex: ATECC508/608A, CryptoCell and Silabs - * - * consider key check success on most Crypt Cb only builds + * ex: ATECC508/608A, CryptoCell and Silabs which validate the key + * internally */ err = MP_OKAY; +#endif #else @@ -10879,6 +10967,10 @@ WOLFSSL_ABI int wc_ecc_check_key(ecc_key* key) { int ret; + /* _ecc_validate_public_key is the single validation entry point: it is + * device-first (offloads to the crypto callback when a device is + * configured) and otherwise runs the software/HW checks, or fails closed + * under WOLF_CRYPTO_CB_ONLY_ECC. */ ret = _ecc_validate_public_key(key, 0, 1); return ret; } diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index d54a7994ea..3246e00e78 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -42783,7 +42783,8 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t ecc_test(void) #if !defined(WOLFSSL_ATECC508A) && !defined(WOLFSSL_ATECC608A) && \ !defined(WOLFSSL_MICROCHIP_TA100) && \ !defined(WOLFSSL_STM32_PKA) && !defined(WOLFSSL_SILABS_SE_ACCEL) && \ - !defined(WOLF_CRYPTO_CB_ONLY_ECC) && !defined(NO_ECC_SECP) + (!defined(WOLF_CRYPTO_CB_ONLY_ECC) || defined(WOLFSSL_SWDEV)) && \ + !defined(NO_ECC_SECP) ret = ecc_test_make_pub(&rng); if (ret != 0) { printf("ecc_test_make_pub failed!\n"); @@ -72303,6 +72304,20 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t blob_test(void) /* Example custom context for crypto callback */ typedef struct { int exampleVar; /* flag for testing if only crypt is enabled. */ +#ifdef HAVE_ECC + int eccMakePubCount; /* EC make-pub callback invocations */ + int eccCheckPubCount; /* EC check-pubkey callback invocations */ + int eccMakePubBadFormat; /* when set, return a malformed (non-uncompressed) + * point to exercise the wrapper's BUFFER_E path */ + int eccMakePubBadLen; /* when set, claim a result size different from the + * curve's X9.63 length to exercise the wrapper's + * size check */ + int eccCheckPubExpectZeroPoint; /* require serialized 0,0 input */ + int eccCheckPubSawZeroPoint; /* EC check-pubkey saw X9.63 0,0 */ + ecc_key* eccResidentKey; /* when set, the make-pub callback emits this key's + * public point, simulating a private scalar + * resident in the device (input key->k empty) */ +#endif } myCryptoDevCtx; #ifdef WOLF_CRYPTO_CB_ONLY_RSA @@ -72895,6 +72910,33 @@ exit_onlycb: } #endif /* WOLF_CRYPTO_CB_ONLY_AES */ +#if defined(HAVE_ECC) && !defined(WOLFSSL_NO_MALLOC) && \ + defined(HAVE_ECC_KEY_EXPORT) +/* Serialize pub to X9.63 uncompressed (0x04 || X || Y) using the curve size + * from dp, so custom-curve keys (idx == ECC_CUSTOM_IDX) work too; + * wc_ecc_export_point_der rejects negative curve indices. */ +static int myCryptoCbExportPointX963(const ecc_set_type* dp, ecc_point* pub, + byte* out, word32* outSz) +{ + int ret; + word32 curveSz = (word32)dp->size; + word32 ptSz = 1 + 2 * curveSz; + word32 xSz = curveSz; + word32 ySz = curveSz; + + if (*outSz < ptSz) + return BUFFER_E; + out[0] = ECC_POINT_UNCOMP; + ret = wc_export_int(pub->x, out + 1, &xSz, curveSz, WC_TYPE_UNSIGNED_BIN); + if (ret == MP_OKAY) + ret = wc_export_int(pub->y, out + 1 + curveSz, &ySz, curveSz, + WC_TYPE_UNSIGNED_BIN); + if (ret == MP_OKAY) + *outSz = ptSz; + return ret; +} +#endif /* HAVE_ECC && !WOLFSSL_NO_MALLOC && HAVE_ECC_KEY_EXPORT */ + /* Example crypto dev callback function that calls software version */ static int myCryptoDevCb(int devIdArg, wc_CryptoInfo* info, void* ctx) { @@ -73132,6 +73174,132 @@ static int myCryptoDevCb(int devIdArg, wc_CryptoInfo* info, void* ctx) ret = 0; } } + else if (info->pk.type == WC_PK_TYPE_EC_MAKE_PUB) { + #if !defined(WOLFSSL_NO_MALLOC) && defined(HAVE_ECC_KEY_EXPORT) + ecc_key* k = info->pk.ecc_make_pub.key; + ecc_point* pub; + if (myCtx->eccResidentKey != NULL) { + /* Secure-element case: the private scalar lives in the device, + * so the input key's k is empty. Emit the resident key's + * public point directly. Reaching this code at all proves the + * dispatch ran before ecc_make_pub_ex's software + * private-scalar range check. */ + ecc_key* src = myCtx->eccResidentKey; + ret = myCryptoCbExportPointX963(src->dp, &src->pubkey, + info->pk.ecc_make_pub.pubOut, + info->pk.ecc_make_pub.pubOutSz); + myCtx->eccMakePubCount++; + } + else { + /* set devId to invalid, so software is used */ + k->devId = INVALID_DEVID; + pub = wc_ecc_new_point_h(HEAP_HINT); + if (pub == NULL) { + ret = MEMORY_E; + } + else { + /* derive Q = d*G then emit X9.63 uncompressed bytes */ + ret = wc_ecc_make_pub(k, pub); + if (ret == 0) + ret = myCryptoCbExportPointX963(k->dp, pub, + info->pk.ecc_make_pub.pubOut, + info->pk.ecc_make_pub.pubOutSz); + /* negative test: corrupt the X9.63 tag so the wrapper + * rejects the result with BUFFER_E instead of accepting + * it */ + if (ret == 0 && myCtx->eccMakePubBadFormat) + info->pk.ecc_make_pub.pubOut[0] = ECC_POINT_COMP_EVEN; + /* negative test: claim a result size different from the + * curve's X9.63 length so the wrapper rejects the result + * with BUFFER_E instead of accepting it */ + if (ret == 0 && myCtx->eccMakePubBadLen) + (*info->pk.ecc_make_pub.pubOutSz)++; + wc_ecc_del_point_h(pub, HEAP_HINT); + } + myCtx->eccMakePubCount++; + /* reset devId */ + k->devId = devIdArg; + } + #else + ret = CRYPTOCB_UNAVAILABLE; /* no software emulation available */ + #endif + } + #ifdef HAVE_ECC_CHECK_KEY + else if (info->pk.type == WC_PK_TYPE_EC_CHECK_PUB_KEY) { + ecc_key* k = info->pk.ecc_check_pub.key; + int validatedFromWire = 0; + myCtx->eccCheckPubCount++; + if (info->pk.ecc_check_pub.pubKeySz == 0) { + /* no host-side public point and this software device holds no + * resident key, so there is nothing to validate (matches the + * software answer for a missing public point) */ + ret = ECC_INF_E; + } + else { + ret = 0; + if (myCtx != NULL && myCtx->eccCheckPubExpectZeroPoint) { + const byte* pub = info->pk.ecc_check_pub.pubKey; + word32 curveSz = (word32)k->dp->size; + word32 ptSz = 1 + 2 * curveSz; + word32 i; + + if (info->pk.ecc_check_pub.pubKeySz != ptSz || + pub[0] != ECC_POINT_UNCOMP) { + ret = BAD_STATE_E; + } + for (i = 1; ret == 0 && i < ptSz; i++) { + if (pub[i] != 0) + ret = BAD_STATE_E; + } + if (ret == 0) + myCtx->eccCheckPubSawZeroPoint = 1; + } + #ifdef HAVE_ECC_KEY_IMPORT + /* vault-style consumption: rebuild the public key from the + * wire bytes and validate the rebuilt key, proving the + * serialized point is sufficient and consistent with + * key->pubkey. The named-curve import does not apply to + * custom-curve keys (idx == ECC_CUSTOM_IDX). */ + if (k->idx >= 0) { + WC_DECLARE_VAR(pubOnly, ecc_key, 1, HEAP_HINT); + WC_ALLOC_VAR(pubOnly, ecc_key, 1, HEAP_HINT); + if (!WC_VAR_OK(pubOnly)) + ret = MEMORY_E; + else + ret = wc_ecc_init_ex(pubOnly, HEAP_HINT, INVALID_DEVID); + if (ret == 0) { + ret = wc_ecc_import_x963_ex( + info->pk.ecc_check_pub.pubKey, + info->pk.ecc_check_pub.pubKeySz, pubOnly, + k->dp->id); + if (ret == 0 && wc_ecc_cmp_point(&pubOnly->pubkey, + &k->pubkey) != MP_EQ) { + /* wire bytes disagree with key->pubkey */ + ret = BAD_STATE_E; + } + if (ret == 0) + ret = wc_ecc_check_key(pubOnly); + wc_ecc_free(pubOnly); + } + WC_FREE_VAR(pubOnly, HEAP_HINT); + validatedFromWire = 1; + } + #endif + /* private/resident part (and custom-curve keys, where the + * named-curve import above is unavailable): validate via the + * key handle */ + if (ret == 0 && (!validatedFromWire || + (info->pk.ecc_check_pub.checkPriv && + k->type == ECC_PRIVATEKEY))) { + /* set devId to invalid, so software is used */ + k->devId = INVALID_DEVID; + ret = wc_ecc_check_key(k); + /* reset devId */ + k->devId = devIdArg; + } + } + } + #endif #endif /* HAVE_ECC */ #ifdef HAVE_CURVE25519 if (info->pk.type == WC_PK_TYPE_CURVE25519_KEYGEN) { @@ -74943,6 +75111,15 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t cryptocb_test(void) /* example data for callback */ myCtx.exampleVar = 1; +#ifdef HAVE_ECC + myCtx.eccMakePubCount = 0; + myCtx.eccCheckPubCount = 0; + myCtx.eccMakePubBadFormat = 0; + myCtx.eccMakePubBadLen = 0; + myCtx.eccCheckPubExpectZeroPoint = 0; + myCtx.eccCheckPubSawZeroPoint = 0; + myCtx.eccResidentKey = NULL; +#endif /* set devId to something other than INVALID_DEVID */ devId = 1; @@ -74974,6 +75151,206 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t cryptocb_test(void) if (ret == 0) ret = ecc_test(); PRIVATE_KEY_LOCK(); + /* Confirm the new ECC pubkey callbacks were routed through the device and + * not silently handled in software. The check-pubkey callback is only + * exercised when wc_ecc_check_key is built (HAVE_ECC_CHECK_KEY) and the + * ecc_test calls to it are not skipped (WC_TEST_SKIP_ECC_CHECK_KEY); the + * counter legitimately stays 0 otherwise. CAAM is excluded because the + * check-pubkey dispatch in _ecc_validate_public_key is compiled out there + * (CAAM uses software validation failure to detect black keys). */ +#if !defined(WOLF_CRYPTO_CB_ONLY_ECC) && defined(HAVE_ECC_CHECK_KEY) && \ + !defined(WC_TEST_SKIP_ECC_CHECK_KEY) && !defined(WOLFSSL_CAAM) + if (ret == 0 && myCtx.eccCheckPubCount == 0) + ret = WC_TEST_RET_ENC_NC; +#endif + /* Regression: an explicit public point with zero coordinates must cross + * the callback boundary as X9.63 0x04||0||0, not as pubKey = NULL. */ +#if !defined(WOLFSSL_SWDEV) && defined(HAVE_ECC_CHECK_KEY) && \ + !defined(WOLFSSL_CAAM) && !defined(NO_ECC256) + if (ret == 0) { + WC_DECLARE_VAR(zeroKey, ecc_key, 1, HEAP_HINT); + int haveZeroKey = 0; + + WC_ALLOC_VAR(zeroKey, ecc_key, 1, HEAP_HINT); + PRIVATE_KEY_UNLOCK(); + if (!WC_VAR_OK(zeroKey)) + ret = MEMORY_E; + else + ret = wc_ecc_init_ex(zeroKey, HEAP_HINT, devId); + if (ret == 0) { + haveZeroKey = 1; + ret = wc_ecc_set_curve(zeroKey, 32, ECC_SECP256R1); + } + if (ret == 0) { + zeroKey->type = ECC_PUBLICKEY; + myCtx.eccCheckPubExpectZeroPoint = 1; + myCtx.eccCheckPubSawZeroPoint = 0; + ret = wc_ecc_check_key(zeroKey); + myCtx.eccCheckPubExpectZeroPoint = 0; + + if (ret == 0) { + ret = WC_TEST_RET_ENC_NC; /* invalid point was accepted */ + } + else if (!myCtx.eccCheckPubSawZeroPoint) { + ret = WC_TEST_RET_ENC_NC; /* callback saw NULL/other point */ + } + else { + ret = 0; /* expected validation failure after serialization */ + } + } + myCtx.eccCheckPubExpectZeroPoint = 0; + if (haveZeroKey) + wc_ecc_free(zeroKey); + WC_FREE_VAR(zeroKey, HEAP_HINT); + PRIVATE_KEY_LOCK(); + } +#endif +#if !defined(WOLFSSL_ATECC508A) && !defined(WOLFSSL_ATECC608A) && \ + !defined(WOLFSSL_MICROCHIP_TA100) && !defined(WOLFSSL_STM32_PKA) && \ + !defined(WOLFSSL_SILABS_SE_ACCEL) && !defined(WOLF_CRYPTO_CB_ONLY_ECC) && \ + !defined(NO_ECC_SECP) && !defined(WOLFSSL_NO_MALLOC) && \ + !defined(WOLFSSL_CRYPTOCELL) && !defined(NO_ECC256) && \ + defined(HAVE_ECC_KEY_EXPORT) + if (ret == 0 && myCtx.eccMakePubCount == 0) + ret = WC_TEST_RET_ENC_NC; +#endif + /* Exercise the make-pub wrapper's device-result validation and the + * resident-key dispatch path. Only meaningful where myCryptoDevCb (not + * swdev) services the make-pub callback. + * + * WOLFSSL_SE050 is excluded: its wc_ecc_make_key generates the key in the + * element, so srcKey has no host-side scalar (key->k) for the callback's + * software Q = d*G derivation to use. */ +#if !defined(WOLFSSL_ATECC508A) && !defined(WOLFSSL_ATECC608A) && \ + !defined(WOLFSSL_MICROCHIP_TA100) && !defined(WOLFSSL_STM32_PKA) && \ + !defined(WOLFSSL_SILABS_SE_ACCEL) && !defined(WOLF_CRYPTO_CB_ONLY_ECC) && \ + !defined(WOLFSSL_SWDEV) && !defined(NO_ECC_SECP) && \ + !defined(WOLFSSL_NO_MALLOC) && !defined(WOLFSSL_CRYPTOCELL) && \ + !defined(WOLFSSL_SE050) && \ + !defined(NO_ECC256) && defined(HAVE_ECC_KEY_EXPORT) && !defined(WC_NO_RNG) + if (ret == 0) { + /* generated keypair: private scalar for the negative tests, public + * point for the resident-key test */ + WC_DECLARE_VAR(srcKey, ecc_key, 1, HEAP_HINT); + /* device-backed key with an empty private scalar */ + WC_DECLARE_VAR(resKey, ecc_key, 1, HEAP_HINT); + WC_DECLARE_VAR(eccRng, WC_RNG, 1, HEAP_HINT); + ecc_point* outPub = NULL; + int haveSrc = 0, haveRes = 0, haveRng = 0; + int beforeCount = 0; + + WC_ALLOC_VAR(srcKey, ecc_key, 1, HEAP_HINT); + WC_ALLOC_VAR(resKey, ecc_key, 1, HEAP_HINT); + WC_ALLOC_VAR(eccRng, WC_RNG, 1, HEAP_HINT); + PRIVATE_KEY_UNLOCK(); + if (!WC_VAR_OK(srcKey) || !WC_VAR_OK(resKey) || !WC_VAR_OK(eccRng)) + ret = MEMORY_E; + else + ret = wc_InitRng_ex(eccRng, HEAP_HINT, devId); + if (ret == 0) { + haveRng = 1; + ret = wc_ecc_init_ex(srcKey, HEAP_HINT, devId); + } + if (ret == 0) { + haveSrc = 1; + ret = wc_ecc_make_key(eccRng, 32, srcKey); + } + if (ret == 0) { + outPub = wc_ecc_new_point_h(HEAP_HINT); + if (outPub == NULL) + ret = WC_TEST_RET_ENC_NC; + } + /* Negative test: when the device returns a malformed + * (non-uncompressed) point, wc_CryptoCb_EccMakePub must reject it + * with BUFFER_E rather than accept a bad public key. */ + if (ret == 0) { + myCtx.eccMakePubBadFormat = 1; + ret = wc_ecc_make_pub(srcKey, outPub); + myCtx.eccMakePubBadFormat = 0; + /* expect the wrapper to reject the malformed point */ + if (ret == WC_NO_ERR_TRACE(BUFFER_E)) + ret = 0; + else if (ret == 0) + ret = WC_TEST_RET_ENC_NC; /* must not silently succeed */ + else + ret = WC_TEST_RET_ENC_EC(ret); /* unexpected error */ + } + /* Negative test: when the device claims a result size different from + * the curve's X9.63 length, wc_CryptoCb_EccMakePub must reject it + * with BUFFER_E rather than silently truncate it. */ + if (ret == 0) { + myCtx.eccMakePubBadLen = 1; + ret = wc_ecc_make_pub(srcKey, outPub); + myCtx.eccMakePubBadLen = 0; + if (ret == WC_NO_ERR_TRACE(BUFFER_E)) + ret = 0; + else if (ret == 0) + ret = WC_TEST_RET_ENC_NC; /* must not silently succeed */ + else + ret = WC_TEST_RET_ENC_EC(ret); /* unexpected error */ + } + /* Regression: a key whose private scalar is resident in the device + * (the input key's k is empty here) must still produce its public + * key. The make-pub dispatch has to run before ecc_make_pub_ex's + * software private-scalar range check; otherwise this returns + * ECC_PRIV_KEY_E and the callback is never reached. */ + if (ret == 0) + ret = wc_ecc_init_ex(resKey, HEAP_HINT, devId); + if (ret == 0) { + haveRes = 1; + /* device-backed key: same curve, but no private scalar present */ + ret = wc_ecc_set_curve(resKey, 32, ECC_SECP256R1); + } + if (ret == 0) { + /* device serves make-pub from srcKey's resident public point */ + myCtx.eccResidentKey = srcKey; + beforeCount = myCtx.eccMakePubCount; + ret = wc_ecc_make_pub(resKey, NULL); + myCtx.eccResidentKey = NULL; + + if (ret != 0) { + /* before the fix this is ECC_PRIV_KEY_E: the empty scalar was + * rejected before the dispatch could run */ + ret = WC_TEST_RET_ENC_EC(ret); + } + else if (myCtx.eccMakePubCount != beforeCount + 1) { + ret = WC_TEST_RET_ENC_NC; /* callback was bypassed */ + } + else if (wc_ecc_cmp_point(&resKey->pubkey, &srcKey->pubkey) + != MP_EQ) { + ret = WC_TEST_RET_ENC_NC; /* wrong public point produced */ + } + } + #ifdef WOLFSSL_CUSTOM_CURVES + /* Regression: make-pub via the device for a custom-curve key + * (idx == ECC_CUSTOM_IDX): the handler must serialize the point + * using key->dp->size, not ecc_sets[key->idx]. Reuse srcKey's P-256 + * parameters under a custom-curve identity. */ + if (ret == 0) + ret = wc_ecc_set_custom_curve(srcKey, + wc_ecc_get_curve_params(srcKey->idx)); + if (ret == 0) { + ret = wc_ecc_make_pub(srcKey, outPub); + if (ret != 0) + ret = WC_TEST_RET_ENC_EC(ret); + else if (wc_ecc_cmp_point(outPub, &srcKey->pubkey) != MP_EQ) + ret = WC_TEST_RET_ENC_NC; /* wrong public point produced */ + } + #endif /* WOLFSSL_CUSTOM_CURVES */ + if (outPub != NULL) + wc_ecc_del_point_h(outPub, HEAP_HINT); + if (haveRes) + wc_ecc_free(resKey); + if (haveSrc) + wc_ecc_free(srcKey); + if (haveRng) + wc_FreeRng(eccRng); + WC_FREE_VAR(srcKey, HEAP_HINT); + WC_FREE_VAR(resKey, HEAP_HINT); + WC_FREE_VAR(eccRng, HEAP_HINT); + PRIVATE_KEY_LOCK(); + } +#endif #endif #if defined(WOLF_CRYPTO_CB_ONLY_ECC) && !defined(WOLFSSL_SWDEV) PRIVATE_KEY_UNLOCK(); diff --git a/wolfssl/wolfcrypt/cryptocb.h b/wolfssl/wolfcrypt/cryptocb.h index 801ec7be1d..3cf13abfeb 100644 --- a/wolfssl/wolfcrypt/cryptocb.h +++ b/wolfssl/wolfcrypt/cryptocb.h @@ -245,6 +245,25 @@ typedef struct wc_CryptoInfo { const ecc_key* key; int* sigSize; } ecc_get_sig_size; + struct { + ecc_key* key; /* routing (devId), curve (key->dp), heap, + * resident/sw private scalar d */ + byte* pubOut; /* [out] X9.63 0x04||X||Y, uncompressed */ + word32* pubOutSz; /* in: buf size; out: bytes written */ + } ecc_make_pub; + #ifdef HAVE_ECC_CHECK_KEY + struct { + ecc_key* key; /* routing, curve, heap, resident priv */ + const byte* pubKey; /* X9.63 0x04||X||Y of key->pubkey; NULL + * when key state is ECC_PRIVATEKEY_ONLY */ + word32 pubKeySz; /* 0 when pubKey is NULL */ + int checkOrder; /* 1: validate the point has the curve + * order (point * order == infinity) */ + int checkPriv; /* 1: also validate the private part + * (scalar range, consistency with the + * public point) */ + } ecc_check_pub; /* distinct from ecc_check (priv-key cmp) */ + #endif #endif /* HAVE_ECC */ #ifdef HAVE_CURVE25519 struct { @@ -748,6 +767,12 @@ WOLFSSL_LOCAL int wc_CryptoCb_EccCheckPrivKey(ecc_key* key, const byte* pubKey, WOLFSSL_LOCAL int wc_CryptoCb_EccGetSize(const ecc_key* key, int* keySize); WOLFSSL_LOCAL int wc_CryptoCb_EccGetSigSize(const ecc_key* key, int* sigSize); + +WOLFSSL_LOCAL int wc_CryptoCb_EccMakePub(ecc_key* key, ecc_point* pubOut); +#ifdef HAVE_ECC_CHECK_KEY +WOLFSSL_LOCAL int wc_CryptoCb_EccCheckPubKey(ecc_key* key, int checkOrder, + int checkPriv); +#endif #endif /* HAVE_ECC */ #ifdef HAVE_CURVE25519 diff --git a/wolfssl/wolfcrypt/types.h b/wolfssl/wolfcrypt/types.h index 423efb0f59..cdbc989a36 100644 --- a/wolfssl/wolfcrypt/types.h +++ b/wolfssl/wolfcrypt/types.h @@ -1595,6 +1595,10 @@ enum wc_PkType { #undef _WC_PK_TYPE_MAX #define _WC_PK_TYPE_MAX WC_PK_TYPE_PQC_STATEFUL_SIG_SIGS_LEFT #endif + WC_PK_TYPE_EC_MAKE_PUB = 34, + WC_PK_TYPE_EC_CHECK_PUB_KEY = 35, + #undef _WC_PK_TYPE_MAX + #define _WC_PK_TYPE_MAX WC_PK_TYPE_EC_CHECK_PUB_KEY WC_PK_TYPE_MAX = _WC_PK_TYPE_MAX };