diff --git a/docs/src/5-Features.md b/docs/src/5-Features.md index 34e3aabc0..35bed3915 100644 --- a/docs/src/5-Features.md +++ b/docs/src/5-Features.md @@ -104,7 +104,7 @@ The wolfHSM server exposes the full set of wolfCrypt software algorithms, and th - **Symmetric ciphers**: AES in CBC, CTR, ECB, GCM, and CCM modes; AES key wrap - **Hashing**: SHA-1, SHA-2 (SHA-224, SHA-256, SHA-384, SHA-512), SHA-3 - **Message authentication**: HMAC (over the supported hash functions) and CMAC -- **Asymmetric**: RSA (encryption, signing, key generation), ECC (ECDSA, ECDH), Ed25519, Curve25519 +- **Asymmetric**: RSA (encryption, signing, key generation), ECC (ECDSA, ECDH, public key derivation, key validation), Ed25519, Curve25519 - **Random number generation**: DRBG/RNG backed by the server's entropy source - **Post-quantum cryptography**: ML-DSA (FIPS 204) and ML-KEM (FIPS 203) diff --git a/src/wh_client_crypto.c b/src/wh_client_crypto.c index 93ef3eb9c..e2b9d3fcd 100644 --- a/src/wh_client_crypto.c +++ b/src/wh_client_crypto.c @@ -2858,57 +2858,288 @@ int wh_Client_EccVerify(whClientContext* ctx, ecc_key* key, const uint8_t* sig, return ret; } -#if 0 +/* Response half for ECC make-public. Single-shot receive: returns + * WH_ERROR_NOTREADY if the reply has not arrived yet. */ +static int _EccMakePubResponse(whClientContext* ctx, uint8_t* pubOut, + uint16_t* inout_pubOutSz) +{ + int ret; + uint16_t group; + uint16_t action; + uint16_t res_len = 0; + uint8_t* dataPtr; + whMessageCrypto_EccMakePubResponse* res = NULL; + + dataPtr = wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + return WH_ERROR_BADARGS; + } + + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, + WOLFHSM_CFG_COMM_DATA_LEN, dataPtr); + if (ret != WH_ERROR_OK) { + return ret; + } + + ret = _getCryptoResponse(dataPtr, WC_PK_TYPE_EC_MAKE_PUB, (uint8_t**)&res); + if (ret >= 0) { + const size_t hdr_sz = + sizeof(whMessageCrypto_GenericResponseHeader) + sizeof(*res); + /* Defensive bound: res->pubSz must fit within the actual received + * frame */ + if (res_len < hdr_sz || res->pubSz > (res_len - hdr_sz)) { + return WH_ERROR_ABORTED; + } + if (res->pubSz > (uint32_t)(*inout_pubOutSz)) { + /* Report the size the caller needs so it can re-call */ + *inout_pubOutSz = (uint16_t)res->pubSz; + return WH_ERROR_BUFFER_SIZE; + } + memcpy(pubOut, (uint8_t*)(res + 1), res->pubSz); + *inout_pubOutSz = (uint16_t)res->pubSz; + } + return ret; +} + +int wh_Client_EccMakePub(whClientContext* ctx, ecc_key* key, uint8_t* pubOut, + uint16_t* inout_pubOutSz) +{ + int ret = WH_ERROR_OK; + whMessageCrypto_EccMakePubRequest* req = NULL; + uint8_t* dataPtr = NULL; + + /* Transaction state */ + whKeyId key_id; + int evict = 0; + + WH_DEBUG_CLIENT_VERBOSE("ctx:%p key:%p pubOut:%p inout_pubOutSz:%p\n", ctx, + key, pubOut, inout_pubOutSz); + + /* Validate response-side args upfront so we never send a request that the + * matching *Response would then reject without consuming the reply. */ + if ((ctx == NULL) || (key == NULL) || (pubOut == NULL) || + (inout_pubOutSz == NULL)) { + return WH_ERROR_BADARGS; + } + + key_id = WH_DEVCTX_TO_KEYID(key->devCtx); + /* Import key if necessary */ + if (WH_KEYID_ISERASED(key_id)) { + /* Must import the key to the server and evict it afterwards */ + uint8_t keyLabel[] = "TempEccMakePub"; + whNvmFlags flags = WH_NVM_FLAGS_NONE; + + ret = wh_Client_EccImportKey(ctx, key, &key_id, flags, sizeof(keyLabel), + keyLabel); + if (ret == 0) { + evict = 1; + } + } + + if (ret == WH_ERROR_OK) { + dataPtr = wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + ret = WH_ERROR_BADARGS; + } + } + + if (ret == WH_ERROR_OK) { + /* Request Message */ + uint16_t group = WH_MESSAGE_GROUP_CRYPTO; + uint16_t action = WC_ALGO_TYPE_PK; + uint32_t options = 0; + + uint16_t req_len = sizeof(whMessageCrypto_GenericRequestHeader) + + sizeof(whMessageCrypto_EccMakePubRequest); + + if (req_len <= WOLFHSM_CFG_COMM_DATA_LEN) { + /* Setup generic header and get pointer to request data */ + req = (whMessageCrypto_EccMakePubRequest*)_createCryptoRequest( + dataPtr, WC_PK_TYPE_EC_MAKE_PUB, ctx->cryptoAffinity); + + if (evict != 0) { + options |= WH_MESSAGE_CRYPTO_ECCMAKEPUB_OPTIONS_EVICT; + } + + memset(req, 0, sizeof(*req)); + req->options = options; + req->keyId = key_id; + + WH_DEBUG_CLIENT_VERBOSE("EccMakePub req: key_id=%x options=%u\n", + key_id, (unsigned int)options); + + /* write request */ + ret = wh_Client_SendRequest(ctx, group, action, req_len, dataPtr); + + if (ret == WH_ERROR_OK) { + /* Server will evict at this point. Reset evict */ + evict = 0; + + do { + ret = _EccMakePubResponse(ctx, pubOut, inout_pubOutSz); + } while (ret == WH_ERROR_NOTREADY); + } + } + else { + /* Request length is too long */ + ret = WH_ERROR_BADARGS; + } + } + /* Evict the key manually on error */ + if (evict != 0) { + (void)wh_Client_KeyEvict(ctx, key_id); + } + WH_DEBUG_CLIENT_VERBOSE("ret:%d\n", ret); + return ret; +} + +#ifdef HAVE_ECC_CHECK_KEY +/* Response half for ECC key validation. The verdict is the response code + * itself: 0 when the key is valid, a wolfCrypt error when it is not. */ +static int _EccCheckPubKeyResponse(whClientContext* ctx) +{ + int ret; + uint16_t group; + uint16_t action; + uint16_t res_len = 0; + uint8_t* dataPtr; + whMessageCrypto_EccCheckResponse* res = NULL; + + dataPtr = wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + return WH_ERROR_BADARGS; + } + + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, + WOLFHSM_CFG_COMM_DATA_LEN, dataPtr); + if (ret != WH_ERROR_OK) { + return ret; + } + + /* A negative rc here is the verdict on an invalid key rather than a + * transport failure, and is handed back to wolfCrypt verbatim. */ + ret = _getCryptoResponse(dataPtr, WC_PK_TYPE_EC_CHECK_PUB_KEY, + (uint8_t**)&res); + if (ret >= 0) { + const size_t hdr_sz = + sizeof(whMessageCrypto_GenericResponseHeader) + sizeof(*res); + /* A success rc must be accompanied by an affirmative body */ + if ((res_len < hdr_sz) || (res->ok == 0)) { + return WH_ERROR_ABORTED; + } + } + return ret; +} + int wh_Client_EccCheckPubKey(whClientContext* ctx, ecc_key* key, - const uint8_t* pub_key, uint16_t pub_key_len) + const uint8_t* pub_key, uint16_t pub_key_len, + int check_order, int check_priv) { -/* TODO: Check if keyid is set on incoming key. - * if not, import private key to server - * send request with pub key der - * server creates new key with private and public. check - * evict temp key - */ - int ret; + int ret = WH_ERROR_OK; + whMessageCrypto_EccCheckRequest* req = NULL; + uint8_t* dataPtr = NULL; + + /* Transaction state */ + whKeyId key_id; + int evict = 0; + + WH_DEBUG_CLIENT_VERBOSE("ctx:%p key:%p pub_key:%p pub_key_len:%u\n", ctx, + key, pub_key, pub_key_len); - if ( (ctx == NULL) || - (key == NULL) || - (pub_key == NULL) || - (pub_key_len == 0) ) { + if ((ctx == NULL) || (key == NULL) || + ((pub_key == NULL) && (pub_key_len > 0))) { return WH_ERROR_BADARGS; } - int curve_id = wc_ecc_get_curve_id(key->idx); - whKeyId key_id = WH_DEVCTX_TO_KEYID(key->devCtx); - /* Request packet */ - wh_Packet_pk_ecc_check_req* req = &packet->pkEccCheckReq; - uint8_t* req_pub_key = (uint8_t*)(req + 1); + /* check_order/check_priv mirror wolfCrypt's cryptocb request shape but + * are not carried on the wire: the server always performs the full + * validation, a strict superset of any partial check. */ + (void)check_order; + (void)check_priv; - req->type = WC_PK_TYPE_EC_CHECK_PRIV_KEY; - req->keyId = key_id; - req->curveId = curve_id; + key_id = WH_DEVCTX_TO_KEYID(key->devCtx); + /* Import key if necessary */ + if (WH_KEYID_ISERASED(key_id)) { + /* Must import the key to the server and evict it afterwards */ + uint8_t keyLabel[] = "TempEccCheck"; + whNvmFlags flags = WH_NVM_FLAGS_NONE; - /* Response packet */ - wh_Packet_pk_ecc_check_res* res = &packet->pkEccCheckRes; + ret = wh_Client_EccImportKey(ctx, key, &key_id, flags, sizeof(keyLabel), + keyLabel); + if (ret == 0) { + evict = 1; + } + } + if (ret == WH_ERROR_OK) { + dataPtr = wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + ret = WH_ERROR_BADARGS; + } + } - /* write request */ - ret = wh_Client_SendRequest(ctx, group, - WC_ALGO_TYPE_PK, - WH_PACKET_STUB_SIZE + sizeof(packet->pkEccCheckReq), - (uint8_t*)packet); - /* read response */ - if (ret == 0) { - do { - ret = wh_Client_RecvResponse(ctx, &group, &action, &dataSz, - WOLFHSM_CFG_COMM_DATA_LEN, (uint8_t*)packet); - } while (ret == WH_ERROR_NOTREADY); + if (ret == WH_ERROR_OK) { + /* Request Message */ + uint16_t group = WH_MESSAGE_GROUP_CRYPTO; + uint16_t action = WC_ALGO_TYPE_PK; + uint32_t options = 0; + + /* Compute the total length in a wide type so a large pub_key_len + * cannot wrap the sum before the bounds check below. Narrowing to + * the uint16_t wire length is safe once the check has passed. */ + uint32_t req_len = (uint32_t)sizeof(whMessageCrypto_GenericRequestHeader) + + (uint32_t)sizeof(whMessageCrypto_EccCheckRequest) + + pub_key_len; + + if (req_len <= WOLFHSM_CFG_COMM_DATA_LEN) { + /* Setup generic header and get pointer to request data */ + req = (whMessageCrypto_EccCheckRequest*)_createCryptoRequest( + dataPtr, WC_PK_TYPE_EC_CHECK_PUB_KEY, ctx->cryptoAffinity); + + if (evict != 0) { + options |= WH_MESSAGE_CRYPTO_ECCCHECK_OPTIONS_EVICT; + } + + memset(req, 0, sizeof(*req)); + req->options = options; + req->keyId = key_id; + req->curveId = (uint32_t)wc_ecc_get_curve_id(key->idx); + req->pubSz = pub_key_len; + if ((pub_key != NULL) && (pub_key_len > 0)) { + memcpy((uint8_t*)(req + 1), pub_key, pub_key_len); + } + + WH_DEBUG_CLIENT_VERBOSE("EccCheckPubKey req: key_id=%x, " + "pub_key_len=%u, options=%u\n", + key_id, (unsigned int)pub_key_len, + (unsigned int)options); + + /* write request */ + ret = wh_Client_SendRequest(ctx, group, action, (uint16_t)req_len, + dataPtr); + + if (ret == WH_ERROR_OK) { + /* Server will evict at this point. Reset evict */ + evict = 0; + + do { + ret = _EccCheckPubKeyResponse(ctx); + } while (ret == WH_ERROR_NOTREADY); + } + } + else { + /* Request length is too long */ + ret = WH_ERROR_BADARGS; + } } - if (ret == 0) { - if (packet->rc != 0) - ret = packet->rc; + /* Evict the key manually on error */ + if (evict != 0) { + (void)wh_Client_KeyEvict(ctx, key_id); } + WH_DEBUG_CLIENT_VERBOSE("ret:%d\n", ret); + return ret; } -#endif /* 0 */ +#endif /* HAVE_ECC_CHECK_KEY */ #endif /* HAVE_ECC */ diff --git a/src/wh_client_cryptocb.c b/src/wh_client_cryptocb.c index 71158d3ba..8ad889399 100644 --- a/src/wh_client_cryptocb.c +++ b/src/wh_client_cryptocb.c @@ -372,20 +372,61 @@ int wh_Client_CryptoCbStd(int devId, wc_CryptoInfo* info, void* inCtx) } break; #endif /* HAVE_ECC_VERIFY */ + case WC_PK_TYPE_EC_MAKE_PUB: { + /* Extract info parameters */ + ecc_key* key = info->pk.ecc_make_pub.key; + uint8_t* pub_out = (uint8_t*)info->pk.ecc_make_pub.pubOut; + word32* out_pub_len = info->pk.ecc_make_pub.pubOutSz; + + uint16_t pub_len = 0; + if (out_pub_len != NULL) { + /* Clamp rather than truncate: an oversized capacity must not + * wrap to a small one and provoke a spurious BUFFER_E. */ + pub_len = (*out_pub_len > UINT16_MAX) + ? UINT16_MAX + : (uint16_t)(*out_pub_len); + } + + ret = wh_Client_EccMakePub(ctx, key, pub_out, &pub_len); + /* Propagate updated length on BUFFER_SIZE so callers can re-call + * with a sufficiently large output buffer. */ + if (((ret == WH_ERROR_OK) || (ret == WH_ERROR_BUFFER_SIZE)) && + (out_pub_len != NULL)) { + *out_pub_len = pub_len; + } + if (ret == WH_ERROR_BADARGS) { + ret = BAD_FUNC_ARG; + } + else if (ret == WH_ERROR_BUFFER_SIZE) { + ret = BUFFER_E; + } + } break; + #ifdef HAVE_ECC_CHECK_KEY - case WC_PK_TYPE_EC_CHECK_PRIV_KEY: - { -#if 0 - /* TODO: Expose this and add wolfcrypt functions to test */ + case WC_PK_TYPE_EC_CHECK_PUB_KEY: { /* Extract info parameters */ - ecc_key* key = info->pk.ecc_check.key; - const uint8_t* pub_key = info->pk.ecc_check.pubKey; - uint32_t pub_key_len = info->pk.ecc_check.pubKeySz; + ecc_key* key = info->pk.ecc_check_pub.key; + const uint8_t* pub_key = + (const uint8_t*)info->pk.ecc_check_pub.pubKey; + word32 pub_key_len = info->pk.ecc_check_pub.pubKeySz; + int check_order = info->pk.ecc_check_pub.checkOrder; + int check_priv = info->pk.ecc_check_pub.checkPriv; + + if (pub_key_len > UINT16_MAX) { + ret = BAD_FUNC_ARG; + } + else { + ret = wh_Client_EccCheckPubKey(ctx, key, pub_key, + (uint16_t)pub_key_len, + check_order, check_priv); + if (ret == WH_ERROR_BADARGS) { + ret = BAD_FUNC_ARG; + } + } + } break; - ret = wh_Client_EccCheckPubKey(ctx, key, pub_key, pub_key_len); -#else + case WC_PK_TYPE_EC_CHECK_PRIV_KEY: { ret = CRYPTOCB_UNAVAILABLE; -#endif } break; #endif /* HAVE_ECC_CHECK_KEY */ diff --git a/src/wh_message_crypto.c b/src/wh_message_crypto.c index 8012252ec..45579b7be 100644 --- a/src/wh_message_crypto.c +++ b/src/wh_message_crypto.c @@ -463,6 +463,31 @@ int wh_MessageCrypto_TranslateEccVerifyResponse( return 0; } +/* ECC Make Public Request translation */ +int wh_MessageCrypto_TranslateEccMakePubRequest( + uint16_t magic, const whMessageCrypto_EccMakePubRequest* src, + whMessageCrypto_EccMakePubRequest* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T32(magic, dest, src, options); + WH_T32(magic, dest, src, keyId); + return 0; +} + +/* ECC Make Public Response translation */ +int wh_MessageCrypto_TranslateEccMakePubResponse( + uint16_t magic, const whMessageCrypto_EccMakePubResponse* src, + whMessageCrypto_EccMakePubResponse* dest) +{ + if ((src == NULL) || (dest == NULL)) { + return WH_ERROR_BADARGS; + } + WH_T32(magic, dest, src, pubSz); + return 0; +} + /* ECC Check Request translation */ int wh_MessageCrypto_TranslateEccCheckRequest( uint16_t magic, const whMessageCrypto_EccCheckRequest* src, @@ -471,8 +496,10 @@ int wh_MessageCrypto_TranslateEccCheckRequest( if ((src == NULL) || (dest == NULL)) { return WH_ERROR_BADARGS; } + WH_T32(magic, dest, src, options); WH_T32(magic, dest, src, keyId); WH_T32(magic, dest, src, curveId); + WH_T32(magic, dest, src, pubSz); return 0; } diff --git a/src/wh_server_crypto.c b/src/wh_server_crypto.c index 5b557973d..b1c5fa584 100644 --- a/src/wh_server_crypto.c +++ b/src/wh_server_crypto.c @@ -145,6 +145,15 @@ static int _HandleEccVerify(whServerContext* ctx, uint16_t magic, int devId, const void* cryptoDataIn, uint16_t inSize, void* cryptoDataOut, uint16_t* outSize); #endif /* HAVE_ECC_VERIFY */ +static int _HandleEccMakePub(whServerContext* ctx, uint16_t magic, int devId, + const void* cryptoDataIn, uint16_t inSize, + void* cryptoDataOut, uint16_t* outSize); +#ifdef HAVE_ECC_CHECK_KEY +static int _HandleEccCheckPubKey(whServerContext* ctx, uint16_t magic, + int devId, const void* cryptoDataIn, + uint16_t inSize, void* cryptoDataOut, + uint16_t* outSize); +#endif /* HAVE_ECC_CHECK_KEY */ #endif /* HAVE_ECC */ #ifdef HAVE_CURVE25519 @@ -1619,6 +1628,179 @@ static int _HandleEccVerify(whServerContext* ctx, uint16_t magic, int devId, return ret; } #endif /* HAVE_ECC_VERIFY */ + +static int _HandleEccMakePub(whServerContext* ctx, uint16_t magic, int devId, + const void* cryptoDataIn, uint16_t inSize, + void* cryptoDataOut, uint16_t* outSize) +{ + int ret; + ecc_key key[1]; + whMessageCrypto_EccMakePubRequest req; + whMessageCrypto_EccMakePubResponse res; + + /* Validate minimum size */ + if (inSize < sizeof(whMessageCrypto_EccMakePubRequest)) { + return WH_ERROR_BADARGS; + } + + /* Translate request */ + ret = wh_MessageCrypto_TranslateEccMakePubRequest( + magic, (const whMessageCrypto_EccMakePubRequest*)cryptoDataIn, &req); + if (ret != 0) { + return ret; + } + + /* Extract parameters from translated request */ + whKeyId key_id = wh_KeyId_TranslateFromClient( + WH_KEYTYPE_CRYPTO, ctx->comm->client_id, req.keyId); + int evict = !!(req.options & WH_MESSAGE_CRYPTO_ECCMAKEPUB_OPTIONS_EVICT); + + /* Response message */ + byte* res_pub = + (uint8_t*)(cryptoDataOut) + sizeof(whMessageCrypto_EccMakePubResponse); + word32 pub_size = (word32)(WOLFHSM_CFG_COMM_DATA_LEN - + sizeof(whMessageCrypto_GenericResponseHeader) - + sizeof(whMessageCrypto_EccMakePubResponse)); + + /* Deliberately no wh_Server_KeystoreFindEnforceKeyUsage(): this operation + * only produces public material, which keystore policy treats as + * always-exportable (see _KeystoreCheckPolicy / WH_KS_OP_EXPORT_PUBLIC). + * The private scalar is used solely to derive the public point. */ + ret = wc_ecc_init_ex(key, NULL, devId); + if (ret == 0) { + /* load the private key */ + ret = wh_Server_EccKeyCacheExport(ctx, key_id, key); + if (ret == WH_ERROR_OK) { + /* Always derive Q = d*G rather than returning whatever public point + * the cached key happens to carry, so the result cannot be steered + * by a cache entry whose stored point disagrees with its scalar. + * This also matches software wc_ecc_make_pub(), which fails on a + * key that holds no private scalar. The RNG blinds the multiply + * on the multi-precision path (SP builds ignore it). */ + ret = wc_ecc_make_pub_ex(key, NULL, ctx->crypto->rng); + if (ret == 0) { + ret = wc_ecc_export_x963(key, res_pub, &pub_size); + } + WH_DEBUG_SERVER_VERBOSE("EccMakePub: key_id=%x, pub_size=%u, " + "ret=%d\n", + key_id, (unsigned)pub_size, ret); + } + wc_ecc_free(key); + } + + if (evict != 0) { + /* User requested to evict from cache, even if the call failed */ + (void)wh_Server_KeystoreEvictKey(ctx, key_id); + } + if (ret == 0) { + res.pubSz = pub_size; + + wh_MessageCrypto_TranslateEccMakePubResponse( + magic, &res, (whMessageCrypto_EccMakePubResponse*)cryptoDataOut); + + *outSize = sizeof(whMessageCrypto_EccMakePubResponse) + pub_size; + } + return ret; +} + +#ifdef HAVE_ECC_CHECK_KEY +static int _HandleEccCheckPubKey(whServerContext* ctx, uint16_t magic, + int devId, const void* cryptoDataIn, + uint16_t inSize, void* cryptoDataOut, + uint16_t* outSize) +{ + int ret; + ecc_key key[1]; + whMessageCrypto_EccCheckRequest req; + whMessageCrypto_EccCheckResponse res; + + /* Validate minimum size */ + if (inSize < sizeof(whMessageCrypto_EccCheckRequest)) { + return WH_ERROR_BADARGS; + } + + /* Translate request */ + ret = wh_MessageCrypto_TranslateEccCheckRequest( + magic, (const whMessageCrypto_EccCheckRequest*)cryptoDataIn, &req); + if (ret != 0) { + return ret; + } + + /* Validate variable-length fields fit within inSize */ + if (req.pubSz > inSize - sizeof(whMessageCrypto_EccCheckRequest)) { + return WH_ERROR_BADARGS; + } + + /* Extract parameters from translated request */ + whKeyId key_id = wh_KeyId_TranslateFromClient( + WH_KEYTYPE_CRYPTO, ctx->comm->client_id, req.keyId); + const uint8_t* req_pub = (const uint8_t*)(cryptoDataIn) + + sizeof(whMessageCrypto_EccCheckRequest); + int evict = !!(req.options & WH_MESSAGE_CRYPTO_ECCCHECK_OPTIONS_EVICT); + + /* The curve travels with the key's DER encoding, so curveId is redundant. + * The request deliberately carries no partial-validation knobs: + * wc_ecc_check_key() is wolfCrypt's only public validation entry point + * (partial validation lives in a static function reachable only from the + * import APIs) and the only route to this server's own crypto callback + * (the key is bound to the server devId), so validation always runs in + * full. */ + (void)req.curveId; + + /* no need to enforce flags for pub key operation */ + ret = wc_ecc_init_ex(key, NULL, devId); + if (ret == 0) { + /* load the key to validate */ + ret = wh_Server_EccKeyCacheExport(ctx, key_id, key); + if (ret == WH_ERROR_OK) { + /* A private-only key has no point to validate yet. Unlike make-pub + * we derive only when one is missing: validating a key means + * checking the point it actually carries. The RNG blinds the + * multiply on the multi-precision path (SP builds ignore it). */ + if (key->type == ECC_PRIVATEKEY_ONLY) { + ret = wc_ecc_make_pub_ex(key, NULL, ctx->crypto->rng); + } + if (ret == 0) { + ret = wc_ecc_check_key(key); + } + /* Reject a key whose caller-held public point disagrees with the + * point that actually belongs to the resident key. ECC_PRIV_KEY_E + * matches what software wc_ecc_check_key() returns for exactly + * this condition (ecc_check_privkey_gen: d*G != Q). */ + if ((ret == 0) && (req.pubSz > 0)) { + byte pub[1 + 2 * MAX_ECC_BYTES]; + word32 pub_size = sizeof(pub); + + ret = wc_ecc_export_x963(key, pub, &pub_size); + if ((ret == 0) && ((pub_size != req.pubSz) || + (memcmp(pub, req_pub, pub_size) != 0))) { + ret = ECC_PRIV_KEY_E; + } + } + WH_DEBUG_SERVER_VERBOSE("EccCheckPubKey: key_id=%x, pubSz=%u, " + "ret=%d\n", + key_id, (unsigned)req.pubSz, ret); + } + wc_ecc_free(key); + } + + if (evict != 0) { + /* User requested to evict from cache, even if the call failed */ + (void)wh_Server_KeystoreEvictKey(ctx, key_id); + } + /* The validation verdict itself travels in the response header rc, which + * is what wolfCrypt's crypto callback contract expects. */ + if (ret == 0) { + res.ok = 1; + + wh_MessageCrypto_TranslateEccCheckResponse( + magic, &res, (whMessageCrypto_EccCheckResponse*)cryptoDataOut); + + *outSize = sizeof(whMessageCrypto_EccCheckResponse); + } + return ret; +} +#endif /* HAVE_ECC_CHECK_KEY */ #endif /* HAVE_ECC */ @@ -5721,6 +5903,18 @@ int wh_Server_HandleCryptoRequest(whServerContext* ctx, uint16_t magic, &cryptoOutSize); break; #endif /* HAVE_ECC_VERIFY */ + case WC_PK_TYPE_EC_MAKE_PUB: + ret = _HandleEccMakePub(ctx, magic, devId, cryptoDataIn, + cryptoInSize, cryptoDataOut, + &cryptoOutSize); + break; +#ifdef HAVE_ECC_CHECK_KEY + case WC_PK_TYPE_EC_CHECK_PUB_KEY: + ret = _HandleEccCheckPubKey(ctx, magic, devId, cryptoDataIn, + cryptoInSize, cryptoDataOut, + &cryptoOutSize); + break; +#endif /* HAVE_ECC_CHECK_KEY */ #endif /* HAVE_ECC */ #ifdef HAVE_CURVE25519 diff --git a/src/wh_server_keystore.c b/src/wh_server_keystore.c index b08143c67..721af39c2 100644 --- a/src/wh_server_keystore.c +++ b/src/wh_server_keystore.c @@ -466,8 +466,9 @@ static int _ExportRsaPublicKey(whServerContext* server, whKeyId keyId, int ret = WH_ERROR_OK; RsaKey key[1]; int pub_ret; + int devId = (server->crypto != NULL) ? server->devId : INVALID_DEVID; - ret = wc_InitRsaKey_ex(key, NULL, INVALID_DEVID); + ret = wc_InitRsaKey_ex(key, NULL, devId); if (ret == 0) { ret = wh_Server_CacheExportRsaKey(server, keyId, key); if (ret == 0) { @@ -493,10 +494,21 @@ static int _ExportEccPublicKey(whServerContext* server, whKeyId keyId, int ret = WH_ERROR_OK; ecc_key key[1]; int pub_ret; + int devId = (server->crypto != NULL) ? server->devId : INVALID_DEVID; - ret = wc_ecc_init_ex(key, NULL, INVALID_DEVID); + ret = wc_ecc_init_ex(key, NULL, devId); if (ret == 0) { ret = wh_Server_EccKeyCacheExport(server, keyId, key); + if (ret == 0 && key->type == ECC_PRIVATEKEY_ONLY) { + /* A private-only key has no public point to encode yet. Derive it, + * as the public half is not sensitive material. The RNG blinds the + * multiply on the multi-precision path (SP builds ignore it), but + * a keystore-only server has no crypto context: pass NULL so + * wolfCrypt skips the blinding instead of dereferencing NULL. */ + WC_RNG* rng = (server->crypto != NULL) ? server->crypto->rng + : NULL; + ret = wc_ecc_make_pub_ex(key, NULL, rng); + } if (ret == 0) { pub_ret = wc_EccPublicKeyToDer(key, out, (word32)*outSz, 1); if (pub_ret > 0) { @@ -519,8 +531,10 @@ static int _ExportEd25519PublicKey(whServerContext* server, whKeyId keyId, int ret = WH_ERROR_OK; ed25519_key key[1]; int pub_ret; + int devId = (server->crypto != NULL) ? server->devId + : INVALID_DEVID; - ret = wc_ed25519_init_ex(key, NULL, INVALID_DEVID); + ret = wc_ed25519_init_ex(key, NULL, devId); if (ret == 0) { ret = wh_Server_CacheExportEd25519Key(server, keyId, key); if (ret == 0) { @@ -545,8 +559,10 @@ static int _ExportMldsaPublicKey(whServerContext* server, whKeyId keyId, int ret = WH_ERROR_OK; wc_MlDsaKey key[1]; int pub_ret; + int devId = (server->crypto != NULL) ? server->devId + : INVALID_DEVID; - ret = wc_MlDsaKey_Init(key, NULL, INVALID_DEVID); + ret = wc_MlDsaKey_Init(key, NULL, devId); if (ret == 0) { ret = wh_Server_MlDsaKeyCacheExport(server, keyId, key); if (ret == 0) { @@ -571,8 +587,10 @@ static int _ExportCurve25519PublicKey(whServerContext* server, whKeyId keyId, int ret = WH_ERROR_OK; curve25519_key key[1]; int pub_ret; + int devId = (server->crypto != NULL) ? server->devId + : INVALID_DEVID; - ret = wc_curve25519_init_ex(key, NULL, INVALID_DEVID); + ret = wc_curve25519_init_ex(key, NULL, devId); if (ret == 0) { ret = wh_Server_CacheExportCurve25519Key(server, keyId, key); if (ret == 0) { @@ -597,6 +615,7 @@ static int _ExportMlkemPublicKey(whServerContext* server, whKeyId keyId, int ret = WH_ERROR_OK; MlKemKey key[1]; word32 pubSize; + int devId = (server->crypto != NULL) ? server->devId : INVALID_DEVID; /* Pick the lowest compiled-in level as the initial hint; * wh_Crypto_MlKemDeserializeKey (called via * wh_Server_MlKemKeyCacheExport) probes the remaining enabled levels. */ @@ -608,7 +627,7 @@ static int _ExportMlkemPublicKey(whServerContext* server, whKeyId keyId, const int initLevel = WC_ML_KEM_1024; #endif - ret = wc_MlKemKey_Init(key, initLevel, NULL, INVALID_DEVID); + ret = wc_MlKemKey_Init(key, initLevel, NULL, devId); if (ret == 0) { ret = wh_Server_MlKemKeyCacheExport(server, keyId, key); if (ret == 0) { @@ -640,8 +659,9 @@ static int _ExportLmsPublicKey(whServerContext* server, whKeyId keyId, int ret; LmsKey key[1]; word32 pubLen = 0; + int devId = (server->crypto != NULL) ? server->devId : INVALID_DEVID; - ret = wc_LmsKey_Init(key, NULL, INVALID_DEVID); + ret = wc_LmsKey_Init(key, NULL, devId); if (ret == 0) { ret = wh_Server_LmsKeyCacheExport(server, keyId, key); if (ret == WH_ERROR_OK) { @@ -669,8 +689,9 @@ static int _ExportXmssPublicKey(whServerContext* server, whKeyId keyId, int ret; XmssKey key[1]; word32 pubLen = 0; + int devId = (server->crypto != NULL) ? server->devId : INVALID_DEVID; - ret = wc_XmssKey_Init(key, NULL, INVALID_DEVID); + ret = wc_XmssKey_Init(key, NULL, devId); if (ret == 0) { ret = wh_Server_XmssKeyCacheExport(server, keyId, key); if (ret == WH_ERROR_OK) { diff --git a/test-refactor/client-server/wh_test_crypto_ecc.c b/test-refactor/client-server/wh_test_crypto_ecc.c index 7be8fa082..ed84629b1 100644 --- a/test-refactor/client-server/wh_test_crypto_ecc.c +++ b/test-refactor/client-server/wh_test_crypto_ecc.c @@ -24,6 +24,10 @@ * _whTest_CryptoEcc - ECDH + ECDSA across ephemeral / export / * cache key paths * _whTest_CryptoEccCacheDuplicate - cache slot replacement semantics + * _whTest_CryptoEccMakePub - WC_PK_TYPE_EC_MAKE_PUB, client-held and + * server-resident private-only keys + * _whTest_CryptoEccCheckPubKey - WC_PK_TYPE_EC_CHECK_PUB_KEY validation, + * including mismatched/off-curve rejection * _whTest_CryptoEccCrossVerify - HSM<->SW signature interop, P-256/384/521 * _whTest_CryptoEccAsync - async sign/verify, ECDH, server keygen */ @@ -650,6 +654,546 @@ static int _whTest_CryptoEccCacheKeyAndExportPublic(whClientContext* ctx) #if !defined(WOLF_CRYPTO_CB_ONLY_ECC) +/* Build a private-key-only ECC key bound to devId, along with a locally + * generated reference keypair holding the public point it must derive to. */ +static int whTest_MakePrivOnlyEccKey(int devId, WC_RNG* rng, ecc_key* refKey, + ecc_key* privOnlyKey) +{ + int ret; + uint8_t d[ECC_MAXSIZE]; + word32 dLen = sizeof(d); + + ret = wc_ecc_init_ex(refKey, NULL, INVALID_DEVID); + if (ret != 0) { + WH_ERROR_PRINT("Failed to init reference ECC key %d\n", ret); + return ret; + } + + ret = wc_ecc_make_key_ex(rng, TEST_ECC_KEYSIZE, refKey, TEST_ECC_CURVE_ID); + if (ret == 0) { + ret = wc_ecc_export_private_only(refKey, d, &dLen); + } + if (ret == 0) { + ret = wc_ecc_init_ex(privOnlyKey, NULL, devId); + if (ret == 0) { + ret = wc_ecc_import_private_key_ex(d, dLen, NULL, 0, privOnlyKey, + TEST_ECC_CURVE_ID); + if (ret != 0) { + wc_ecc_free(privOnlyKey); + } + } + } + if (ret != 0) { + wc_ecc_free(refKey); + WH_ERROR_PRINT("Failed to build private-only ECC key %d\n", ret); + } + return ret; +} + +/* Compare an ecc_point against the public point of key */ +static int whTest_EccPointMatchesKey(ecc_point* point, ecc_key* key) +{ + if (wc_ecc_cmp_point(point, &key->pubkey) != MP_EQ) { + WH_ERROR_PRINT("Derived ECC public point does not match\n"); + return -1; + } + return 0; +} + +/* Exercise WC_PK_TYPE_EC_MAKE_PUB, both for a key the client holds and for one + * resident on the server. Also covers exporting the public half of a resident + * private-only key, which requires the server to derive it. */ +static int _whTest_CryptoEccMakePub(whClientContext* ctx) +{ + int devId = WH_CLIENT_DEVID(ctx); + int ret; + WC_RNG rng[1]; + ecc_key refKey[1]; + ecc_key privOnlyKey[1]; + ecc_point* pub = NULL; + whKeyId keyId = WH_KEYID_ERASED; + uint8_t keyLabel[] = "EccMakePub"; + + ret = wc_InitRng_ex(rng, NULL, devId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret); + return ret; + } + + ret = whTest_MakePrivOnlyEccKey(devId, rng, refKey, privOnlyKey); + if (ret != 0) { + (void)wc_FreeRng(rng); + return ret; + } + + pub = wc_ecc_new_point(); + if (pub == NULL) { + ret = MEMORY_E; + } + + /* Client-held private-only key: the cryptoCb imports it, derives on the + * server, and evicts it again. */ + if (ret == 0) { + ret = wc_ecc_make_pub(privOnlyKey, pub); + if (ret != 0) { + WH_ERROR_PRINT("wc_ecc_make_pub on client key failed %d\n", ret); + } + else { + ret = whTest_EccPointMatchesKey(pub, refKey); + } + } + + /* Direct API with an undersized output buffer must report the required + * X9.63 size (0x04 || X || Y) in *inout_pubOutSz and return + * WH_ERROR_BUFFER_SIZE, after which a re-call with the reported size + * succeeds. */ + if (ret == 0) { + uint8_t smallPub[4] = {0}; + uint8_t pubOut[1 + 2 * TEST_ECC_KEYSIZE] = {0}; + uint16_t pubOutSz = sizeof(smallPub); + int rc; + + rc = wh_Client_EccMakePub(ctx, privOnlyKey, smallPub, &pubOutSz); + if (rc != WH_ERROR_BUFFER_SIZE) { + WH_ERROR_PRINT("Undersized EccMakePub returned %d " + "(want WH_ERROR_BUFFER_SIZE)\n", + rc); + ret = -1; + } + else if (pubOutSz != (1 + 2 * TEST_ECC_KEYSIZE)) { + WH_ERROR_PRINT("Undersized EccMakePub required size %u " + "(want %u)\n", + (unsigned)pubOutSz, + (unsigned)(1 + 2 * TEST_ECC_KEYSIZE)); + ret = -1; + } + if (ret == 0) { + rc = wh_Client_EccMakePub(ctx, privOnlyKey, pubOut, &pubOutSz); + if (rc != 0) { + WH_ERROR_PRINT( + "EccMakePub re-call with reported size failed %d\n", rc); + ret = rc; + } + else if (pubOutSz != (1 + 2 * TEST_ECC_KEYSIZE) || + pubOut[0] != 0x04) { + WH_ERROR_PRINT("EccMakePub re-call produced bad point " + "(size=%u first=0x%02x)\n", + (unsigned)pubOutSz, pubOut[0]); + ret = -1; + } + } + } + + /* Same derivation against a key that lives on the server */ + if (ret == 0) { + ret = wh_Client_EccImportKey(ctx, privOnlyKey, &keyId, + WH_NVM_FLAGS_USAGE_ANY, sizeof(keyLabel), + keyLabel); + if (ret != 0) { + WH_ERROR_PRINT("Failed to cache private-only ECC key %d\n", ret); + } + } + if (ret == 0) { + ecc_key hsmKey[1]; + ecc_point* cachedPub = wc_ecc_new_point(); + + if (cachedPub == NULL) { + ret = MEMORY_E; + } + else { + ret = wc_ecc_init_ex(hsmKey, NULL, devId); + if (ret == 0) { + ret = wh_Client_EccSetKeyId(hsmKey, keyId); + if (ret == 0) { + /* Curve parameters aren't carried by a cached key handle */ + ret = wc_ecc_set_curve(hsmKey, TEST_ECC_KEYSIZE, + TEST_ECC_CURVE_ID); + } + if (ret == 0) { + ret = wc_ecc_make_pub(hsmKey, cachedPub); + if (ret != 0) { + WH_ERROR_PRINT( + "wc_ecc_make_pub on cached key failed %d\n", ret); + } + else { + ret = whTest_EccPointMatchesKey(cachedPub, refKey); + } + } + wc_ecc_free(hsmKey); + } + wc_ecc_del_point(cachedPub); + } + } + + /* Exporting the public half of a resident private-only key requires the + * server to derive it first. */ + if (ret == 0) { + ecc_key pubKey[1]; + ret = wc_ecc_init_ex(pubKey, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wh_Client_EccExportPublicKey(ctx, keyId, pubKey, 0, NULL); + if (ret != 0) { + WH_ERROR_PRINT( + "Public export of private-only ECC key failed %d\n", ret); + } + else if (wc_ecc_cmp_point(&pubKey->pubkey, &refKey->pubkey) != + MP_EQ) { + WH_ERROR_PRINT("Exported ECC public key does not match\n"); + ret = -1; + } + wc_ecc_free(pubKey); + } + } + + /* A cached keypair whose stored public point does not belong to its private + * scalar must still derive the true point, not hand back the stored one. */ + if (ret == 0) { + whKeyId poisonedId = WH_KEYID_ERASED; + uint8_t poisonedLabel[] = "EccMakePubPoison"; + uint8_t d[ECC_MAXSIZE]; + word32 dLen = sizeof(d); + uint8_t qx[ECC_MAXSIZE]; + word32 qxLen = sizeof(qx); + uint8_t qy[ECC_MAXSIZE]; + word32 qyLen = sizeof(qy); + + ret = wc_ecc_export_private_only(refKey, d, &dLen); + if (ret == 0) { + ecc_key otherKey[1]; + ret = wc_ecc_init_ex(otherKey, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_ecc_make_key_ex(rng, TEST_ECC_KEYSIZE, otherKey, + TEST_ECC_CURVE_ID); + if (ret == 0) { + ret = wc_ecc_export_public_raw(otherKey, qx, &qxLen, qy, + &qyLen); + } + wc_ecc_free(otherKey); + } + } + if (ret == 0) { + ecc_key poisoned[1]; + ret = wc_ecc_init_ex(poisoned, NULL, INVALID_DEVID); + if (ret == 0) { + /* refKey's private scalar paired with a foreign public point */ + ret = wc_ecc_import_unsigned(poisoned, qx, qy, d, + TEST_ECC_CURVE_ID); + if (ret == 0) { + ret = wh_Client_EccImportKey( + ctx, poisoned, &poisonedId, WH_NVM_FLAGS_USAGE_ANY, + sizeof(poisonedLabel), poisonedLabel); + } + wc_ecc_free(poisoned); + } + } + if (ret == 0) { + ecc_key hsmKey[1]; + ecc_point* derived = wc_ecc_new_point(); + + if (derived == NULL) { + ret = MEMORY_E; + } + else { + ret = wc_ecc_init_ex(hsmKey, NULL, devId); + if (ret == 0) { + ret = wh_Client_EccSetKeyId(hsmKey, poisonedId); + if (ret == 0) { + ret = wc_ecc_set_curve(hsmKey, TEST_ECC_KEYSIZE, + TEST_ECC_CURVE_ID); + } + if (ret == 0) { + ret = wc_ecc_make_pub(hsmKey, derived); + if (ret != 0) { + WH_ERROR_PRINT( + "wc_ecc_make_pub on poisoned key failed %d\n", + ret); + } + else { + ret = whTest_EccPointMatchesKey(derived, refKey); + } + } + wc_ecc_free(hsmKey); + } + wc_ecc_del_point(derived); + } + } + if (!WH_KEYID_ISERASED(poisonedId)) { + (void)wh_Client_KeyEvict(ctx, poisonedId); + } + } + + /* Make-pub against a cached PUBLIC-only key must fail: there is no + * private scalar to derive from, matching software wc_ecc_make_pub(). */ + if (ret == 0) { + whKeyId pubOnlyId = WH_KEYID_ERASED; + uint8_t pubOnlyLabel[] = "EccMakePubPubOnly"; + uint8_t qx[ECC_MAXSIZE]; + word32 qxLen = sizeof(qx); + uint8_t qy[ECC_MAXSIZE]; + word32 qyLen = sizeof(qy); + + ret = wc_ecc_export_public_raw(refKey, qx, &qxLen, qy, &qyLen); + if (ret == 0) { + ecc_key pubOnly[1]; + ret = wc_ecc_init_ex(pubOnly, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_ecc_import_unsigned(pubOnly, qx, qy, NULL, + TEST_ECC_CURVE_ID); + if (ret == 0) { + ret = wh_Client_EccImportKey( + ctx, pubOnly, &pubOnlyId, WH_NVM_FLAGS_USAGE_ANY, + sizeof(pubOnlyLabel), pubOnlyLabel); + if (ret != 0) { + WH_ERROR_PRINT( + "Failed to cache public-only ECC key %d\n", ret); + } + } + wc_ecc_free(pubOnly); + } + } + if (ret == 0) { + ecc_key hsmKey[1]; + ecc_point* derived = wc_ecc_new_point(); + + if (derived == NULL) { + ret = MEMORY_E; + } + else { + ret = wc_ecc_init_ex(hsmKey, NULL, devId); + if (ret == 0) { + ret = wh_Client_EccSetKeyId(hsmKey, pubOnlyId); + if (ret == 0) { + ret = wc_ecc_set_curve(hsmKey, TEST_ECC_KEYSIZE, + TEST_ECC_CURVE_ID); + } + if (ret == 0) { + int rc = wc_ecc_make_pub(hsmKey, derived); + if (rc == 0) { + WH_ERROR_PRINT("wc_ecc_make_pub on public-only " + "key was not rejected\n"); + ret = -1; + } + } + wc_ecc_free(hsmKey); + } + wc_ecc_del_point(derived); + } + } + if (!WH_KEYID_ISERASED(pubOnlyId)) { + (void)wh_Client_KeyEvict(ctx, pubOnlyId); + } + } + + if (!WH_KEYID_ISERASED(keyId)) { + (void)wh_Client_KeyEvict(ctx, keyId); + } + if (pub != NULL) { + wc_ecc_del_point(pub); + } + wc_ecc_free(privOnlyKey); + wc_ecc_free(refKey); + (void)wc_FreeRng(rng); + + if (ret == 0) { + WH_TEST_PRINT("ECC MAKE-PUB SUCCESS\n"); + } + return ret; +} + +#ifdef HAVE_ECC_CHECK_KEY +/* Exercise WC_PK_TYPE_EC_CHECK_PUB_KEY: a private-only key, a full keypair, and + * a keypair whose public point does not belong to its private scalar. */ +static int _whTest_CryptoEccCheckPubKey(whClientContext* ctx) +{ + int devId = WH_CLIENT_DEVID(ctx); + int ret; + WC_RNG rng[1]; + ecc_key refKey[1]; + ecc_key privOnlyKey[1]; + whKeyId keyId = WH_KEYID_ERASED; + uint8_t keyLabel[] = "EccCheckPub"; + uint8_t d[ECC_MAXSIZE]; + word32 dLen = sizeof(d); + uint8_t qx[ECC_MAXSIZE]; + word32 qxLen = sizeof(qx); + uint8_t qy[ECC_MAXSIZE]; + word32 qyLen = sizeof(qy); + + ret = wc_InitRng_ex(rng, NULL, devId); + if (ret != 0) { + WH_ERROR_PRINT("Failed to wc_InitRng_ex %d\n", ret); + return ret; + } + + ret = whTest_MakePrivOnlyEccKey(devId, rng, refKey, privOnlyKey); + if (ret != 0) { + (void)wc_FreeRng(rng); + return ret; + } + + /* A private-only key carries no public point, so only the server can + * validate it. */ + ret = wc_ecc_check_key(privOnlyKey); + if (ret != 0) { + WH_ERROR_PRINT("Check of private-only ECC key failed %d\n", ret); + } + + /* A full keypair validates, and its public point survives the server-side + * cross-check against the private scalar. */ + if (ret == 0) { + ret = wc_ecc_export_private_only(refKey, d, &dLen); + } + if (ret == 0) { + ret = wc_ecc_export_public_raw(refKey, qx, &qxLen, qy, &qyLen); + } + if (ret == 0) { + ecc_key fullKey[1]; + byte pub963[1 + 2 * TEST_ECC_KEYSIZE]; + word32 pub963Len = sizeof(pub963); + ret = wc_ecc_init_ex(fullKey, NULL, devId); + if (ret == 0) { + ret = wc_ecc_import_unsigned(fullKey, qx, qy, d, TEST_ECC_CURVE_ID); + if (ret == 0) { + ret = wc_ecc_check_key(fullKey); + if (ret != 0) { + WH_ERROR_PRINT("Check of full ECC keypair failed %d\n", + ret); + } + } + /* The partial-validation shape (check_order == 0) produced by + * wolfCrypt's import paths must offload and validate end-to-end: + * a callback-only client (WOLF_CRYPTO_CB_ONLY_ECC) has no + * software fallback for it. */ + if (ret == 0) { + ret = wc_ecc_export_x963(fullKey, pub963, &pub963Len); + } + if (ret == 0) { + ret = wh_Client_EccCheckPubKey(ctx, fullKey, pub963, + (uint16_t)pub963Len, 0, 1); + if (ret != 0) { + WH_ERROR_PRINT( + "Partial-shape ECC check (check_order=0) failed %d\n", + ret); + } + } + wc_ecc_free(fullKey); + } + } + + /* A public point that is valid in its own right, but belongs to a + * different private scalar than the one the server holds, must be + * rejected. */ + if (ret == 0) { + ret = wh_Client_EccImportKey(ctx, privOnlyKey, &keyId, + WH_NVM_FLAGS_USAGE_ANY, sizeof(keyLabel), + keyLabel); + if (ret != 0) { + WH_ERROR_PRINT("Failed to cache private-only ECC key %d\n", ret); + } + } + if (ret == 0) { + ecc_key otherKey[1]; + ret = wc_ecc_init_ex(otherKey, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_ecc_make_key_ex(rng, TEST_ECC_KEYSIZE, otherKey, + TEST_ECC_CURVE_ID); + if (ret == 0) { + qxLen = sizeof(qx); + qyLen = sizeof(qy); + ret = + wc_ecc_export_public_raw(otherKey, qx, &qxLen, qy, &qyLen); + } + wc_ecc_free(otherKey); + } + } + if (ret == 0) { + ecc_key mismatched[1]; + ret = wc_ecc_init_ex(mismatched, NULL, devId); + if (ret == 0) { + /* Correct private scalar, wrong public point */ + ret = wc_ecc_import_unsigned(mismatched, qx, qy, d, + TEST_ECC_CURVE_ID); + if (ret == 0) { + ret = wh_Client_EccSetKeyId(mismatched, keyId); + } + if (ret == 0) { + /* Must match software wc_ecc_check_key(), which returns + * ECC_PRIV_KEY_E when the public point does not belong to + * the private scalar. */ + int checkRet = wc_ecc_check_key(mismatched); + if (checkRet != ECC_PRIV_KEY_E) { + WH_ERROR_PRINT("Mismatched ECC public point check " + "returned %d (want ECC_PRIV_KEY_E)\n", + checkRet); + ret = -1; + } + else { + ret = 0; + } + } + wc_ecc_free(mismatched); + } + } + + /* A structurally invalid point - one that is not on the curve at all - + * must be rejected by the server's own point validation. Flipping a low + * bit of Y provably leaves the curve: for a given X only the two +/-Y + * solutions satisfy the curve equation. */ + if (ret == 0) { + qxLen = sizeof(qx); + qyLen = sizeof(qy); + ret = wc_ecc_export_public_raw(refKey, qx, &qxLen, qy, &qyLen); + } + if (ret == 0) { + ecc_key offCurve[1]; + ret = wc_ecc_init_ex(offCurve, NULL, devId); + if (ret == 0) { + /* Correct private scalar, off-curve public point */ + qy[qyLen - 1] ^= 0x01; + ret = wc_ecc_import_unsigned(offCurve, qx, qy, d, + TEST_ECC_CURVE_ID); + if (ret == 0) { + int checkRet = wc_ecc_check_key(offCurve); + if (checkRet == 0) { + WH_ERROR_PRINT( + "Off-curve ECC public point was not rejected\n"); + ret = -1; + } + } + wc_ecc_free(offCurve); + } + } + + /* A non-NULL pub_key length paired with a NULL pointer is a client-side + * argument error, rejected before any transport activity. */ + if (ret == 0) { + int badret = wh_Client_EccCheckPubKey(ctx, refKey, NULL, 1, 0, 0); + if (badret != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("EccCheckPubKey with NULL pub_key and nonzero " + "pub_key_len returned %d (want BADARGS)\n", + badret); + ret = -1; + } + } + + if (!WH_KEYID_ISERASED(keyId)) { + (void)wh_Client_KeyEvict(ctx, keyId); + } + wc_ecc_free(privOnlyKey); + wc_ecc_free(refKey); + (void)wc_FreeRng(rng); + + if (ret == 0) { + WH_TEST_PRINT("ECC CHECK-PUBKEY SUCCESS\n"); + } + return ret; +} +#endif /* HAVE_ECC_CHECK_KEY */ + +#endif /* !WOLF_CRYPTO_CB_ONLY_ECC */ + +#if !defined(WOLF_CRYPTO_CB_ONLY_ECC) + /* Curve sizes used by the cross-verify and async test families. */ #define WH_TEST_ECC_P256_KEY_SIZE 32 #define WH_TEST_ECC_P384_KEY_SIZE 48 @@ -1845,9 +2389,13 @@ int whTest_Crypto_Ecc(whClientContext* ctx) WH_TEST_RETURN_ON_FAIL(_whTest_CryptoEccExportPublicKey(ctx)); WH_TEST_RETURN_ON_FAIL(_whTest_CryptoEccCacheKeyAndExportPublic(ctx)); #if !defined(WOLF_CRYPTO_CB_ONLY_ECC) + WH_TEST_RETURN_ON_FAIL(_whTest_CryptoEccMakePub(ctx)); +#ifdef HAVE_ECC_CHECK_KEY + WH_TEST_RETURN_ON_FAIL(_whTest_CryptoEccCheckPubKey(ctx)); +#endif /* HAVE_ECC_CHECK_KEY */ WH_TEST_RETURN_ON_FAIL(_whTest_CryptoEccCrossVerify(ctx)); WH_TEST_RETURN_ON_FAIL(_whTest_CryptoEccAsync(ctx)); -#endif +#endif /* !WOLF_CRYPTO_CB_ONLY_ECC */ #ifdef WOLFHSM_CFG_DMA WH_TEST_RETURN_ON_FAIL(_whTest_CryptoEccExportPublicKeyDma(ctx)); #endif diff --git a/test-refactor/misc/wh_test_check_struct_padding.c b/test-refactor/misc/wh_test_check_struct_padding.c index a4b5a3aa8..983b5a087 100644 --- a/test-refactor/misc/wh_test_check_struct_padding.c +++ b/test-refactor/misc/wh_test_check_struct_padding.c @@ -105,6 +105,7 @@ whMessageCrypto_EcdhRequest pkEcdhReq; whMessageCrypto_Curve25519Request pkCurve25519Req; whMessageCrypto_EccSignRequest pkEccSignReq; whMessageCrypto_EccVerifyRequest pkEccVerifyReq; +whMessageCrypto_EccMakePubRequest pkEccMakePubReq; whMessageCrypto_EccCheckRequest pkEccCheckReq; whMessageCrypto_RngRequest rngReq; whMessageCrypto_CmacAesRequest cmacReq; @@ -118,6 +119,7 @@ whMessageCrypto_EcdhResponse pkEcdhRes; whMessageCrypto_Curve25519Response pkCurve25519Res; whMessageCrypto_EccSignResponse pkEccSignRes; whMessageCrypto_EccVerifyResponse pkEccVerifyRes; +whMessageCrypto_EccMakePubResponse pkEccMakePubRes; whMessageCrypto_EccCheckResponse pkEccCheckRes; whMessageCrypto_RngResponse rngRes; whMessageCrypto_CmacAesResponse cmacRes; diff --git a/test/wh_test_check_struct_padding.c b/test/wh_test_check_struct_padding.c index 0965c51e3..bf29822a6 100644 --- a/test/wh_test_check_struct_padding.c +++ b/test/wh_test_check_struct_padding.c @@ -105,6 +105,7 @@ whMessageCrypto_EcdhRequest pkEcdhReq; whMessageCrypto_Curve25519Request pkCurve25519Req; whMessageCrypto_EccSignRequest pkEccSignReq; whMessageCrypto_EccVerifyRequest pkEccVerifyReq; +whMessageCrypto_EccMakePubRequest pkEccMakePubReq; whMessageCrypto_EccCheckRequest pkEccCheckReq; whMessageCrypto_RngRequest rngReq; whMessageCrypto_CmacAesRequest cmacReq; @@ -118,6 +119,7 @@ whMessageCrypto_EcdhResponse pkEcdhRes; whMessageCrypto_Curve25519Response pkCurve25519Res; whMessageCrypto_EccSignResponse pkEccSignRes; whMessageCrypto_EccVerifyResponse pkEccVerifyRes; +whMessageCrypto_EccMakePubResponse pkEccMakePubRes; whMessageCrypto_EccCheckResponse pkEccCheckRes; whMessageCrypto_RngResponse rngRes; whMessageCrypto_CmacAesResponse cmacRes; diff --git a/test/wh_test_crypto.c b/test/wh_test_crypto.c index 02f7bd814..8ee35737b 100644 --- a/test/wh_test_crypto.c +++ b/test/wh_test_crypto.c @@ -1679,6 +1679,529 @@ static int whTest_CryptoEcc(whClientContext* ctx, int devId, WC_RNG* rng) return ret; } +#if !defined(WOLF_CRYPTO_CB_ONLY_ECC) + +/* Build a private-key-only ECC key bound to devId, along with a locally + * generated reference keypair holding the public point it must derive to. + * This is the shape that cannot be resolved client-side: the caller holds a + * private scalar and no public point. */ +static int whTest_MakePrivOnlyEccKey(int devId, WC_RNG* rng, ecc_key* refKey, + ecc_key* privOnlyKey) +{ + int ret; + uint8_t d[ECC_MAXSIZE]; + word32 dLen = sizeof(d); + + ret = wc_ecc_init_ex(refKey, NULL, INVALID_DEVID); + if (ret != 0) { + WH_ERROR_PRINT("Failed to init reference ECC key %d\n", ret); + return ret; + } + + ret = wc_ecc_make_key_ex(rng, TEST_ECC_KEYSIZE, refKey, TEST_ECC_CURVE_ID); + if (ret == 0) { + ret = wc_ecc_export_private_only(refKey, d, &dLen); + } + if (ret == 0) { + ret = wc_ecc_init_ex(privOnlyKey, NULL, devId); + if (ret == 0) { + ret = wc_ecc_import_private_key_ex(d, dLen, NULL, 0, privOnlyKey, + TEST_ECC_CURVE_ID); + if (ret != 0) { + wc_ecc_free(privOnlyKey); + } + } + } + if (ret != 0) { + wc_ecc_free(refKey); + WH_ERROR_PRINT("Failed to build private-only ECC key %d\n", ret); + } + return ret; +} + +/* Compare an ecc_point against the public point of key */ +static int whTest_EccPointMatchesKey(ecc_point* point, ecc_key* key) +{ + if (wc_ecc_cmp_point(point, &key->pubkey) != MP_EQ) { + WH_ERROR_PRINT("Derived ECC public point does not match\n"); + return -1; + } + return 0; +} + +/* Exercise WC_PK_TYPE_EC_MAKE_PUB, both for a key the client holds and for one + * resident on the server. Also covers exporting the public half of a resident + * private-only key, which requires the server to derive it. */ +static int whTest_CryptoEccMakePub(whClientContext* ctx, int devId, WC_RNG* rng) +{ + int ret; + ecc_key refKey[1]; + ecc_key privOnlyKey[1]; + ecc_point* pub = NULL; + whKeyId keyId = WH_KEYID_ERASED; + uint8_t keyLabel[] = "EccMakePub"; + + ret = whTest_MakePrivOnlyEccKey(devId, rng, refKey, privOnlyKey); + if (ret != 0) { + return ret; + } + + pub = wc_ecc_new_point(); + if (pub == NULL) { + ret = MEMORY_E; + } + + /* Client-held private-only key: the cryptoCb imports it, derives on the + * server, and evicts it again. */ + if (ret == 0) { + ret = wc_ecc_make_pub(privOnlyKey, pub); + if (ret != 0) { + WH_ERROR_PRINT("wc_ecc_make_pub on client key failed %d\n", ret); + } + else { + ret = whTest_EccPointMatchesKey(pub, refKey); + } + } + + /* Direct API with an undersized output buffer must report the required + * X9.63 size (0x04 || X || Y) in *inout_pubOutSz and return + * WH_ERROR_BUFFER_SIZE, after which a re-call with the reported size + * succeeds. */ + if (ret == 0) { + uint8_t smallPub[4] = {0}; + uint8_t pubOut[1 + 2 * TEST_ECC_KEYSIZE] = {0}; + uint16_t pubOutSz = sizeof(smallPub); + int rc; + + rc = wh_Client_EccMakePub(ctx, privOnlyKey, smallPub, &pubOutSz); + if (rc != WH_ERROR_BUFFER_SIZE) { + WH_ERROR_PRINT("Undersized EccMakePub returned %d " + "(want WH_ERROR_BUFFER_SIZE)\n", + rc); + ret = -1; + } + else if (pubOutSz != (1 + 2 * TEST_ECC_KEYSIZE)) { + WH_ERROR_PRINT("Undersized EccMakePub required size %u " + "(want %u)\n", + (unsigned)pubOutSz, + (unsigned)(1 + 2 * TEST_ECC_KEYSIZE)); + ret = -1; + } + if (ret == 0) { + rc = wh_Client_EccMakePub(ctx, privOnlyKey, pubOut, &pubOutSz); + if (rc != 0) { + WH_ERROR_PRINT( + "EccMakePub re-call with reported size failed %d\n", rc); + ret = rc; + } + else if (pubOutSz != (1 + 2 * TEST_ECC_KEYSIZE) || + pubOut[0] != 0x04) { + WH_ERROR_PRINT("EccMakePub re-call produced bad point " + "(size=%u first=0x%02x)\n", + (unsigned)pubOutSz, pubOut[0]); + ret = -1; + } + } + } + + /* Same derivation against a key that lives on the server */ + if (ret == 0) { + ret = wh_Client_EccImportKey(ctx, privOnlyKey, &keyId, + WH_NVM_FLAGS_USAGE_ANY, sizeof(keyLabel), + keyLabel); + if (ret != 0) { + WH_ERROR_PRINT("Failed to cache private-only ECC key %d\n", ret); + } + } + if (ret == 0) { + ecc_key hsmKey[1]; + ecc_point* cachedPub = wc_ecc_new_point(); + + if (cachedPub == NULL) { + ret = MEMORY_E; + } + else { + ret = wc_ecc_init_ex(hsmKey, NULL, devId); + if (ret == 0) { + ret = wh_Client_EccSetKeyId(hsmKey, keyId); + if (ret == 0) { + /* Curve parameters aren't carried by a cached key handle */ + ret = wc_ecc_set_curve(hsmKey, TEST_ECC_KEYSIZE, + TEST_ECC_CURVE_ID); + } + if (ret == 0) { + ret = wc_ecc_make_pub(hsmKey, cachedPub); + if (ret != 0) { + WH_ERROR_PRINT( + "wc_ecc_make_pub on cached key failed %d\n", ret); + } + else { + ret = whTest_EccPointMatchesKey(cachedPub, refKey); + } + } + wc_ecc_free(hsmKey); + } + wc_ecc_del_point(cachedPub); + } + } + + /* Exporting the public half of a resident private-only key requires the + * server to derive it first. */ + if (ret == 0) { + ecc_key pubKey[1]; + ret = wc_ecc_init_ex(pubKey, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wh_Client_EccExportPublicKey(ctx, keyId, pubKey, 0, NULL); + if (ret != 0) { + WH_ERROR_PRINT( + "Public export of private-only ECC key failed %d\n", ret); + } + else if (wc_ecc_cmp_point(&pubKey->pubkey, &refKey->pubkey) != + MP_EQ) { + WH_ERROR_PRINT("Exported ECC public key does not match\n"); + ret = -1; + } + wc_ecc_free(pubKey); + } + } + + /* A cached keypair whose stored public point does not belong to its private + * scalar must still derive the true point, not hand back the stored one. */ + if (ret == 0) { + whKeyId poisonedId = WH_KEYID_ERASED; + uint8_t poisonedLabel[] = "EccMakePubPoison"; + uint8_t d[ECC_MAXSIZE]; + word32 dLen = sizeof(d); + uint8_t qx[ECC_MAXSIZE]; + word32 qxLen = sizeof(qx); + uint8_t qy[ECC_MAXSIZE]; + word32 qyLen = sizeof(qy); + + ret = wc_ecc_export_private_only(refKey, d, &dLen); + if (ret == 0) { + ecc_key otherKey[1]; + ret = wc_ecc_init_ex(otherKey, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_ecc_make_key_ex(rng, TEST_ECC_KEYSIZE, otherKey, + TEST_ECC_CURVE_ID); + if (ret == 0) { + ret = wc_ecc_export_public_raw(otherKey, qx, &qxLen, qy, + &qyLen); + } + wc_ecc_free(otherKey); + } + } + if (ret == 0) { + ecc_key poisoned[1]; + ret = wc_ecc_init_ex(poisoned, NULL, INVALID_DEVID); + if (ret == 0) { + /* refKey's private scalar paired with a foreign public point */ + ret = wc_ecc_import_unsigned(poisoned, qx, qy, d, + TEST_ECC_CURVE_ID); + if (ret == 0) { + ret = wh_Client_EccImportKey( + ctx, poisoned, &poisonedId, WH_NVM_FLAGS_USAGE_ANY, + sizeof(poisonedLabel), poisonedLabel); + } + wc_ecc_free(poisoned); + } + } + if (ret == 0) { + ecc_key hsmKey[1]; + ecc_point* derived = wc_ecc_new_point(); + + if (derived == NULL) { + ret = MEMORY_E; + } + else { + ret = wc_ecc_init_ex(hsmKey, NULL, devId); + if (ret == 0) { + ret = wh_Client_EccSetKeyId(hsmKey, poisonedId); + if (ret == 0) { + ret = wc_ecc_set_curve(hsmKey, TEST_ECC_KEYSIZE, + TEST_ECC_CURVE_ID); + } + if (ret == 0) { + ret = wc_ecc_make_pub(hsmKey, derived); + if (ret != 0) { + WH_ERROR_PRINT( + "wc_ecc_make_pub on poisoned key failed %d\n", + ret); + } + else { + ret = whTest_EccPointMatchesKey(derived, refKey); + } + } + wc_ecc_free(hsmKey); + } + wc_ecc_del_point(derived); + } + } + if (!WH_KEYID_ISERASED(poisonedId)) { + (void)wh_Client_KeyEvict(ctx, poisonedId); + } + } + + /* Make-pub against a cached PUBLIC-only key must fail: there is no + * private scalar to derive from, matching software wc_ecc_make_pub(). */ + if (ret == 0) { + whKeyId pubOnlyId = WH_KEYID_ERASED; + uint8_t pubOnlyLabel[] = "EccMakePubPubOnly"; + uint8_t qx[ECC_MAXSIZE]; + word32 qxLen = sizeof(qx); + uint8_t qy[ECC_MAXSIZE]; + word32 qyLen = sizeof(qy); + + ret = wc_ecc_export_public_raw(refKey, qx, &qxLen, qy, &qyLen); + if (ret == 0) { + ecc_key pubOnly[1]; + ret = wc_ecc_init_ex(pubOnly, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_ecc_import_unsigned(pubOnly, qx, qy, NULL, + TEST_ECC_CURVE_ID); + if (ret == 0) { + ret = wh_Client_EccImportKey( + ctx, pubOnly, &pubOnlyId, WH_NVM_FLAGS_USAGE_ANY, + sizeof(pubOnlyLabel), pubOnlyLabel); + if (ret != 0) { + WH_ERROR_PRINT( + "Failed to cache public-only ECC key %d\n", ret); + } + } + wc_ecc_free(pubOnly); + } + } + if (ret == 0) { + ecc_key hsmKey[1]; + ecc_point* derived = wc_ecc_new_point(); + + if (derived == NULL) { + ret = MEMORY_E; + } + else { + ret = wc_ecc_init_ex(hsmKey, NULL, devId); + if (ret == 0) { + ret = wh_Client_EccSetKeyId(hsmKey, pubOnlyId); + if (ret == 0) { + ret = wc_ecc_set_curve(hsmKey, TEST_ECC_KEYSIZE, + TEST_ECC_CURVE_ID); + } + if (ret == 0) { + int rc = wc_ecc_make_pub(hsmKey, derived); + if (rc == 0) { + WH_ERROR_PRINT("wc_ecc_make_pub on public-only " + "key was not rejected\n"); + ret = -1; + } + } + wc_ecc_free(hsmKey); + } + wc_ecc_del_point(derived); + } + } + if (!WH_KEYID_ISERASED(pubOnlyId)) { + (void)wh_Client_KeyEvict(ctx, pubOnlyId); + } + } + + if (!WH_KEYID_ISERASED(keyId)) { + (void)wh_Client_KeyEvict(ctx, keyId); + } + if (pub != NULL) { + wc_ecc_del_point(pub); + } + wc_ecc_free(privOnlyKey); + wc_ecc_free(refKey); + + if (ret == 0) { + WH_TEST_PRINT("ECC MAKE-PUB SUCCESS\n"); + } + return ret; +} + +#ifdef HAVE_ECC_CHECK_KEY +/* Exercise WC_PK_TYPE_EC_CHECK_PUB_KEY: a private-only key, a full keypair, and + * a keypair whose public point does not belong to its private scalar. */ +static int whTest_CryptoEccCheckPubKey(whClientContext* ctx, int devId, + WC_RNG* rng) +{ + int ret; + ecc_key refKey[1]; + ecc_key privOnlyKey[1]; + whKeyId keyId = WH_KEYID_ERASED; + uint8_t keyLabel[] = "EccCheckPub"; + uint8_t d[ECC_MAXSIZE]; + word32 dLen = sizeof(d); + uint8_t qx[ECC_MAXSIZE]; + word32 qxLen = sizeof(qx); + uint8_t qy[ECC_MAXSIZE]; + word32 qyLen = sizeof(qy); + + ret = whTest_MakePrivOnlyEccKey(devId, rng, refKey, privOnlyKey); + if (ret != 0) { + return ret; + } + + /* A private-only key carries no public point, so only the server can + * validate it. */ + ret = wc_ecc_check_key(privOnlyKey); + if (ret != 0) { + WH_ERROR_PRINT("Check of private-only ECC key failed %d\n", ret); + } + + /* A full keypair validates, and its public point survives the server-side + * cross-check against the private scalar. */ + if (ret == 0) { + ret = wc_ecc_export_private_only(refKey, d, &dLen); + } + if (ret == 0) { + ret = wc_ecc_export_public_raw(refKey, qx, &qxLen, qy, &qyLen); + } + if (ret == 0) { + ecc_key fullKey[1]; + byte pub963[1 + 2 * TEST_ECC_KEYSIZE]; + word32 pub963Len = sizeof(pub963); + ret = wc_ecc_init_ex(fullKey, NULL, devId); + if (ret == 0) { + ret = wc_ecc_import_unsigned(fullKey, qx, qy, d, TEST_ECC_CURVE_ID); + if (ret == 0) { + ret = wc_ecc_check_key(fullKey); + if (ret != 0) { + WH_ERROR_PRINT("Check of full ECC keypair failed %d\n", + ret); + } + } + /* The partial-validation shape (check_order == 0) produced by + * wolfCrypt's import paths must offload and validate end-to-end: + * a callback-only client (WOLF_CRYPTO_CB_ONLY_ECC) has no + * software fallback for it. */ + if (ret == 0) { + ret = wc_ecc_export_x963(fullKey, pub963, &pub963Len); + } + if (ret == 0) { + ret = wh_Client_EccCheckPubKey(ctx, fullKey, pub963, + (uint16_t)pub963Len, 0, 1); + if (ret != 0) { + WH_ERROR_PRINT( + "Partial-shape ECC check (check_order=0) failed %d\n", + ret); + } + } + wc_ecc_free(fullKey); + } + } + + /* A public point that is valid in its own right, but belongs to a + * different private scalar than the one the server holds, must be + * rejected. */ + if (ret == 0) { + ret = wh_Client_EccImportKey(ctx, privOnlyKey, &keyId, + WH_NVM_FLAGS_USAGE_ANY, sizeof(keyLabel), + keyLabel); + if (ret != 0) { + WH_ERROR_PRINT("Failed to cache private-only ECC key %d\n", ret); + } + } + if (ret == 0) { + ecc_key otherKey[1]; + ret = wc_ecc_init_ex(otherKey, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_ecc_make_key_ex(rng, TEST_ECC_KEYSIZE, otherKey, + TEST_ECC_CURVE_ID); + if (ret == 0) { + qxLen = sizeof(qx); + qyLen = sizeof(qy); + ret = + wc_ecc_export_public_raw(otherKey, qx, &qxLen, qy, &qyLen); + } + wc_ecc_free(otherKey); + } + } + if (ret == 0) { + ecc_key mismatched[1]; + ret = wc_ecc_init_ex(mismatched, NULL, devId); + if (ret == 0) { + /* Correct private scalar, wrong public point */ + ret = wc_ecc_import_unsigned(mismatched, qx, qy, d, + TEST_ECC_CURVE_ID); + if (ret == 0) { + ret = wh_Client_EccSetKeyId(mismatched, keyId); + } + if (ret == 0) { + /* Must match software wc_ecc_check_key(), which returns + * ECC_PRIV_KEY_E when the public point does not belong to + * the private scalar. */ + int checkRet = wc_ecc_check_key(mismatched); + if (checkRet != ECC_PRIV_KEY_E) { + WH_ERROR_PRINT("Mismatched ECC public point check " + "returned %d (want ECC_PRIV_KEY_E)\n", + checkRet); + ret = -1; + } + else { + ret = 0; + } + } + wc_ecc_free(mismatched); + } + } + + /* A structurally invalid point - one that is not on the curve at all - + * must be rejected by the server's own point validation. Flipping a low + * bit of Y provably leaves the curve: for a given X only the two +/-Y + * solutions satisfy the curve equation. */ + if (ret == 0) { + qxLen = sizeof(qx); + qyLen = sizeof(qy); + ret = wc_ecc_export_public_raw(refKey, qx, &qxLen, qy, &qyLen); + } + if (ret == 0) { + ecc_key offCurve[1]; + ret = wc_ecc_init_ex(offCurve, NULL, devId); + if (ret == 0) { + /* Correct private scalar, off-curve public point */ + qy[qyLen - 1] ^= 0x01; + ret = wc_ecc_import_unsigned(offCurve, qx, qy, d, + TEST_ECC_CURVE_ID); + if (ret == 0) { + int checkRet = wc_ecc_check_key(offCurve); + if (checkRet == 0) { + WH_ERROR_PRINT( + "Off-curve ECC public point was not rejected\n"); + ret = -1; + } + } + wc_ecc_free(offCurve); + } + } + + /* A non-NULL pub_key length paired with a NULL pointer is a client-side + * argument error, rejected before any transport activity. */ + if (ret == 0) { + int badret = wh_Client_EccCheckPubKey(ctx, refKey, NULL, 1, 0, 0); + if (badret != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("EccCheckPubKey with NULL pub_key and nonzero " + "pub_key_len returned %d (want BADARGS)\n", + badret); + ret = -1; + } + } + + if (!WH_KEYID_ISERASED(keyId)) { + (void)wh_Client_KeyEvict(ctx, keyId); + } + wc_ecc_free(privOnlyKey); + wc_ecc_free(refKey); + + if (ret == 0) { + WH_TEST_PRINT("ECC CHECK-PUBKEY SUCCESS\n"); + } + return ret; +} +#endif /* HAVE_ECC_CHECK_KEY */ + +#endif /* !WOLF_CRYPTO_CB_ONLY_ECC */ + #ifdef WOLFHSM_CFG_DMA /* Generic-transport DMA smoke test: cache an ECC keypair, export the * public half via wh_Client_KeyExportPublicDma (no per-algo wrapper), @@ -1701,7 +2224,6 @@ static int whTest_CryptoEccExportPublicDma(whClientContext* ctx, int devId, byte derBuf[ECC_BUFSIZE]; uint16_t derSz = sizeof(derBuf); word32 i; - (void)devId; for (i = 0; i < sizeof(hash); i++) { hash[i] = (uint8_t)(i + 1); @@ -1769,6 +2291,65 @@ static int whTest_CryptoEccExportPublicDma(whClientContext* ctx, int devId, wc_ecc_free(pubKey); } +#if !defined(WOLF_CRYPTO_CB_ONLY_ECC) + /* A resident PRIVATE-only key carries no public half, so the DMA public + * export must derive it on the server first. Mirrors the non-DMA + * private-only export covered by whTest_CryptoEccMakePub. Skipped in + * CB_ONLY builds along with the software reference-key helper. */ + if (ret == 0) { + ecc_key refKey[1]; + ecc_key privOnlyKey[1]; + whKeyId privKeyId = WH_KEYID_ERASED; + uint8_t privLabel[] = "EccExportPubDmaPriv"; + + ret = whTest_MakePrivOnlyEccKey(devId, rng, refKey, privOnlyKey); + if (ret == 0) { + ret = wh_Client_EccImportKey(ctx, privOnlyKey, &privKeyId, + WH_NVM_FLAGS_USAGE_ANY, + sizeof(privLabel), privLabel); + if (ret != 0) { + WH_ERROR_PRINT( + "Failed to cache private-only ECC key (DMA) %d\n", ret); + } + if (ret == 0) { + derSz = sizeof(derBuf); + ret = wh_Client_KeyExportPublicDma(ctx, privKeyId, + WH_KEY_ALGO_ECC, derBuf, + derSz, NULL, 0, &derSz); + if (ret != 0) { + WH_ERROR_PRINT("wh_Client_KeyExportPublicDma(ECC " + "private-only) failed %d\n", + ret); + } + } + if (ret == 0) { + ecc_key derivedPub[1]; + ret = wc_ecc_init_ex(derivedPub, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wh_Crypto_EccDeserializeKeyDer(derBuf, derSz, + derivedPub); + if (ret == 0 && + wc_ecc_cmp_point(&derivedPub->pubkey, + &refKey->pubkey) != MP_EQ) { + WH_ERROR_PRINT( + "Derived ECC public key (DMA) does not match\n"); + ret = -1; + } + wc_ecc_free(derivedPub); + } + } + if (!WH_KEYID_ISERASED(privKeyId)) { + (void)wh_Client_KeyEvict(ctx, privKeyId); + } + wc_ecc_free(privOnlyKey); + wc_ecc_free(refKey); + } + } +#else + /* devId is only consumed by the private-only sub-test above */ + (void)devId; +#endif /* !WOLF_CRYPTO_CB_ONLY_ECC */ + if (!WH_KEYID_ISERASED(keyId)) { (void)wh_Client_KeyEvict(ctx, keyId); } @@ -17279,6 +17860,16 @@ int whTest_CryptoClientConfig(whClientConfig* config) if (ret == 0) { ret = whTest_CryptoEccCacheDuplicate(client); } +#if !defined(WOLF_CRYPTO_CB_ONLY_ECC) + if (ret == 0) { + ret = whTest_CryptoEccMakePub(client, WH_CLIENT_DEVID(client), rng); + } +#ifdef HAVE_ECC_CHECK_KEY + if (ret == 0) { + ret = whTest_CryptoEccCheckPubKey(client, WH_CLIENT_DEVID(client), rng); + } +#endif /* HAVE_ECC_CHECK_KEY */ +#endif /* !WOLF_CRYPTO_CB_ONLY_ECC */ #if defined(HAVE_ECC_SIGN) && defined(HAVE_ECC_VERIFY) && \ !defined(WOLF_CRYPTO_CB_ONLY_ECC) if (ret == 0) { diff --git a/wolfhsm/wh_client_crypto.h b/wolfhsm/wh_client_crypto.h index f213f5e8d..9a0e921c6 100644 --- a/wolfhsm/wh_client_crypto.h +++ b/wolfhsm/wh_client_crypto.h @@ -680,6 +680,88 @@ int wh_Client_EccVerify(whClientContext* ctx, ecc_key* key, const uint8_t* hash, uint16_t hash_len, int *out_res); +/** + * @brief Derive the public key of an ECC private key on the server. + * + * This function requests the server to compute Q = d*G for the specified ECC + * key and return the resulting point. The key context may either carry actual + * key material or refer to a server-cached key by keyId via its devCtx + * (associated by wh_Client_EccSetKeyId or returned from a server-side keygen). + * If the key does not reference a cached keyId, the client will temporarily + * import its material to the server for the duration of the operation and evict + * it afterwards. For a key resident on the server that holds no public half, + * this returns the derived point directly; wh_Client_EccExportPublicKey + * likewise derives the public half on demand and returns it DER-encoded. + * + * Unlike wh_Client_EccSign, there is no NULL-based size-query mode: pubOut must + * be non-NULL. A caller that passes a too-small buffer gets + * WH_ERROR_BUFFER_SIZE with the required size written to *inout_pubOutSz, and + * can then re-call with a large enough buffer. + * + * @param[in] ctx Pointer to the client context. + * @param[in] key Pointer to a wolfCrypt ECC key structure that either holds the + * private key material or references a server-cached private key + * via its devCtx (keyId). + * @param[out] pubOut Buffer to receive the public point in X9.63 uncompressed + * form (0x04 || X || Y). Must not be NULL. + * @param[in,out] inout_pubOutSz On input, the size of pubOut in bytes. On + * success, the number of bytes written. If pubOut + * is too small, this is set to the required size + * and WH_ERROR_BUFFER_SIZE is returned. + * @return int Returns 0 on success or a negative error code on failure. + */ +int wh_Client_EccMakePub(whClientContext* ctx, ecc_key* key, uint8_t* pubOut, + uint16_t* inout_pubOutSz); + +/** + * @brief Validate an ECC key on the server. + * + * This function requests the server to validate the specified ECC key. The key + * context may either carry actual key material or refer to a server-cached key + * by keyId via its devCtx (associated by wh_Client_EccSetKeyId or returned from + * a server-side keygen). If the key does not reference a cached keyId, the + * client will temporarily import its material to the server for the duration of + * the operation and evict it afterwards. + * + * When pub_key is supplied, the server additionally checks it against the point + * belonging to the key it holds, so that a caller-held public key that does not + * match a server-resident private key is rejected. + * + * Via the crypto callback (WC_PK_TYPE_EC_CHECK_PUB_KEY), this function is + * reachable not only from wc_ecc_check_key() but also from wolfCrypt's + * internal validation paths: post-keygen validation under + * WOLFSSL_VALIDATE_ECC_KEYGEN and import validation under + * WOLFSSL_VALIDATE_ECC_IMPORT. Every shape offloads to the server — the + * callback never returns CRYPTOCB_UNAVAILABLE for this op, so a + * callback-only client (WOLF_CRYPTO_CB_ONLY_ECC), which has no software to + * fall back to, keeps working. Note this means a validated import of a + * devId-bound key that is not yet resident temporarily caches the key on the + * server for the duration of the check. + * + * @param[in] ctx Pointer to the client context. + * @param[in] key Pointer to a wolfCrypt ECC key structure that either holds the + * key material or references a server-cached key via its devCtx + * (keyId). + * @param[in] pub_key Public point held by the caller in X9.63 uncompressed form + * (0x04 || X || Y), or NULL if the caller holds none. + * @param[in] pub_key_len Length of pub_key in bytes, or 0 when pub_key is NULL. + * @param[in] check_order Requests validation that the point has the order of + * the curve. Accepted for wolfCrypt cryptocb parity but + * not sent to the server: the server always performs + * the full validation, a superset of the partial check. + * @param[in] check_priv Requests validation of the private part of the key. + * Accepted for wolfCrypt cryptocb parity but not sent to + * the server: the server always performs the full + * validation, a superset of the partial check. + * @return int Returns 0 if the key is valid, or a negative error code if the + * key is invalid or the operation failed. A wolfHSM error code + * (transport or keystore failure) is likewise returned as a + * negative value, so the key is not validated in that case either. + */ +int wh_Client_EccCheckPubKey(whClientContext* ctx, ecc_key* key, + const uint8_t* pub_key, uint16_t pub_key_len, + int check_order, int check_priv); + /** * @brief Async request half of an ECC sign operation. * diff --git a/wolfhsm/wh_message_crypto.h b/wolfhsm/wh_message_crypto.h index f87cab564..809513bc9 100644 --- a/wolfhsm/wh_message_crypto.h +++ b/wolfhsm/wh_message_crypto.h @@ -588,10 +588,41 @@ int wh_MessageCrypto_TranslateEccVerifyResponse( uint16_t magic, const whMessageCrypto_EccVerifyResponse* src, whMessageCrypto_EccVerifyResponse* dest); +/* ECC Make Public Request */ +typedef struct { + uint32_t options; +#define WH_MESSAGE_CRYPTO_ECCMAKEPUB_OPTIONS_EVICT (1 << 0) + uint32_t keyId; +} whMessageCrypto_EccMakePubRequest; + +/* ECC Make Public Response */ +typedef struct { + uint32_t pubSz; + /* Data follows: + * uint8_t pub[pubSz]; X9.63 uncompressed point: 0x04 || X || Y + */ +} whMessageCrypto_EccMakePubResponse; + +int wh_MessageCrypto_TranslateEccMakePubRequest( + uint16_t magic, const whMessageCrypto_EccMakePubRequest* src, + whMessageCrypto_EccMakePubRequest* dest); + +int wh_MessageCrypto_TranslateEccMakePubResponse( + uint16_t magic, const whMessageCrypto_EccMakePubResponse* src, + whMessageCrypto_EccMakePubResponse* dest); + /* ECC Check Request */ typedef struct { + uint32_t options; +#define WH_MESSAGE_CRYPTO_ECCCHECK_OPTIONS_EVICT (1 << 0) uint32_t keyId; - uint32_t curveId; + uint32_t curveId; /* wolfCrypt curve id, carried as int32: + * ECC_CURVE_INVALID (-1) for a custom curve. The curve + * travels with the key, so the server ignores this */ + uint32_t pubSz; /* 0 when the caller holds no public point */ + /* Data follows: + * uint8_t pub[pubSz]; X9.63 uncompressed point: 0x04 || X || Y + */ } whMessageCrypto_EccCheckRequest; /* ECC Check Response */ diff --git a/wolfhsm/wh_settings.h b/wolfhsm/wh_settings.h index 1139fee9c..a0ff30999 100644 --- a/wolfhsm/wh_settings.h +++ b/wolfhsm/wh_settings.h @@ -440,6 +440,20 @@ #error "wolfHSM requires wolfCrypt built without NO_RNG" #endif +/* Client response parsing calls wc_ecc_import_* on devId-bound keys (e.g. + * wh_Crypto_EccUpdatePrivateOnlyKeyDer from wh_Client_EccVerifyResponse) + * while the response still sits in the client's comm packet buffer. With + * WOLFSSL_VALIDATE_ECC_IMPORT those imports re-enter the crypto callback and + * run a nested HSM transaction that reuses that buffer. The wire protocol + * tolerates nesting (transactions are strictly sequential), but correctness + * would rest on every response parser having copied out all data it needs + * before any import — an invariant that is not enforced or audited. Server + * builds are unaffected: their import validation dispatches to the server's + * local crypto provider, not the transport. */ +#if defined(WOLFHSM_CFG_ENABLE_CLIENT) && defined(WOLFSSL_VALIDATE_ECC_IMPORT) +#error "WOLFSSL_VALIDATE_ECC_IMPORT is not supported in wolfHSM client builds" +#endif + #if defined(WOLFHSM_CFG_SHE_EXTENSION) #if defined(NO_AES) || \ !defined(WOLFSSL_CMAC) || \