diff --git a/docs/src/5-Features.md b/docs/src/5-Features.md index 743a87c2f..15c0136ec 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 0b2585729..d5815a652 100644 --- a/src/wh_client_crypto.c +++ b/src/wh_client_crypto.c @@ -2790,57 +2790,282 @@ 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, 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_USAGE_ANY; + + 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, 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); + 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_USAGE_ANY; - req->type = WC_PK_TYPE_EC_CHECK_PRIV_KEY; - req->keyId = key_id; - req->curveId = curve_id; + 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; - /* Response packet */ - wh_Packet_pk_ecc_check_res* res = &packet->pkEccCheckRes; + /* 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); - /* 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, - (uint8_t*)packet); - } while (ret == WH_ERROR_NOTREADY); + 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->checkOrder = (uint32_t)(check_order != 0); + req->checkPriv = (uint32_t)(check_priv != 0); + 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 9da9f402e..1ee33b451 100644 --- a/src/wh_client_cryptocb.c +++ b/src/wh_client_cryptocb.c @@ -372,20 +372,54 @@ 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 > 0xFFFFU) ? 0xFFFFU + : (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; + uint16_t pub_key_len = (uint16_t)info->pk.ecc_check_pub.pubKeySz; + int check_order = info->pk.ecc_check_pub.checkOrder; + int check_priv = info->pk.ecc_check_pub.checkPriv; + + ret = wh_Client_EccCheckPubKey(ctx, key, pub_key, 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 205065c9a..d76d99043 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,12 @@ 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, checkOrder); + WH_T32(magic, dest, src, checkPriv); + WH_T32(magic, dest, src, pubSz); return 0; } diff --git a/src/wh_server_crypto.c b/src/wh_server_crypto.c index 5755676c3..c71a9b4e2 100644 --- a/src/wh_server_crypto.c +++ b/src/wh_server_crypto.c @@ -142,6 +142,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 @@ -1583,6 +1592,185 @@ 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. + * checkOrder and checkPriv are carried on the wire but clamped to a full + * wc_ecc_check_key() here rather than honored, because: + * - 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. It is also the only + * route to this server's own crypto callback (the key is bound to the + * server devId). */ + (void)req.curveId; + (void)req.checkOrder; + (void)req.checkPriv; + + /* Deliberately no wh_Server_KeystoreFindEnforceKeyUsage(): this operation + * only validates 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 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 */ @@ -5469,6 +5657,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 7c84e0a12..b1d7b6c75 100644 --- a/src/wh_server_keystore.c +++ b/src/wh_server_keystore.c @@ -440,7 +440,7 @@ static int _ExportRsaPublicKey(whServerContext* server, whKeyId keyId, RsaKey key[1]; int pub_ret; - ret = wc_InitRsaKey_ex(key, NULL, INVALID_DEVID); + ret = wc_InitRsaKey_ex(key, NULL, server->devId); if (ret == 0) { ret = wh_Server_CacheExportRsaKey(server, keyId, key); if (ret == 0) { @@ -467,9 +467,19 @@ static int _ExportEccPublicKey(whServerContext* server, whKeyId keyId, ecc_key key[1]; int pub_ret; - ret = wc_ecc_init_ex(key, NULL, INVALID_DEVID); + ret = wc_ecc_init_ex(key, NULL, server->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) { @@ -493,7 +503,7 @@ static int _ExportEd25519PublicKey(whServerContext* server, whKeyId keyId, ed25519_key key[1]; int pub_ret; - ret = wc_ed25519_init_ex(key, NULL, INVALID_DEVID); + ret = wc_ed25519_init_ex(key, NULL, server->devId); if (ret == 0) { ret = wh_Server_CacheExportEd25519Key(server, keyId, key); if (ret == 0) { @@ -519,7 +529,7 @@ static int _ExportMldsaPublicKey(whServerContext* server, whKeyId keyId, wc_MlDsaKey key[1]; int pub_ret; - ret = wc_MlDsaKey_Init(key, NULL, INVALID_DEVID); + ret = wc_MlDsaKey_Init(key, NULL, server->devId); if (ret == 0) { ret = wh_Server_MlDsaKeyCacheExport(server, keyId, key); if (ret == 0) { @@ -545,7 +555,7 @@ static int _ExportCurve25519PublicKey(whServerContext* server, whKeyId keyId, curve25519_key key[1]; int pub_ret; - ret = wc_curve25519_init_ex(key, NULL, INVALID_DEVID); + ret = wc_curve25519_init_ex(key, NULL, server->devId); if (ret == 0) { ret = wh_Server_CacheExportCurve25519Key(server, keyId, key); if (ret == 0) { @@ -581,7 +591,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, server->devId); if (ret == 0) { ret = wh_Server_MlKemKeyCacheExport(server, keyId, key); if (ret == 0) { @@ -614,7 +624,7 @@ static int _ExportLmsPublicKey(whServerContext* server, whKeyId keyId, LmsKey key[1]; word32 pubLen = 0; - ret = wc_LmsKey_Init(key, NULL, INVALID_DEVID); + ret = wc_LmsKey_Init(key, NULL, server->devId); if (ret == 0) { ret = wh_Server_LmsKeyCacheExport(server, keyId, key); if (ret == WH_ERROR_OK) { @@ -643,7 +653,7 @@ static int _ExportXmssPublicKey(whServerContext* server, whKeyId keyId, XmssKey key[1]; word32 pubLen = 0; - ret = wc_XmssKey_Init(key, NULL, INVALID_DEVID); + ret = wc_XmssKey_Init(key, NULL, server->devId); if (ret == 0) { ret = wh_Server_XmssKeyCacheExport(server, keyId, key); if (ret == WH_ERROR_OK) { diff --git a/test/wh_test_check_struct_padding.c b/test/wh_test_check_struct_padding.c index ba3d86295..01d6e3ed4 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 250aad6f7..3b8a2f3b4 100644 --- a/test/wh_test_crypto.c +++ b/test/wh_test_crypto.c @@ -1488,6 +1488,513 @@ static int whTest_CryptoEcc(whClientContext* ctx, int devId, WC_RNG* rng) return ret; } +/* 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); + } + } + + 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 */ + #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), @@ -1510,7 +2017,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); @@ -1578,6 +2084,59 @@ static int whTest_CryptoEccExportPublicDma(whClientContext* ctx, int devId, wc_ecc_free(pubKey); } + /* 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. */ + 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); + } + } + if (!WH_KEYID_ISERASED(keyId)) { (void)wh_Client_KeyEvict(ctx, keyId); } @@ -15544,6 +16103,14 @@ int whTest_CryptoClientConfig(whClientConfig* config) if (ret == 0) { ret = whTest_CryptoEccCacheDuplicate(client); } + 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 */ #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 ed1843033..9e75c3981 100644 --- a/wolfhsm/wh_client_crypto.h +++ b/wolfhsm/wh_client_crypto.h @@ -610,6 +610,85 @@ 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. This is the only way to obtain the public key of a key that is + * resident on the server and holds no public half. + * + * 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. Carried to the server but not honored: the + * server always performs the full validation. + * @param[in] check_priv Requests validation of the private part of the key. + * Carried to the server but not honored: the server + * always performs the full validation. + * @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 74fbfd24c..d66670026 100644 --- a/wolfhsm/wh_message_crypto.h +++ b/wolfhsm/wh_message_crypto.h @@ -588,10 +588,45 @@ 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 checkOrder; /* Validate the point has the curve order. Reserved: + * the server always validates in full */ + uint32_t checkPriv; /* Validate the private part as well. Reserved: the + * server always validates in full */ + 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) || \