diff --git a/.gitignore b/.gitignore index 3da94679..5f294cbf 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ tools/static-analysis/reports/ *.gcda *.gcno coverage/ +scan_out/* diff --git a/src/wh_client_crypto.c b/src/wh_client_crypto.c index ef433aa1..96ec5747 100644 --- a/src/wh_client_crypto.c +++ b/src/wh_client_crypto.c @@ -70,10 +70,13 @@ /** Forward declarations */ #ifdef HAVE_ECC -/* Server creates a key based on incoming flags */ -static int _EccMakeKey(whClientContext* ctx, int size, int curveId, - whKeyId* inout_key_id, whNvmFlags flags, - uint16_t label_len, uint8_t* label, ecc_key* key); +/* Async halves of the keygen path used by the public Request/Response APIs + * and the blocking wrappers wh_Client_EccMakeCacheKey/EccMakeExportKey. */ +static int _EccMakeKeyRequest(whClientContext* ctx, int size, int curveId, + whKeyId key_id, whNvmFlags flags, + uint16_t label_len, uint8_t* label); +static int _EccMakeKeyResponse(whClientContext* ctx, whKeyId* out_key_id, + ecc_key* out_key); #endif /* HAVE_ECC */ #ifdef HAVE_CURVE25519 @@ -1616,145 +1619,295 @@ int wh_Client_EccExportKey(whClientContext* ctx, whKeyId keyId, ecc_key* key, return ret; } -static int _EccMakeKey(whClientContext* ctx, int size, int curveId, - whKeyId* inout_key_id, whNvmFlags flags, - uint16_t label_len, uint8_t* label, ecc_key* key) +/* Shared async Request half for ECC keygen. Builds and sends the keygen + * request packet. The caller must arrange the matching async Response (or + * blocking poll) to consume the reply. */ +static int _EccMakeKeyRequest(whClientContext* ctx, int size, int curveId, + whKeyId key_id, whNvmFlags flags, + uint16_t label_len, uint8_t* label) { - int ret = WH_ERROR_OK; - whKeyId key_id = WH_KEYID_ERASED; - uint8_t* dataPtr = NULL; - whMessageCrypto_EccKeyGenRequest* req = NULL; - whMessageCrypto_EccKeyGenResponse* res = NULL; + whMessageCrypto_EccKeyGenRequest* req = NULL; + uint8_t* dataPtr = NULL; + uint16_t req_len; if (ctx == NULL) { return WH_ERROR_BADARGS; } - /* Get data pointer from the context to use as request/response storage */ + req_len = sizeof(whMessageCrypto_GenericRequestHeader) + sizeof(*req); + if (req_len > WOLFHSM_CFG_COMM_DATA_LEN) { + return WH_ERROR_BADARGS; + } + dataPtr = wh_CommClient_GetDataPtr(ctx->comm); if (dataPtr == NULL) { return WH_ERROR_BADARGS; } - /* Setup generic header and get pointer to request data */ req = (whMessageCrypto_EccKeyGenRequest*)_createCryptoRequest( dataPtr, WC_PK_TYPE_EC_KEYGEN, ctx->cryptoAffinity); - /* Use the supplied key id if provided */ - if (inout_key_id != NULL) { - key_id = *inout_key_id; + memset(req, 0, sizeof(*req)); + req->sz = size; + req->curveId = curveId; + req->flags = flags; + req->keyId = key_id; + if ((label != NULL) && (label_len > 0)) { + if (label_len > WH_NVM_LABEL_LEN) { + label_len = WH_NVM_LABEL_LEN; + } + memcpy(req->label, label, label_len); } - /* No other calls before here, so this is always true */ - if (ret == WH_ERROR_OK) { - /* Request Message */ - uint16_t group = WH_MESSAGE_GROUP_CRYPTO; - uint16_t action = WC_ALGO_TYPE_PK; + WH_DEBUG_CLIENT_VERBOSE("EccMakeKey req: size=%u curveId=%d flags=0x%x\n", + (unsigned)size, curveId, (unsigned)flags); - uint16_t req_len = - sizeof(whMessageCrypto_GenericRequestHeader) + sizeof(*req); + return wh_Client_SendRequest(ctx, WH_MESSAGE_GROUP_CRYPTO, WC_ALGO_TYPE_PK, + req_len, dataPtr); +} - if (req_len <= WOLFHSM_CFG_COMM_DATA_LEN) { - memset(req, 0, sizeof(*req)); - req->sz = size; - req->curveId = curveId; - req->flags = flags; - req->keyId = key_id; - if ((label != NULL) && (label_len > 0)) { - if (label_len > WH_NVM_LABEL_LEN) { - label_len = WH_NVM_LABEL_LEN; - } - memcpy(req->label, label, label_len); - } +/* Shared async Response half for ECC keygen. Single-shot receive: returns + * WH_ERROR_NOTREADY if the reply has not arrived yet. When the request was + * EPHEMERAL (export), the server-supplied DER is deserialized into out_key; + * otherwise the assigned keyId is written to *out_key_id (if non-NULL) and + * also stamped into out_key->devCtx (if non-NULL). */ +static int _EccMakeKeyResponse(whClientContext* ctx, whKeyId* out_key_id, + ecc_key* out_key) +{ + int ret; + uint16_t group; + uint16_t action; + uint16_t res_len = 0; + uint8_t* dataPtr = NULL; + whMessageCrypto_EccKeyGenResponse* res = NULL; - ret = wh_Client_SendRequest(ctx, group, action, req_len, - (uint8_t*)dataPtr); - WH_DEBUG_CLIENT_VERBOSE("Req sent:size:%u, ret:%d\n", - (unsigned int)req->sz, ret); - if (ret == WH_ERROR_OK) { - /* Response Message */ - uint16_t res_len; - do { - ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); - } while (ret == WH_ERROR_NOTREADY); + if (ctx == NULL) { + return WH_ERROR_BADARGS; + } - if (ret == WH_ERROR_OK) { - /* Get response structure pointer, validates generic header - * rc */ - ret = _getCryptoResponse(dataPtr, WC_PK_TYPE_EC_KEYGEN, - (uint8_t**)&res); - WH_DEBUG_CLIENT_VERBOSE("Res recv:keyid:%u, len:%u, ret:%d\n", - (unsigned int)res->keyId, - (unsigned int)res->len, ret); - /* wolfCrypt allows positive error codes on success in some - * scenarios */ - if (ret >= 0) { - /* Key is cached on server or is ephemeral */ - key_id = (whKeyId)(res->keyId); + dataPtr = wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + return WH_ERROR_BADARGS; + } - /* Update output variable if requested */ - if (inout_key_id != NULL) { - *inout_key_id = key_id; - } + ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, dataPtr); + if (ret != WH_ERROR_OK) { + return ret; + } - /* Update the context if provided */ - if (key != NULL) { - /* Set the key_id. Should be ERASED if EPHEMERAL */ - wh_Client_EccSetKeyId(key, key_id); + /* Get response structure pointer; validates the generic header rc */ + ret = _getCryptoResponse(dataPtr, WC_PK_TYPE_EC_KEYGEN, (uint8_t**)&res); + /* wolfCrypt allows positive error codes on success in some scenarios */ + if (ret >= 0) { + whKeyId key_id = (whKeyId)(res->keyId); + const size_t hdr_sz = + sizeof(whMessageCrypto_GenericResponseHeader) + sizeof(*res); + /* Defensive bound: res->len must fit within the actual received + * frame */ + if (res_len < hdr_sz || res->len > (res_len - hdr_sz)) { + return WH_ERROR_ABORTED; + } + WH_DEBUG_CLIENT_VERBOSE("EccMakeKey res: keyid:%u len:%u\n", + (unsigned)key_id, (unsigned)res->len); - if (flags & WH_NVM_FLAGS_EPHEMERAL) { - uint8_t* key_der = (uint8_t*)(res + 1); - uint16_t der_size = (uint16_t)(res->len); - /* Response has the exported key */ - ret = wh_Crypto_EccDeserializeKeyDer( - key_der, der_size, key); - WH_DEBUG_VERBOSE_HEXDUMP("[client] KeyGen export:", - key_der, der_size); - } - } - } - } - } + if (out_key_id != NULL) { + *out_key_id = key_id; } - else { - ret = WH_ERROR_BADARGS; + + /* If a DER blob is present (ephemeral/export keygen), deserialize it + * into the supplied key context. When called from the cache wrapper, + * out_key is NULL and the server returns an empty body. */ + if (out_key != NULL) { + if (res->len > 0) { + uint8_t* key_der = (uint8_t*)(res + 1); + uint16_t der_size = (uint16_t)(res->len); + /* Ephemeral key — leave devCtx ERASED */ + wh_Client_EccSetKeyId(out_key, WH_KEYID_ERASED); + ret = + wh_Crypto_EccDeserializeKeyDer(key_der, der_size, out_key); + WH_DEBUG_VERBOSE_HEXDUMP("[client] KeyGen export:", key_der, + der_size); + } + else { + /* Cached key — stamp the assigned keyId */ + wh_Client_EccSetKeyId(out_key, key_id); + } } } return ret; } +int wh_Client_EccMakeCacheKeyRequest(whClientContext* ctx, int size, + int curveId, whKeyId key_id, + whNvmFlags flags, uint16_t label_len, + uint8_t* label) +{ + /* The export pair owns ephemeral keygen — reject EPHEMERAL here so callers + * don't accidentally export when they meant to cache. */ + if (flags & WH_NVM_FLAGS_EPHEMERAL) { + return WH_ERROR_BADARGS; + } + return _EccMakeKeyRequest(ctx, size, curveId, key_id, flags, label_len, + label); +} + +int wh_Client_EccMakeCacheKeyResponse(whClientContext* ctx, whKeyId* out_key_id) +{ + if (out_key_id == NULL) { + return WH_ERROR_BADARGS; + } + return _EccMakeKeyResponse(ctx, out_key_id, NULL); +} + +int wh_Client_EccMakeExportKeyRequest(whClientContext* ctx, int size, + int curveId) +{ + return _EccMakeKeyRequest(ctx, size, curveId, WH_KEYID_ERASED, + WH_NVM_FLAGS_EPHEMERAL, 0, NULL); +} + +int wh_Client_EccMakeExportKeyResponse(whClientContext* ctx, ecc_key* key) +{ + if (key == NULL) { + return WH_ERROR_BADARGS; + } + return _EccMakeKeyResponse(ctx, NULL, key); +} + int wh_Client_EccMakeCacheKey(whClientContext* ctx, int size, int curveId, whKeyId* inout_key_id, whNvmFlags flags, uint16_t label_len, uint8_t* label) { + int ret; + whKeyId key_id = WH_KEYID_ERASED; + if (inout_key_id == NULL) { return WH_ERROR_BADARGS; } - return _EccMakeKey(ctx, size, curveId, inout_key_id, flags, label_len, - label, NULL); + ret = wh_Client_EccMakeCacheKeyRequest(ctx, size, curveId, *inout_key_id, + flags, label_len, label); + if (ret == WH_ERROR_OK) { + do { + ret = wh_Client_EccMakeCacheKeyResponse(ctx, &key_id); + } while (ret == WH_ERROR_NOTREADY); + if (ret >= 0) { + *inout_key_id = key_id; + } + } + return ret; } int wh_Client_EccMakeExportKey(whClientContext* ctx, int size, int curveId, ecc_key* key) { + int ret; + if (key == NULL) { return WH_ERROR_BADARGS; } - return _EccMakeKey(ctx, size, curveId, NULL, WH_NVM_FLAGS_EPHEMERAL, 0, - NULL, key); + ret = wh_Client_EccMakeExportKeyRequest(ctx, size, curveId); + if (ret == WH_ERROR_OK) { + do { + ret = wh_Client_EccMakeExportKeyResponse(ctx, key); + } while (ret == WH_ERROR_NOTREADY); + } + return ret; +} + +int wh_Client_EccSharedSecretRequest(whClientContext* ctx, whKeyId prv_key_id, + whKeyId pub_key_id) +{ + whMessageCrypto_EcdhRequest* req = NULL; + uint8_t* dataPtr = NULL; + uint16_t req_len; + + if (ctx == NULL) { + return WH_ERROR_BADARGS; + } + if (WH_KEYID_ISERASED(prv_key_id) || WH_KEYID_ISERASED(pub_key_id)) { + return WH_ERROR_BADARGS; + } + + req_len = sizeof(whMessageCrypto_GenericRequestHeader) + sizeof(*req); + if (req_len > WOLFHSM_CFG_COMM_DATA_LEN) { + return WH_ERROR_BADARGS; + } + + dataPtr = wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + return WH_ERROR_BADARGS; + } + + req = (whMessageCrypto_EcdhRequest*)_createCryptoRequest( + dataPtr, WC_PK_TYPE_ECDH, ctx->cryptoAffinity); + + memset(req, 0, sizeof(*req)); + req->options = 0; + req->privateKeyId = prv_key_id; + req->publicKeyId = pub_key_id; + + return wh_Client_SendRequest(ctx, WH_MESSAGE_GROUP_CRYPTO, WC_ALGO_TYPE_PK, + req_len, dataPtr); +} + +int wh_Client_EccSharedSecretResponse(whClientContext* ctx, uint8_t* out, + uint16_t* inout_size) +{ + int ret; + uint16_t group; + uint16_t action; + uint16_t res_len = 0; + uint8_t* dataPtr; + whMessageCrypto_EcdhResponse* res = NULL; + + if ((ctx == NULL) || ((out != NULL) && (inout_size == NULL))) { + return WH_ERROR_BADARGS; + } + + 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_ECDH, (uint8_t**)&res); + if (ret >= 0) { + uint8_t* res_out = (uint8_t*)(res + 1); + const size_t hdr_sz = + sizeof(whMessageCrypto_GenericResponseHeader) + sizeof(*res); + /* Defensive bound: res->sz must fit within the actual received frame */ + if (res_len < hdr_sz || res->sz > (res_len - hdr_sz)) { + return WH_ERROR_ABORTED; + } + if (inout_size != NULL) { + if ((out != NULL) && (res->sz > *inout_size)) { + /* Output buffer too small. Report required size and fail + * rather than silently truncating ECDH key material. */ + *inout_size = res->sz; + return WH_ERROR_BUFFER_SIZE; + } + *inout_size = res->sz; + if ((out != NULL) && (res->sz > 0)) { + memcpy(out, res_out, res->sz); + } + } + } + return ret; } int wh_Client_EccSharedSecret(whClientContext* ctx, ecc_key* priv_key, ecc_key* pub_key, uint8_t* out, - uint16_t* out_size) + uint16_t* inout_size) { - int ret = WH_ERROR_OK; - uint8_t* dataPtr = NULL; - whMessageCrypto_EcdhRequest* req = NULL; - whMessageCrypto_EcdhResponse* res = NULL; + int ret = WH_ERROR_OK; + uint8_t* dataPtr = NULL; + whMessageCrypto_EcdhRequest* req = NULL; /* Transaction state */ whKeyId prv_key_id; @@ -1762,7 +1915,10 @@ int wh_Client_EccSharedSecret(whClientContext* ctx, ecc_key* priv_key, whKeyId pub_key_id; int pub_evict = 0; - if ((ctx == NULL) || (pub_key == NULL) || (priv_key == NULL)) { + /* 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) || (pub_key == NULL) || (priv_key == NULL) || + ((out != NULL) && (inout_size == NULL))) { return WH_ERROR_BADARGS; } @@ -1834,34 +1990,12 @@ int wh_Client_EccSharedSecret(whClientContext* ctx, ecc_key* priv_key, /* Server will evict. Reset our flags */ pub_evict = prv_evict = 0; - /* Response Message */ - uint16_t res_len; - - /* Recv Response */ + /* Poll shared Response */ do { - ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); + ret = + wh_Client_EccSharedSecretResponse(ctx, out, inout_size); } while (ret == WH_ERROR_NOTREADY); WH_DEBUG_CLIENT_VERBOSE("resp packet recv. ret:%d\n", ret); - if (ret == WH_ERROR_OK) { - /* Get response structure pointer, validates generic header - * rc */ - ret = _getCryptoResponse(dataPtr, WC_PK_TYPE_ECDH, - (uint8_t**)&res); - /* wolfCrypt allows positive error codes on success in some - * scenarios */ - if (ret >= 0) { - uint8_t* res_out = (uint8_t*)(res + 1); - /* TODO: should we sanity check res->sz? */ - if (out_size != NULL) { - *out_size = res->sz; - } - if (out != NULL) { - memcpy(out, res_out, res->sz); - } - WH_DEBUG_VERBOSE_HEXDUMP("[client] Eccdh:", res_out, res->sz); - } - } } } else { @@ -1881,13 +2015,101 @@ int wh_Client_EccSharedSecret(whClientContext* ctx, ecc_key* priv_key, } +int wh_Client_EccSignRequest(whClientContext* ctx, whKeyId keyId, + const uint8_t* hash, uint16_t hash_len) +{ + whMessageCrypto_EccSignRequest* req = NULL; + uint8_t* dataPtr = NULL; + size_t req_len; + + if ((ctx == NULL) || ((hash == NULL) && (hash_len > 0))) { + return WH_ERROR_BADARGS; + } + if (WH_KEYID_ISERASED(keyId)) { + return WH_ERROR_BADARGS; + } + + req_len = + sizeof(whMessageCrypto_GenericRequestHeader) + sizeof(*req) + hash_len; + if (req_len > WOLFHSM_CFG_COMM_DATA_LEN) { + return WH_ERROR_BADARGS; + } + + dataPtr = wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + return WH_ERROR_BADARGS; + } + + req = (whMessageCrypto_EccSignRequest*)_createCryptoRequest( + dataPtr, WC_PK_TYPE_ECDSA_SIGN, ctx->cryptoAffinity); + + memset(req, 0, sizeof(*req)); + req->options = 0; + req->keyId = keyId; + req->sz = hash_len; + if ((hash != NULL) && (hash_len > 0)) { + memcpy((uint8_t*)(req + 1), hash, hash_len); + } + + return wh_Client_SendRequest(ctx, WH_MESSAGE_GROUP_CRYPTO, WC_ALGO_TYPE_PK, + (uint16_t)req_len, dataPtr); +} + +int wh_Client_EccSignResponse(whClientContext* ctx, uint8_t* sig, + uint16_t* inout_sig_len) +{ + int ret; + uint16_t group; + uint16_t action; + uint16_t res_len = 0; + uint8_t* dataPtr; + whMessageCrypto_EccSignResponse* res = NULL; + + if ((ctx == NULL) || ((sig != NULL) && (inout_sig_len == NULL))) { + return WH_ERROR_BADARGS; + } + + 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_ECDSA_SIGN, (uint8_t**)&res); + if (ret >= 0) { + uint8_t* res_sig = (uint8_t*)(res + 1); + const size_t hdr_sz = + sizeof(whMessageCrypto_GenericResponseHeader) + sizeof(*res); + /* Defensive bound: res->sz must fit within the actual received frame */ + if (res_len < hdr_sz || res->sz > (res_len - hdr_sz)) { + return WH_ERROR_ABORTED; + } + if (inout_sig_len != NULL) { + if ((sig != NULL) && (res->sz > *inout_sig_len)) { + /* Output buffer too small. Report required size and fail + * rather than silently truncating signature bytes. */ + *inout_sig_len = res->sz; + return WH_ERROR_BUFFER_SIZE; + } + *inout_sig_len = res->sz; + if ((sig != NULL) && (res->sz > 0)) { + memcpy(sig, res_sig, res->sz); + } + } + } + return ret; +} + int wh_Client_EccSign(whClientContext* ctx, ecc_key* key, const uint8_t* hash, uint16_t hash_len, uint8_t* sig, uint16_t* inout_sig_len) { - int ret = 0; - whMessageCrypto_EccSignRequest* req = NULL; - whMessageCrypto_EccSignResponse* res = NULL; - uint8_t* dataPtr = NULL; + int ret = 0; + whMessageCrypto_EccSignRequest* req = NULL; + uint8_t* dataPtr = NULL; /* Transaction state */ whKeyId key_id; @@ -1970,40 +2192,10 @@ int wh_Client_EccSign(whClientContext* ctx, ecc_key* key, const uint8_t* hash, /* Server will evict at this point. Reset evict */ evict = 0; - /* Response Message */ - uint16_t res_len = 0; - - /* Recv Response */ + /* Poll shared Response */ do { - ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); + ret = wh_Client_EccSignResponse(ctx, sig, inout_sig_len); } while (ret == WH_ERROR_NOTREADY); - - if (ret == WH_ERROR_OK) { - /* Get response structure pointer, validates generic header - * rc */ - ret = _getCryptoResponse(dataPtr, WC_PK_TYPE_ECDSA_SIGN, - (uint8_t**)&res); - /* wolfCrypt allows positive error codes on success in some - * scenarios */ - if (ret >= 0) { - uint8_t* res_sig = (uint8_t*)(res + 1); - uint16_t sig_len = res->sz; - /* check inoutlen and read out */ - if (inout_sig_len != NULL) { - if (sig_len > *inout_sig_len) { - /* Silently truncate the signature */ - sig_len = *inout_sig_len; - } - *inout_sig_len = sig_len; - if ((sig != NULL) && (sig_len > 0)) { - memcpy(sig, res_sig, sig_len); - } - WH_DEBUG_VERBOSE_HEXDUMP("[client] EccSign:", res_sig, - sig_len); - } - } - } } } else { @@ -2019,14 +2211,101 @@ int wh_Client_EccSign(whClientContext* ctx, ecc_key* key, const uint8_t* hash, return ret; } +int wh_Client_EccVerifyRequest(whClientContext* ctx, whKeyId keyId, + const uint8_t* sig, uint16_t sig_len, + const uint8_t* hash, uint16_t hash_len) +{ + whMessageCrypto_EccVerifyRequest* req = NULL; + uint8_t* dataPtr = NULL; + size_t req_len; + + if ((ctx == NULL) || ((sig == NULL) && (sig_len > 0)) || + ((hash == NULL) && (hash_len > 0))) { + return WH_ERROR_BADARGS; + } + if (WH_KEYID_ISERASED(keyId)) { + return WH_ERROR_BADARGS; + } + + req_len = sizeof(whMessageCrypto_GenericRequestHeader) + sizeof(*req) + + sig_len + hash_len; + if (req_len > WOLFHSM_CFG_COMM_DATA_LEN) { + return WH_ERROR_BADARGS; + } + + dataPtr = wh_CommClient_GetDataPtr(ctx->comm); + if (dataPtr == NULL) { + return WH_ERROR_BADARGS; + } + + req = (whMessageCrypto_EccVerifyRequest*)_createCryptoRequest( + dataPtr, WC_PK_TYPE_ECDSA_VERIFY, ctx->cryptoAffinity); + + memset(req, 0, sizeof(*req)); + req->options = 0; + req->keyId = keyId; + req->sigSz = sig_len; + req->hashSz = hash_len; + if ((sig != NULL) && (sig_len > 0)) { + memcpy((uint8_t*)(req + 1), sig, sig_len); + } + if ((hash != NULL) && (hash_len > 0)) { + memcpy((uint8_t*)(req + 1) + sig_len, hash, hash_len); + } + + return wh_Client_SendRequest(ctx, WH_MESSAGE_GROUP_CRYPTO, WC_ALGO_TYPE_PK, + (uint16_t)req_len, dataPtr); +} + +int wh_Client_EccVerifyResponse(whClientContext* ctx, ecc_key* opt_key, + int* out_res) +{ + int ret; + uint16_t group; + uint16_t action; + uint16_t res_len = 0; + uint8_t* dataPtr; + whMessageCrypto_EccVerifyResponse* res = NULL; + + if ((ctx == NULL) || (out_res == NULL)) { + return WH_ERROR_BADARGS; + } + + 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_ECDSA_VERIFY, (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; + } + *out_res = res->res; + if ((opt_key != NULL) && (res->pubSz > 0)) { + ret = wh_Crypto_EccUpdatePrivateOnlyKeyDer(opt_key, res->pubSz, + (uint8_t*)(res + 1)); + } + } + return ret; +} + int wh_Client_EccVerify(whClientContext* ctx, ecc_key* key, const uint8_t* sig, uint16_t sig_len, const uint8_t* hash, uint16_t hash_len, int* out_res) { - int ret = 0; - whMessageCrypto_EccVerifyRequest* req = NULL; - whMessageCrypto_EccVerifyResponse* res = NULL; - uint8_t* dataPtr = NULL; + int ret = 0; + whMessageCrypto_EccVerifyRequest* req = NULL; + uint8_t* dataPtr = NULL; /* Transaction state */ whKeyId key_id; @@ -2038,8 +2317,10 @@ int wh_Client_EccVerify(whClientContext* ctx, ecc_key* key, const uint8_t* sig, "out_res:%p\n", ctx, key, sig, sig_len, hash, hash_len, out_res); + /* 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) || ((sig == NULL) && (sig_len > 0)) || - ((hash == NULL) && (hash_len > 0))) { + ((hash == NULL) && (hash_len > 0)) || (out_res == NULL)) { return WH_ERROR_BADARGS; } @@ -2124,33 +2405,14 @@ int wh_Client_EccVerify(whClientContext* ctx, ecc_key* key, const uint8_t* sig, if (ret == WH_ERROR_OK) { /* Server will evict at this point. Reset evict */ evict = 0; - /* Response Message */ - uint16_t res_len = 0; - /* Recv Response */ + /* Poll shared Response. When EXPORTPUB was requested, the + * Response updates the caller's key with the server-derived + * public half. */ do { - ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); + ret = wh_Client_EccVerifyResponse( + ctx, (export_pub_key != 0) ? key : NULL, out_res); } while (ret == WH_ERROR_NOTREADY); - if (ret == WH_ERROR_OK) { - /* Get response structure pointer, validates generic header - * rc */ - ret = _getCryptoResponse(dataPtr, WC_PK_TYPE_ECDSA_VERIFY, - (uint8_t**)&res); - /* wolfCrypt allows positive error codes on success in some - * scenarios */ - if (ret >= 0) { - uint32_t res_der_size = 0; - *out_res = res->res; - res_der_size = res->pubSz; - if (res_der_size > 0) { - uint8_t* res_pub_der = (uint8_t*)(res + 1); - /* Update the key with the generated public key */ - ret = wh_Crypto_EccUpdatePrivateOnlyKeyDer( - key, res_der_size, res_pub_der); - } - } - } } } else { diff --git a/src/wh_client_cryptocb.c b/src/wh_client_cryptocb.c index 43ff0a32..48c620d6 100644 --- a/src/wh_client_cryptocb.c +++ b/src/wh_client_cryptocb.c @@ -259,8 +259,10 @@ int wh_Client_CryptoCb(int devId, wc_CryptoInfo* info, void* inCtx) ret = wh_Client_EccSharedSecret(ctx, priv_key, pub_key, out, &len); - if ( (ret == WH_ERROR_OK) && - (out_len != NULL) ) { + /* 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_len != NULL)) { *out_len = len; } } break; @@ -282,8 +284,10 @@ int wh_Client_CryptoCb(int devId, wc_CryptoInfo* info, void* inCtx) } ret = wh_Client_EccSign(ctx, key, hash, hash_len, sig, &sig_len); - if ( (ret == WH_ERROR_OK) && - (out_sig_len != NULL) ) { + /* 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_sig_len != NULL)) { *out_sig_len = sig_len; } } break; @@ -584,6 +588,9 @@ int wh_Client_CryptoCb(int devId, wc_CryptoInfo* info, void* inCtx) if (ret == WH_ERROR_BADARGS) { ret = BAD_FUNC_ARG; } + else if (ret == WH_ERROR_BUFFER_SIZE) { + ret = BUFFER_E; + } if (ret == CRYPTOCB_UNAVAILABLE) { WH_DEBUG_CLIENT("X not implemented: algo->type:%d\n", info->algo_type); diff --git a/test/wh_test_crypto.c b/test/wh_test_crypto.c index f876e72e..d33fa192 100644 --- a/test/wh_test_crypto.c +++ b/test/wh_test_crypto.c @@ -1209,6 +1209,902 @@ static int whTest_CryptoEccCrossVerify(whClientContext* ctx, WC_RNG* rng) return ret; } + +/** + * Test the async Request/Response API for ECC sign and verify. + * + * For each curve: + * 1. Generate a HSM ECC key (server-cached with an assigned keyId). + * 2. Call wh_Client_EccSignRequest + poll wh_Client_EccSignResponse; verify + * the resulting signature in software. + * 3. Import the public key as a separate cache slot; call + * wh_Client_EccVerifyRequest + poll wh_Client_EccVerifyResponse; assert + * res == 1. + * 4. Assert that *Request() with an erased keyId returns WH_ERROR_BADARGS. + */ +static int whTest_CryptoEccSignVerifyAsync_OneCurve(whClientContext* ctx, + WC_RNG* rng, int keySize, + int curveId, + const char* name) +{ + ecc_key hsmKey[1] = {0}; + ecc_key swKey[1] = {0}; + uint8_t hash[WH_TEST_ECC_HASH_SIZE] = {0}; + uint8_t sig[ECC_MAX_SIG_SIZE] = {0}; + uint8_t pubX[ECC_MAXSIZE] = {0}; + uint8_t pubY[ECC_MAXSIZE] = {0}; + word32 pubXLen = 0; + word32 pubYLen = 0; + uint16_t sigLen = 0; + int res = 0; + whKeyId signKeyId = WH_KEYID_ERASED; + whKeyId verifyKeyId = WH_KEYID_ERASED; + int hsmKeyInit = 0; + int swKeyInit = 0; + int ret = WH_ERROR_OK; + int i; + + /* Use non-repeating pattern to detect hash truncation bugs */ + for (i = 0; i < WH_TEST_ECC_HASH_SIZE; i++) { + hash[i] = (uint8_t)i; + } + + WH_TEST_PRINT(" Testing async Sign/Verify %s curve...\n", name); + + pubXLen = keySize; + pubYLen = keySize; + + ret = wc_ecc_init_ex(hsmKey, NULL, WH_DEV_ID); + if (ret == 0) { + hsmKeyInit = 1; + ret = wc_ecc_make_key(rng, keySize, hsmKey); + } + if (ret == 0) { + uint8_t signLabel[] = "TestEccAsyncSign"; + signKeyId = WH_KEYID_ERASED; + ret = wh_Client_EccImportKey(ctx, hsmKey, &signKeyId, + WH_NVM_FLAGS_USAGE_SIGN, sizeof(signLabel), + signLabel); + } + + /* Async sign */ + if (ret == 0) { + sigLen = sizeof(sig); + ret = wh_Client_EccSignRequest(ctx, signKeyId, hash, sizeof(hash)); + if (ret != WH_ERROR_OK) { + WH_ERROR_PRINT("%s: EccSignRequest failed: %d\n", name, ret); + } + } + if (ret == 0) { + do { + ret = wh_Client_EccSignResponse(ctx, sig, &sigLen); + } while (ret == WH_ERROR_NOTREADY); + if (ret != WH_ERROR_OK) { + WH_ERROR_PRINT("%s: EccSignResponse failed: %d\n", name, ret); + } + } + + /* Precondition: erased keyId must return BADARGS from Request */ + if (ret == 0) { + int badret = + wh_Client_EccSignRequest(ctx, WH_KEYID_ERASED, hash, sizeof(hash)); + if (badret != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("%s: EccSignRequest with erased keyId returned %d " + "(want BADARGS)\n", + name, badret); + ret = -1; + } + } + + /* Export public key from HSM for verify path */ + if (ret == 0) { + ret = wc_ecc_export_public_raw(hsmKey, pubX, &pubXLen, pubY, &pubYLen); + } + + /* Software verify as an independent sanity check */ + if (ret == 0) { + ret = wc_ecc_init_ex(swKey, NULL, INVALID_DEVID); + if (ret == 0) { + swKeyInit = 1; + ret = wc_ecc_import_unsigned(swKey, pubX, pubY, NULL, curveId); + } + } + if (ret == 0) { + res = 0; + ret = wc_ecc_verify_hash(sig, sigLen, hash, sizeof(hash), &res, swKey); + if (ret == 0 && res != 1) { + WH_ERROR_PRINT("%s: async sign produced invalid signature\n", name); + ret = -1; + } + } + + /* Import the public key into a second HSM cache slot and async-verify */ + if (ret == 0) { + ecc_key pubOnly[1] = {0}; + uint8_t label[] = "TestEccAsyncVerify"; + + ret = wc_ecc_init_ex(pubOnly, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_ecc_import_unsigned(pubOnly, pubX, pubY, NULL, curveId); + } + if (ret == 0) { + verifyKeyId = WH_KEYID_ERASED; + ret = wh_Client_EccImportKey(ctx, pubOnly, &verifyKeyId, + WH_NVM_FLAGS_USAGE_VERIFY, + sizeof(label), label); + } + wc_ecc_free(pubOnly); + } + if (ret == 0) { + ret = wh_Client_EccVerifyRequest(ctx, verifyKeyId, sig, sigLen, hash, + sizeof(hash)); + if (ret != WH_ERROR_OK) { + WH_ERROR_PRINT("%s: EccVerifyRequest failed: %d\n", name, ret); + } + } + if (ret == 0) { + res = 0; + do { + ret = wh_Client_EccVerifyResponse(ctx, NULL, &res); + } while (ret == WH_ERROR_NOTREADY); + if (ret != WH_ERROR_OK) { + WH_ERROR_PRINT("%s: EccVerifyResponse failed: %d\n", name, ret); + } + else if (res != 1) { + WH_ERROR_PRINT("%s: async verify returned res=%d (want 1)\n", name, + res); + ret = -1; + } + } + if (ret == 0) { + int badret = wh_Client_EccVerifyRequest(ctx, WH_KEYID_ERASED, sig, + sigLen, hash, sizeof(hash)); + if (badret != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("%s: EccVerifyRequest with erased keyId returned %d " + "(want BADARGS)\n", + name, badret); + ret = -1; + } + } + + /* NULL ctx must be rejected by every async half (matches RNG discipline). + */ + if (ret == 0) { + int rc1 = wh_Client_EccSignRequest(NULL, signKeyId, hash, sizeof(hash)); + int rc2 = wh_Client_EccSignResponse(NULL, sig, &sigLen); + int rc3 = wh_Client_EccVerifyRequest(NULL, verifyKeyId, sig, sigLen, + hash, sizeof(hash)); + int rc4 = wh_Client_EccVerifyResponse(NULL, NULL, &res); + if (rc1 != WH_ERROR_BADARGS || rc2 != WH_ERROR_BADARGS || + rc3 != WH_ERROR_BADARGS || rc4 != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("%s: NULL ctx async API rc=(%d,%d,%d,%d) want all " + "BADARGS\n", + name, rc1, rc2, rc3, rc4); + ret = -1; + } + } + + /* Mismatched output-arg shape on Response must BADARGS pre-Recv. ctx has + * no pending request here, so this also confirms the call is + * non-mutating. */ + if (ret == 0) { + int rc1 = wh_Client_EccSignResponse(ctx, sig, NULL); + int rc2 = wh_Client_EccVerifyResponse(ctx, NULL, NULL); + if (rc1 != WH_ERROR_BADARGS || rc2 != WH_ERROR_BADARGS) { + WH_ERROR_PRINT( + "%s: bad-arg Response rc=(%d,%d) want both BADARGS\n", name, + rc1, rc2); + ret = -1; + } + } + + /* Wrapper-level regression: response-side bad args must be caught before + * SendRequest so the caller's ctx is not left stuck-pending. */ + if (ret == 0) { + int badret = wh_Client_EccVerify(ctx, hsmKey, sig, sigLen, hash, + sizeof(hash), NULL); + if (badret != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("%s: EccVerify with NULL out_res returned %d " + "(want BADARGS)\n", + name, badret); + ret = -1; + } + } + + /* Confirm ctx is still usable after the wrapper rejection. */ + if (ret == 0) { + int rc; + sigLen = sizeof(sig); + rc = wh_Client_EccSignRequest(ctx, signKeyId, hash, sizeof(hash)); + if (rc == WH_ERROR_OK) { + do { + rc = wh_Client_EccSignResponse(ctx, sig, &sigLen); + } while (rc == WH_ERROR_NOTREADY); + } + if (rc != WH_ERROR_OK) { + WH_ERROR_PRINT("%s: ctx stuck after wrapper BADARGS (rc=%d)\n", + name, rc); + ret = -1; + } + } + + /* Too-small sig buffer must return WH_ERROR_BUFFER_SIZE with the required + * size in *inout_sig_len, and must NOT write a partial signature. Mirrors + * the matching ECDH regression test, since the implementation explicitly + * promises to fail rather than silently truncate signature bytes. */ + if (ret == 0) { + uint8_t small_buf[1] = {0xAA}; + uint16_t small_len = sizeof(small_buf); + int rc; + rc = wh_Client_EccSignRequest(ctx, signKeyId, hash, sizeof(hash)); + if (rc == WH_ERROR_OK) { + do { + rc = wh_Client_EccSignResponse(ctx, small_buf, &small_len); + } while (rc == WH_ERROR_NOTREADY); + } + if (rc != WH_ERROR_BUFFER_SIZE) { + WH_ERROR_PRINT( + "%s: too-small buffer Sign Response rc=%d (want BUFFER_SIZE)\n", + name, rc); + ret = -1; + } + else if (small_len <= 1 || small_len > ECC_MAX_SIG_SIZE) { + WH_ERROR_PRINT("%s: too-small buffer Sign required size=%u " + "(want > 1 and <= ECC_MAX_SIG_SIZE)\n", + name, (unsigned)small_len); + ret = -1; + } + else if (small_buf[0] != 0xAA) { + WH_ERROR_PRINT( + "%s: partial signature leaked into too-small buffer\n", name); + ret = -1; + } + } + + if (ret == 0) { + WH_TEST_PRINT(" async Sign/Verify %s: PASS\n", name); + } + + /* Cleanup: evict any cached keys, free wolfCrypt structs */ + if (!WH_KEYID_ISERASED(verifyKeyId)) { + (void)wh_Client_KeyEvict(ctx, verifyKeyId); + } + if (!WH_KEYID_ISERASED(signKeyId)) { + (void)wh_Client_KeyEvict(ctx, signKeyId); + } + if (swKeyInit) { + wc_ecc_free(swKey); + } + if (hsmKeyInit) { + wc_ecc_free(hsmKey); + } + return ret; +} + +#ifdef HAVE_ECC_DHE +/** + * Test the async Request/Response API for ECDH. + * + * For each curve: + * 1. Generate two HSM ECC keys (both server-cached). + * 2. Export public bytes from each, import each into the opposite side as a + * separate cache slot so we have a (private_A, public_B) and + * (private_B, public_A) pair for cross-verification. + * 3. Call wh_Client_EccSharedSecretRequest + poll + * wh_Client_EccSharedSecretResponse with (privA, pubB) and (privB, pubA). + * 4. Assert both shared secrets are equal. + * 5. Assert that *Request() with an erased keyId returns WH_ERROR_BADARGS. + */ +static int whTest_CryptoEccSharedSecretAsync_OneCurve(whClientContext* ctx, + WC_RNG* rng, int keySize, + int curveId, + const char* name) +{ + ecc_key keyA[1] = {0}; + ecc_key keyB[1] = {0}; + ecc_key pubA[1] = {0}; + ecc_key pubB[1] = {0}; + uint8_t pubAx[ECC_MAXSIZE] = {0}; + uint8_t pubAy[ECC_MAXSIZE] = {0}; + uint8_t pubBx[ECC_MAXSIZE] = {0}; + uint8_t pubBy[ECC_MAXSIZE] = {0}; + word32 pubAxLen = 0; + word32 pubAyLen = 0; + word32 pubBxLen = 0; + word32 pubByLen = 0; + uint8_t secret_AB[ECC_MAXSIZE] = {0}; + uint8_t secret_BA[ECC_MAXSIZE] = {0}; + uint16_t secret_AB_len = sizeof(secret_AB); + uint16_t secret_BA_len = sizeof(secret_BA); + whKeyId privAId = WH_KEYID_ERASED; + whKeyId privBId = WH_KEYID_ERASED; + whKeyId pubAId = WH_KEYID_ERASED; + whKeyId pubBId = WH_KEYID_ERASED; + int keyAInit = 0; + int keyBInit = 0; + int pubAInit = 0; + int pubBInit = 0; + uint8_t labelA[] = "TestEccDhAsyncA"; + uint8_t labelB[] = "TestEccDhAsyncB"; + int ret = WH_ERROR_OK; + + WH_TEST_PRINT(" Testing async ECDH %s curve...\n", name); + + pubAxLen = pubAyLen = pubBxLen = pubByLen = keySize; + + /* Generate two local ECC keys, then import each to the server cache so + * that both private keys have valid keyIds usable by the async API. */ + ret = wc_ecc_init_ex(keyA, NULL, WH_DEV_ID); + if (ret == 0) { + keyAInit = 1; + ret = wc_ecc_make_key(rng, keySize, keyA); + } + if (ret == 0) { + uint8_t privLabelA[] = "TestEccDhAsyncPrivA"; + privAId = WH_KEYID_ERASED; + ret = wh_Client_EccImportKey(ctx, keyA, &privAId, + WH_NVM_FLAGS_USAGE_DERIVE, + sizeof(privLabelA), privLabelA); + } + if (ret == 0) { + ret = wc_ecc_init_ex(keyB, NULL, WH_DEV_ID); + } + if (ret == 0) { + keyBInit = 1; + ret = wc_ecc_make_key(rng, keySize, keyB); + } + if (ret == 0) { + uint8_t privLabelB[] = "TestEccDhAsyncPrivB"; + privBId = WH_KEYID_ERASED; + ret = wh_Client_EccImportKey(ctx, keyB, &privBId, + WH_NVM_FLAGS_USAGE_DERIVE, + sizeof(privLabelB), privLabelB); + } + + /* Export raw public bytes so we can import into independent cache slots */ + if (ret == 0) { + ret = + wc_ecc_export_public_raw(keyA, pubAx, &pubAxLen, pubAy, &pubAyLen); + } + if (ret == 0) { + ret = + wc_ecc_export_public_raw(keyB, pubBx, &pubBxLen, pubBy, &pubByLen); + } + + /* Build public-only keys and import each as a distinct cache slot */ + if (ret == 0) { + ret = wc_ecc_init_ex(pubA, NULL, INVALID_DEVID); + if (ret == 0) { + pubAInit = 1; + ret = wc_ecc_import_unsigned(pubA, pubAx, pubAy, NULL, curveId); + } + } + if (ret == 0) { + ret = wh_Client_EccImportKey(ctx, pubA, &pubAId, + WH_NVM_FLAGS_USAGE_DERIVE, sizeof(labelA), + labelA); + } + if (ret == 0) { + ret = wc_ecc_init_ex(pubB, NULL, INVALID_DEVID); + if (ret == 0) { + pubBInit = 1; + ret = wc_ecc_import_unsigned(pubB, pubBx, pubBy, NULL, curveId); + } + } + if (ret == 0) { + ret = wh_Client_EccImportKey(ctx, pubB, &pubBId, + WH_NVM_FLAGS_USAGE_DERIVE, sizeof(labelB), + labelB); + } + + /* Async ECDH: A_priv * B_pub */ + if (ret == 0) { + ret = wh_Client_EccSharedSecretRequest(ctx, privAId, pubBId); + } + if (ret == 0) { + do { + ret = wh_Client_EccSharedSecretResponse(ctx, secret_AB, + &secret_AB_len); + } while (ret == WH_ERROR_NOTREADY); + } + + /* Async ECDH: B_priv * A_pub */ + if (ret == 0) { + ret = wh_Client_EccSharedSecretRequest(ctx, privBId, pubAId); + } + if (ret == 0) { + do { + ret = wh_Client_EccSharedSecretResponse(ctx, secret_BA, + &secret_BA_len); + } while (ret == WH_ERROR_NOTREADY); + } + + if (ret == 0) { + if (secret_AB_len != secret_BA_len || + memcmp(secret_AB, secret_BA, secret_AB_len) != 0) { + WH_ERROR_PRINT("%s: async ECDH secrets differ across sides\n", + name); + ret = -1; + } + } + + /* Too-small output buffer must return WH_ERROR_BUFFER_SIZE with the + * required size in *inout_size, and must NOT write a partial secret. */ + if (ret == 0) { + uint8_t small_buf[1] = {0xAA}; + uint16_t small_len = sizeof(small_buf); + int rc; + rc = wh_Client_EccSharedSecretRequest(ctx, privAId, pubBId); + if (rc == WH_ERROR_OK) { + do { + rc = wh_Client_EccSharedSecretResponse(ctx, small_buf, + &small_len); + } while (rc == WH_ERROR_NOTREADY); + } + if (rc != WH_ERROR_BUFFER_SIZE) { + WH_ERROR_PRINT( + "%s: too-small buffer ECDH Response rc=%d (want BUFFER_SIZE)\n", + name, rc); + ret = -1; + } + else if (small_len != secret_AB_len) { + WH_ERROR_PRINT("%s: too-small buffer required size=%u (want %u)\n", + name, (unsigned)small_len, (unsigned)secret_AB_len); + ret = -1; + } + else if (small_buf[0] != 0xAA) { + WH_ERROR_PRINT("%s: partial secret leaked into too-small buffer\n", + name); + ret = -1; + } + } + + /* Wrapper path must surface BUFFER_SIZE the same way. */ + if (ret == 0) { + uint8_t small_buf[1] = {0xAA}; + uint16_t small_len = sizeof(small_buf); + int rc = + wh_Client_EccSharedSecret(ctx, keyA, pubB, small_buf, &small_len); + if (rc != WH_ERROR_BUFFER_SIZE) { + WH_ERROR_PRINT( + "%s: too-small buffer ECDH wrapper rc=%d (want BUFFER_SIZE)\n", + name, rc); + ret = -1; + } + else if (small_len != secret_AB_len) { + WH_ERROR_PRINT("%s: wrapper too-small required size=%u (want %u)\n", + name, (unsigned)small_len, (unsigned)secret_AB_len); + ret = -1; + } + else if (small_buf[0] != 0xAA) { + WH_ERROR_PRINT( + "%s: wrapper leaked partial secret into too-small buffer\n", + name); + ret = -1; + } + } + + /* Precondition: erased keyId on either side must return BADARGS */ + if (ret == 0) { + int badret = + wh_Client_EccSharedSecretRequest(ctx, WH_KEYID_ERASED, pubBId); + if (badret != WH_ERROR_BADARGS) { + WH_ERROR_PRINT( + "%s: ECDH Request with erased priv keyId returned %d\n", name, + badret); + ret = -1; + } + } + if (ret == 0) { + int badret = + wh_Client_EccSharedSecretRequest(ctx, privAId, WH_KEYID_ERASED); + if (badret != WH_ERROR_BADARGS) { + WH_ERROR_PRINT( + "%s: ECDH Request with erased pub keyId returned %d\n", name, + badret); + ret = -1; + } + } + + /* NULL ctx must be rejected by both async halves. */ + if (ret == 0) { + int rc1 = wh_Client_EccSharedSecretRequest(NULL, privAId, pubBId); + int rc2 = + wh_Client_EccSharedSecretResponse(NULL, secret_AB, &secret_AB_len); + if (rc1 != WH_ERROR_BADARGS || rc2 != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("%s: NULL ctx async ECDH rc=(%d,%d) want BADARGS\n", + name, rc1, rc2); + ret = -1; + } + } + + /* Response must reject (out != NULL && inout_size == NULL) pre-Recv. */ + if (ret == 0) { + int rc = wh_Client_EccSharedSecretResponse(ctx, secret_AB, NULL); + if (rc != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("%s: SharedSecretResponse(out, NULL) returned %d " + "(want BADARGS)\n", + name, rc); + ret = -1; + } + } + + /* Wrapper-level regression: NULL inout_size must BADARGS pre-Send so the + * caller's ctx is not left stuck-pending. */ + if (ret == 0) { + int badret = + wh_Client_EccSharedSecret(ctx, keyA, pubB, secret_AB, NULL); + if (badret != WH_ERROR_BADARGS) { + WH_ERROR_PRINT( + "%s: EccSharedSecret with NULL inout_size returned %d " + "(want BADARGS)\n", + name, badret); + ret = -1; + } + } + + /* Confirm ctx is still usable after the wrapper rejection. */ + if (ret == 0) { + int rc; + secret_AB_len = sizeof(secret_AB); + rc = wh_Client_EccSharedSecretRequest(ctx, privAId, pubBId); + if (rc == WH_ERROR_OK) { + do { + rc = wh_Client_EccSharedSecretResponse(ctx, secret_AB, + &secret_AB_len); + } while (rc == WH_ERROR_NOTREADY); + } + if (rc != WH_ERROR_OK) { + WH_ERROR_PRINT("%s: ctx stuck after ECDH wrapper BADARGS (rc=%d)\n", + name, rc); + ret = -1; + } + } + + if (ret == 0) { + WH_TEST_PRINT(" async ECDH %s: PASS\n", name); + } + + /* Cleanup */ + if (!WH_KEYID_ISERASED(pubBId)) { + (void)wh_Client_KeyEvict(ctx, pubBId); + } + if (!WH_KEYID_ISERASED(pubAId)) { + (void)wh_Client_KeyEvict(ctx, pubAId); + } + if (!WH_KEYID_ISERASED(privBId)) { + (void)wh_Client_KeyEvict(ctx, privBId); + } + if (!WH_KEYID_ISERASED(privAId)) { + (void)wh_Client_KeyEvict(ctx, privAId); + } + if (pubBInit) { + wc_ecc_free(pubB); + } + if (pubAInit) { + wc_ecc_free(pubA); + } + if (keyBInit) { + wc_ecc_free(keyB); + } + if (keyAInit) { + wc_ecc_free(keyA); + } + return ret; +} +#endif /* HAVE_ECC_DHE */ + +/** + * Test the async Request/Response API for ECC server-side keygen. + * + * For each curve: + * 1. Use wh_Client_EccMakeCacheKeyRequest + poll + * wh_Client_EccMakeCacheKeyResponse to generate a server-cached key, then + * sign-and-software-verify against the cached keyId to prove the key is + * usable. + * 2. Use wh_Client_EccMakeExportKeyRequest + poll + * wh_Client_EccMakeExportKeyResponse to generate an ephemeral key returned + * to the client; verify the wolfCrypt struct is populated and the curve + * matches. + * 3. Assert the precondition / arg-shape contract on each async half. + */ +static int whTest_CryptoEccMakeKeyAsync_OneCurve(whClientContext* ctx, + WC_RNG* rng, int keySize, + int curveId, const char* name) +{ + ecc_key exportKey[1] = {0}; + ecc_key swKey[1] = {0}; + uint8_t hash[WH_TEST_ECC_HASH_SIZE] = {0}; + uint8_t sig[ECC_MAX_SIG_SIZE] = {0}; + uint8_t pubX[ECC_MAXSIZE] = {0}; + uint8_t pubY[ECC_MAXSIZE] = {0}; + word32 pubXLen = 0; + word32 pubYLen = 0; + uint16_t sigLen = 0; + int res = 0; + whKeyId cacheKeyId = WH_KEYID_ERASED; + int exportKeyInit = 0; + int swKeyInit = 0; + uint8_t cacheLabel[] = "TestEccAsyncCacheGen"; + int ret = WH_ERROR_OK; + int i; + + /* Use non-repeating pattern so we'd notice silent truncation */ + for (i = 0; i < WH_TEST_ECC_HASH_SIZE; i++) { + hash[i] = (uint8_t)i; + } + + pubXLen = keySize; + pubYLen = keySize; + + WH_TEST_PRINT(" Testing async MakeKey %s curve...\n", name); + + /* --- MakeCacheKey async: generate, then sign with the new keyId --- */ + if (ret == 0) { + ret = wh_Client_EccMakeCacheKeyRequest( + ctx, keySize, curveId, WH_KEYID_ERASED, WH_NVM_FLAGS_USAGE_SIGN, + sizeof(cacheLabel), cacheLabel); + if (ret != WH_ERROR_OK) { + WH_ERROR_PRINT("%s: EccMakeCacheKeyRequest failed: %d\n", name, + ret); + } + } + if (ret == 0) { + do { + ret = wh_Client_EccMakeCacheKeyResponse(ctx, &cacheKeyId); + } while (ret == WH_ERROR_NOTREADY); + if (ret != WH_ERROR_OK) { + WH_ERROR_PRINT("%s: EccMakeCacheKeyResponse failed: %d\n", name, + ret); + } + else if (WH_KEYID_ISERASED(cacheKeyId)) { + WH_ERROR_PRINT("%s: server returned erased keyId\n", name); + ret = -1; + } + } + /* Sign with the cached key via the existing async sign API to prove the + * generated key is usable on the server. */ + if (ret == 0) { + sigLen = sizeof(sig); + ret = wh_Client_EccSignRequest(ctx, cacheKeyId, hash, sizeof(hash)); + if (ret == WH_ERROR_OK) { + do { + ret = wh_Client_EccSignResponse(ctx, sig, &sigLen); + } while (ret == WH_ERROR_NOTREADY); + } + if (ret != WH_ERROR_OK) { + WH_ERROR_PRINT("%s: sign with cache-generated keyId failed: %d\n", + name, ret); + } + } + /* Software-verify the signature using the public half exported from the + * server cache. */ + if (ret == 0) { + ecc_key pubOnly[1] = {0}; + uint8_t labelBuf[WH_NVM_LABEL_LEN]; + ret = wc_ecc_init_ex(pubOnly, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wh_Client_EccExportKey(ctx, cacheKeyId, pubOnly, + sizeof(labelBuf), labelBuf); + if (ret == 0) { + ret = wc_ecc_export_public_raw(pubOnly, pubX, &pubXLen, pubY, + &pubYLen); + } + wc_ecc_free(pubOnly); + } + if (ret != 0) { + WH_ERROR_PRINT("%s: export of cached pub failed: %d\n", name, ret); + } + } + if (ret == 0) { + ret = wc_ecc_init_ex(swKey, NULL, INVALID_DEVID); + if (ret == 0) { + swKeyInit = 1; + ret = wc_ecc_import_unsigned(swKey, pubX, pubY, NULL, curveId); + } + if (ret == 0) { + res = 0; + ret = wc_ecc_verify_hash(sig, sigLen, hash, sizeof(hash), &res, + swKey); + if (ret == 0 && res != 1) { + WH_ERROR_PRINT( + "%s: software verify of cache-generated key failed\n", + name); + ret = -1; + } + } + } + if (swKeyInit) { + wc_ecc_free(swKey); + } + + /* --- MakeExportKey async: generate, sign locally, verify locally --- */ + if (ret == 0) { + ret = wc_ecc_init_ex(exportKey, NULL, INVALID_DEVID); + if (ret == 0) { + exportKeyInit = 1; + } + } + if (ret == 0) { + ret = wh_Client_EccMakeExportKeyRequest(ctx, keySize, curveId); + if (ret != WH_ERROR_OK) { + WH_ERROR_PRINT("%s: EccMakeExportKeyRequest failed: %d\n", name, + ret); + } + } + if (ret == 0) { + do { + ret = wh_Client_EccMakeExportKeyResponse(ctx, exportKey); + } while (ret == WH_ERROR_NOTREADY); + if (ret != WH_ERROR_OK) { + WH_ERROR_PRINT("%s: EccMakeExportKeyResponse failed: %d\n", name, + ret); + } + } + /* Sanity-check the deserialized key by sign+verify entirely locally. */ + if (ret == 0) { + word32 swSigLen = sizeof(sig); + memset(sig, 0, sizeof(sig)); + ret = wc_ecc_sign_hash(hash, sizeof(hash), sig, &swSigLen, rng, + exportKey); + if (ret == 0) { + res = 0; + ret = wc_ecc_verify_hash(sig, swSigLen, hash, sizeof(hash), &res, + exportKey); + if (ret == 0 && res != 1) { + WH_ERROR_PRINT( + "%s: local verify of exported keygen key failed\n", name); + ret = -1; + } + } + if (ret != 0) { + WH_ERROR_PRINT( + "%s: local sign/verify of exported keygen key failed: %d\n", + name, ret); + } + } + + /* --- BADARGS: NULL ctx must be rejected by every async half. --- */ + if (ret == 0) { + int rc1 = wh_Client_EccMakeCacheKeyRequest(NULL, keySize, curveId, + WH_KEYID_ERASED, + WH_NVM_FLAGS_NONE, 0, NULL); + int rc2 = wh_Client_EccMakeCacheKeyResponse(NULL, &cacheKeyId); + int rc3 = wh_Client_EccMakeExportKeyRequest(NULL, keySize, curveId); + int rc4 = wh_Client_EccMakeExportKeyResponse(NULL, exportKey); + if (rc1 != WH_ERROR_BADARGS || rc2 != WH_ERROR_BADARGS || + rc3 != WH_ERROR_BADARGS || rc4 != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("%s: NULL ctx async MakeKey rc=(%d,%d,%d,%d) want " + "all BADARGS\n", + name, rc1, rc2, rc3, rc4); + ret = -1; + } + } + + /* --- BADARGS: NULL out args. ctx has no pending request, so this also + * confirms these calls don't mutate state. --- */ + if (ret == 0) { + int rc1 = wh_Client_EccMakeCacheKeyResponse(ctx, NULL); + int rc2 = wh_Client_EccMakeExportKeyResponse(ctx, NULL); + if (rc1 != WH_ERROR_BADARGS || rc2 != WH_ERROR_BADARGS) { + WH_ERROR_PRINT( + "%s: NULL out arg Response rc=(%d,%d) want both BADARGS\n", + name, rc1, rc2); + ret = -1; + } + } + + /* --- BADARGS: EPHEMERAL flag must be rejected by the cache Request so + * the export pair owns ephemeral keygen unambiguously. --- */ + if (ret == 0) { + int rc = wh_Client_EccMakeCacheKeyRequest( + ctx, keySize, curveId, WH_KEYID_ERASED, WH_NVM_FLAGS_EPHEMERAL, 0, + NULL); + if (rc != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("%s: cache Request with EPHEMERAL flag returned %d " + "(want BADARGS)\n", + name, rc); + ret = -1; + } + } + + /* --- Confirm ctx is still usable after the BADARGS rejections. --- */ + if (ret == 0) { + whKeyId tmpId = WH_KEYID_ERASED; + int rc = wh_Client_EccMakeCacheKeyRequest( + ctx, keySize, curveId, WH_KEYID_ERASED, WH_NVM_FLAGS_USAGE_SIGN, + sizeof(cacheLabel), cacheLabel); + if (rc == WH_ERROR_OK) { + do { + rc = wh_Client_EccMakeCacheKeyResponse(ctx, &tmpId); + } while (rc == WH_ERROR_NOTREADY); + } + if (rc != WH_ERROR_OK) { + WH_ERROR_PRINT("%s: ctx stuck after MakeKey BADARGS (rc=%d)\n", + name, rc); + ret = -1; + } + if (!WH_KEYID_ISERASED(tmpId)) { + (void)wh_Client_KeyEvict(ctx, tmpId); + } + } + + if (ret == 0) { + WH_TEST_PRINT(" async MakeKey %s: PASS\n", name); + } + + /* Cleanup */ + if (!WH_KEYID_ISERASED(cacheKeyId)) { + (void)wh_Client_KeyEvict(ctx, cacheKeyId); + } + if (exportKeyInit) { + wc_ecc_free(exportKey); + } + return ret; +} + +static int whTest_CryptoEccAsync(whClientContext* ctx, WC_RNG* rng) +{ + int ret = WH_ERROR_OK; + + WH_TEST_PRINT("Testing ECC async API...\n"); + +#if !defined(NO_ECC256) + if (ret == 0) { + ret = whTest_CryptoEccSignVerifyAsync_OneCurve( + ctx, rng, WH_TEST_ECC_P256_KEY_SIZE, ECC_SECP256R1, "P-256"); + } +#ifdef HAVE_ECC_DHE + if (ret == 0) { + ret = whTest_CryptoEccSharedSecretAsync_OneCurve( + ctx, rng, WH_TEST_ECC_P256_KEY_SIZE, ECC_SECP256R1, "P-256"); + } +#endif + if (ret == 0) { + ret = whTest_CryptoEccMakeKeyAsync_OneCurve( + ctx, rng, WH_TEST_ECC_P256_KEY_SIZE, ECC_SECP256R1, "P-256"); + } +#endif + +#if (defined(HAVE_ECC384) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 384 + if (ret == 0) { + ret = whTest_CryptoEccSignVerifyAsync_OneCurve( + ctx, rng, WH_TEST_ECC_P384_KEY_SIZE, ECC_SECP384R1, "P-384"); + } +#ifdef HAVE_ECC_DHE + if (ret == 0) { + ret = whTest_CryptoEccSharedSecretAsync_OneCurve( + ctx, rng, WH_TEST_ECC_P384_KEY_SIZE, ECC_SECP384R1, "P-384"); + } +#endif + if (ret == 0) { + ret = whTest_CryptoEccMakeKeyAsync_OneCurve( + ctx, rng, WH_TEST_ECC_P384_KEY_SIZE, ECC_SECP384R1, "P-384"); + } +#endif + +#if (defined(HAVE_ECC521) || defined(HAVE_ALL_CURVES)) && ECC_MIN_KEY_SZ <= 521 + if (ret == 0) { + ret = whTest_CryptoEccSignVerifyAsync_OneCurve( + ctx, rng, WH_TEST_ECC_P521_KEY_SIZE, ECC_SECP521R1, "P-521"); + } +#ifdef HAVE_ECC_DHE + if (ret == 0) { + ret = whTest_CryptoEccSharedSecretAsync_OneCurve( + ctx, rng, WH_TEST_ECC_P521_KEY_SIZE, ECC_SECP521R1, "P-521"); + } +#endif + if (ret == 0) { + ret = whTest_CryptoEccMakeKeyAsync_OneCurve( + ctx, rng, WH_TEST_ECC_P521_KEY_SIZE, ECC_SECP521R1, "P-521"); + } +#endif + + if (ret == 0) { + WH_TEST_PRINT("ECC async API SUCCESS\n"); + } + return ret; +} #endif /* HAVE_ECC_SIGN && HAVE_ECC_VERIFY && !WOLF_CRYPTO_CB_ONLY_ECC */ #endif /* HAVE_ECC */ @@ -7742,6 +8638,9 @@ int whTest_CryptoClientConfig(whClientConfig* config) if (ret == 0) { ret = whTest_CryptoEccCrossVerify(client, rng); } + if (ret == 0) { + ret = whTest_CryptoEccAsync(client, rng); + } #endif #endif /* HAVE_ECC */ diff --git a/wolfhsm/wh_client_crypto.h b/wolfhsm/wh_client_crypto.h index 5c1666a2..044fb8c0 100644 --- a/wolfhsm/wh_client_crypto.h +++ b/wolfhsm/wh_client_crypto.h @@ -314,41 +314,412 @@ int wh_Client_EccSetKeyId(ecc_key* key, whKeyId keyId); */ int wh_Client_EccGetKeyId(ecc_key* key, whKeyId* outId); -/* TODO: Send key to server */ +/** + * @brief Imports a wolfCrypt ECC key as a DER-formatted blob into the wolfHSM + * server key cache. + * + * This function serializes the ecc_key struct to DER format, installs it into + * the server's key cache, and provides the server-allocated keyId for + * reference. + * + * @param[in] ctx Pointer to the wolfHSM client structure. + * @param[in] key Pointer to the ECC key structure. + * @param[in,out] inout_keyId Pointer to the key ID. Set to WH_KEYID_ERASED to + * have the server allocate a unique id. May be NULL. + * @param[in] flags Value of flags to indicate server usage + * @param[in] label_len Length of the optional label in bytes, Valid values are + * 0 to WH_NVM_LABEL_LEN. + * @param[in] label pointer to the optional label byte array. May be NULL. + * @return int Returns 0 on success or a negative error code on failure. + */ int wh_Client_EccImportKey(whClientContext* ctx, ecc_key* key, whKeyId *inout_keyId, whNvmFlags flags, uint16_t label_len, uint8_t* label); -/* TODO: Recv key from server */ + +/** + * @brief Exports a DER-formatted ECC key from the wolfHSM server keycache and + * decodes it into the wolfCrypt ECC key structure. + * + * This function exports the specified key from the wolfHSM server key cache as + * a DER blob and decodes it into the wolfCrypt ecc_key structure, optionally + * copying out the associated label as well. + * + * @param[in] ctx Pointer to the wolfHSM client structure. + * @param[in] keyId Server key ID to export. + * @param[out] key Pointer to the ECC key structure to populate. + * @param[in] label_len Length of the optional label in bytes, Valid values are + * 0 to WH_NVM_LABEL_LEN. + * @param[out] label pointer to the optional label byte array. May be NULL. + * @return int Returns 0 on success or a negative error code on failure. + */ int wh_Client_EccExportKey(whClientContext* ctx, whKeyId keyId, ecc_key* key, uint16_t label_len, uint8_t* label); -/* TODO: Server creates and exports a key, without caching */ +/** + * @brief Generate an ECC key pair on the server and export it to the client. + * + * This function requests the server to generate a new ECC key pair and export + * it to the client, without using any key cache or additional resources. + * + * @param[in] ctx Pointer to the client context. + * @param[in] size Size of the key to generate in bytes (e.g. 32 for P-256). + * @param[in] curveId wolfCrypt curve identifier (e.g. ECC_SECP256R1). + * @param[out] key Pointer to a wolfCrypt ECC key structure, which will be + * populated with the new key pair when successful. + * @return int Returns 0 on success or a negative error code on failure. + */ int wh_Client_EccMakeExportKey(whClientContext* ctx, int size, int curveId, ecc_key* key); -/* TODO: Server creates and imports the key to cache. */ + +/** + * @brief Generate an ECC key pair in the server key cache. + * + * This function requests the server to generate a new ECC key pair and insert + * it into the server's key cache. The generated key material is not returned + * to the client. + * + * @param[in] ctx Pointer to the client context. + * @param[in] size Size of the key to generate in bytes (e.g. 32 for P-256). + * @param[in] curveId wolfCrypt curve identifier (e.g. ECC_SECP256R1). + * @param[in,out] inout_key_id Pointer to the key ID. Set to WH_KEYID_ERASED to + * have the server allocate a unique id. Must not be NULL. + * @param[in] flags Optional flags to be associated with the key while in the + * key cache or after being committed. Set to WH_NVM_FLAGS_NONE + * if not used. + * @param[in] label_len Size of the label up to WH_NVM_LABEL_LEN. Set to 0 if + * not used. + * @param[in] label Optional label to be associated with the key while in the + * key cache or after being committed. Set to NULL if not used. + * @return int Returns 0 on success or a negative error code on failure. + */ int wh_Client_EccMakeCacheKey(whClientContext* ctx, int size, int curveId, whKeyId *inout_key_id, whNvmFlags flags, uint16_t label_len, uint8_t* label); -/* TODO: Perform shared secret computation (ECDH) */ -int wh_Client_EccSharedSecret(whClientContext* ctx, - ecc_key* priv_key, ecc_key* pub_key, - uint8_t* out, uint16_t *out_size); - -/* TODO: Server generates signature of input hash */ +/** + * @brief Compute an ECDH shared secret using a public and private ECC key. + * + * This function requests the server to compute the shared secret using the + * provided wolfCrypt private and public keys. Either key context may 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). + * For any context that 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. + * + * @param[in] ctx Pointer to the client context. + * @param[in] priv_key Pointer to a wolfCrypt key structure that either holds + * the private key material or references a server-cached + * private key via its devCtx (keyId). + * @param[in] pub_key Pointer to a wolfCrypt key structure that either holds + * the public key material or references a server-cached + * public key via its devCtx (keyId). + * @param[out] out Buffer to receive the computed shared secret. May be NULL + * to query the required size, in which case inout_size must be + * non-NULL and the required size will be written to + * *inout_size with WH_ERROR_BUFFER_SIZE returned. + * @param[in,out] inout_size On input, the capacity of the out buffer in bytes + * when out is non-NULL. On output, the number of bytes + * written on success, or the required buffer size when + * WH_ERROR_BUFFER_SIZE is returned. Must not be NULL when + * out is non-NULL; may be NULL only when out is also NULL. + * @return int Returns 0 on success, WH_ERROR_BUFFER_SIZE if the caller's out + * buffer is too small to hold the shared secret (with required + * size written to *inout_size), or a negative error code on + * failure. + */ +int wh_Client_EccSharedSecret(whClientContext* ctx, ecc_key* priv_key, + ecc_key* pub_key, uint8_t* out, + uint16_t* inout_size); + +/** + * @brief Generate an ECDSA signature of the provided hash on the server. + * + * This function requests the server to sign the provided hash using 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. + * + * @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[in] hash Hash data to sign. May be NULL only if hash_len is 0. + * @param[in] hash_len Length of hash in bytes. + * @param[out] sig Buffer to receive the generated signature. May be NULL to + * query the required size, in which case inout_sig_len must be + * non-NULL and the required size will be written to + * *inout_sig_len with WH_ERROR_BUFFER_SIZE returned. + * @param[in,out] inout_sig_len On input, the capacity of the sig buffer in + * bytes when sig is non-NULL. On output, the number of + * bytes written on success, or the required buffer size + * when WH_ERROR_BUFFER_SIZE is returned. Must not be NULL + * when sig is non-NULL; may be NULL only when sig is also + * NULL. + * @return int Returns 0 on success, WH_ERROR_BUFFER_SIZE if the caller's sig + * buffer is too small to hold the signature (with required size + * written to *inout_sig_len), or a negative error code on + * failure. + */ int wh_Client_EccSign(whClientContext* ctx, ecc_key* key, const uint8_t* hash, uint16_t hash_len, uint8_t* sig, uint16_t *inout_sig_len); -/* TODO: Server verifies the signature of the provided hash */ +/** + * @brief Verify an ECDSA signature of the provided hash on the server. + * + * This function requests the server to verify the provided signature against + * the provided hash using 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. If the supplied key is private-only, the + * server will derive the public key as needed. + * + * @param[in] ctx Pointer to the client context. + * @param[in] key Pointer to a wolfCrypt ECC key structure that either holds + * the public key material or references a server-cached public + * key via its devCtx (keyId). + * @param[in] sig Signature bytes. + * @param[in] sig_len Length of sig in bytes. + * @param[in] hash Hash bytes that were signed. + * @param[in] hash_len Length of hash in bytes. + * @param[out] out_res Pointer to receive the verification result. Set to 1 if + * the signature is valid, 0 otherwise. Must not be NULL. + * @return int Returns 0 on success or a negative error code on failure. + */ int wh_Client_EccVerify(whClientContext* ctx, ecc_key* key, const uint8_t* sig, uint16_t sig_len, const uint8_t* hash, uint16_t hash_len, int *out_res); +/** + * @brief Async request half of an ECC sign operation. + * + * Serializes and sends a sign request for the hash using the server-cached + * private key identified by keyId. Does NOT wait for a reply. The key must + * already be cached on the server; auto-import is only available via the + * blocking wrapper wh_Client_EccSign. + * + * Contract: at most one outstanding async request may be in flight per + * whClientContext. The caller MUST call wh_Client_EccSignResponse before + * issuing any other async Request on the same ctx. + * + * @param[in] ctx Client context. + * @param[in] keyId Key ID of a cached ECC private key. Must not be erased. + * @param[in] hash Hash data to sign (may be NULL only if hash_len == 0). + * @param[in] hash_len Length of hash in bytes. + * @return WH_ERROR_OK on success, WH_ERROR_BADARGS for invalid args or erased + * keyId, or a negative error from the transport. + */ +int wh_Client_EccSignRequest(whClientContext* ctx, whKeyId keyId, + const uint8_t* hash, uint16_t hash_len); + +/** + * @brief Async response half of an ECC sign operation. + * + * Single-shot RecvResponse; returns WH_ERROR_NOTREADY if the server has not + * yet replied. On success, copies the signature into sig and updates + * *inout_sig_len. If the server-reported signature is larger than the + * caller's *inout_sig_len capacity, returns WH_ERROR_BUFFER_SIZE with the + * required size written to *inout_sig_len. + * + * @param[in] ctx Client context. + * @param[out] sig Buffer to receive the generated signature. May be NULL to + * query the required size, in which case inout_sig_len must be + * non-NULL and the required size will be written to + * *inout_sig_len with WH_ERROR_BUFFER_SIZE returned. + * @param[in,out] inout_sig_len On input, the capacity of the sig buffer in + * bytes when sig is non-NULL. On output, the number of + * bytes written on success, or the required buffer size + * when WH_ERROR_BUFFER_SIZE is returned. Must not be NULL + * when sig is non-NULL; may be NULL only when sig is also + * NULL. + * @return WH_ERROR_OK on success, WH_ERROR_NOTREADY if no reply yet, + * WH_ERROR_BUFFER_SIZE if sig is too small (required size written to + * *inout_sig_len), WH_ERROR_BADARGS for invalid args, or a negative + * error code from the transport. + */ +int wh_Client_EccSignResponse(whClientContext* ctx, uint8_t* sig, + uint16_t* inout_sig_len); + +/** + * @brief Async request half of an ECC verify operation. + * + * Serializes and sends a verify request for (sig, hash) using the server-cached + * public key identified by keyId. Does NOT wait for a reply. The key must + * already be cached on the server; auto-import is only available via the + * blocking wrapper wh_Client_EccVerify. + * + * Note: the async API does not support the EXPORTPUB convenience (deriving + * a public key from a private-only key) — that stays a blocking-wrapper + * convenience. + * + * @param[in] ctx Client context. + * @param[in] keyId Key ID of a cached ECC public key. Must not be erased. + * @param[in] sig Signature bytes. + * @param[in] sig_len Length of sig. + * @param[in] hash Hash bytes. + * @param[in] hash_len Length of hash. + * @return WH_ERROR_OK on success, WH_ERROR_BADARGS for invalid args or erased + * keyId, or a negative error from the transport. + */ +int wh_Client_EccVerifyRequest(whClientContext* ctx, whKeyId keyId, + const uint8_t* sig, uint16_t sig_len, + const uint8_t* hash, uint16_t hash_len); + +/** + * @brief Async response half of an ECC verify operation. + * + * Single-shot RecvResponse; returns WH_ERROR_NOTREADY if the server has not + * yet replied. On success, writes the verify result (1 = valid, 0 = invalid) + * to *out_res. + * + * @param[in] ctx Client context. + * @param[in,out] opt_key Optional ecc_key whose public half should be updated + * from the server-supplied DER bytes when the matching + * Request had EXPORTPUB set. Pass NULL when no key update + * is desired. The async Request half does not currently + * expose EXPORTPUB, so this parameter is primarily for the + * blocking wrapper wh_Client_EccVerify. + * @param[out] out_res 1 if the signature is valid, 0 otherwise. + */ +int wh_Client_EccVerifyResponse(whClientContext* ctx, ecc_key* opt_key, + int* out_res); + +/** + * @brief Async request half of an ECDH shared-secret operation. + * + * Serializes and sends a shared-secret request using two server-cached keys + * (private and public). Does NOT wait for a reply. Both keys must already be + * cached on the server; auto-import is only available via the blocking + * wrapper wh_Client_EccSharedSecret. + * + * @param[in] ctx Client context. + * @param[in] prv_key_id Key ID of the cached private key. Must not be erased. + * @param[in] pub_key_id Key ID of the cached public key. Must not be erased. + * @return WH_ERROR_OK on success, WH_ERROR_BADARGS for invalid args or erased + * keyIds, or a negative error from the transport. + */ +int wh_Client_EccSharedSecretRequest(whClientContext* ctx, whKeyId prv_key_id, + whKeyId pub_key_id); + +/** + * @brief Async response half of an ECDH shared-secret operation. + * + * Single-shot RecvResponse; returns WH_ERROR_NOTREADY if the server has not + * yet replied. On success, copies the shared secret into out and updates + * *inout_size. *inout_size is in/out: when out is non-NULL, callers must + * initialize it to the capacity of the out buffer. If the server-reported + * secret is larger than the caller's capacity, returns WH_ERROR_BUFFER_SIZE + * with the required size written to *inout_size — the partial buffer is NOT + * written, since truncated key material would be unsafe to use. + * + * @param[in] ctx Client context. + * @param[out] out Buffer to receive the computed shared secret. May be NULL + * to query the required size, in which case inout_size must be + * non-NULL and the required size will be written to + * *inout_size with WH_ERROR_BUFFER_SIZE returned. + * @param[in,out] inout_size On input, the capacity of the out buffer in bytes + * when out is non-NULL. On output, the number of bytes + * written on success, or the required buffer size when + * WH_ERROR_BUFFER_SIZE is returned. Must not be NULL when + * out is non-NULL; may be NULL only when out is also NULL. + * @return WH_ERROR_OK on success, WH_ERROR_NOTREADY if no reply yet, + * WH_ERROR_BUFFER_SIZE if out is too small (required size written to + * *inout_size), WH_ERROR_BADARGS for invalid args, or a negative + * error code from the transport. + */ +int wh_Client_EccSharedSecretResponse(whClientContext* ctx, uint8_t* out, + uint16_t* inout_size); + +/** + * @brief Async request half of an ECC server-side keygen that caches the new + * key in the server. + * + * Serializes and sends a keygen request that asks the server to generate a new + * ECC key pair on the specified curve and insert it into the server key cache. + * Does NOT wait for a reply. + * + * Contract: at most one outstanding async request may be in flight per + * whClientContext. The caller MUST call wh_Client_EccMakeCacheKeyResponse + * before issuing any other async Request on the same ctx. + * + * @param[in] ctx Client context. + * @param[in] size Size of the key to generate in bytes (e.g. 32 for + * P-256). + * @param[in] curveId wolfCrypt curve identifier (e.g. ECC_SECP256R1). + * @param[in] key_id Suggested key ID. Pass WH_KEYID_ERASED to have the + * server allocate one. + * @param[in] flags Optional NVM flags. Must NOT include + * WH_NVM_FLAGS_EPHEMERAL — use the MakeExportKey async + * pair for ephemeral (export) keygen instead. + * @param[in] label_len Size of the label up to WH_NVM_LABEL_LEN. Set to 0 if + * not used. + * @param[in] label Optional label byte array. May be NULL when label_len + * is 0. + * @return WH_ERROR_OK on success, WH_ERROR_BADARGS for invalid args (including + * EPHEMERAL flag), or a negative error from the transport. + */ +int wh_Client_EccMakeCacheKeyRequest(whClientContext* ctx, int size, + int curveId, whKeyId key_id, + whNvmFlags flags, uint16_t label_len, + uint8_t* label); + +/** + * @brief Async response half of an ECC server-side keygen that caches the new + * key in the server. + * + * Single-shot RecvResponse; returns WH_ERROR_NOTREADY if the server has not + * yet replied. On success, writes the server-allocated key ID into + * *out_key_id. + * + * @param[in] ctx Client context. + * @param[out] out_key_id Pointer to receive the assigned key ID. Must not be + * NULL. + */ +int wh_Client_EccMakeCacheKeyResponse(whClientContext* ctx, + whKeyId* out_key_id); + +/** + * @brief Async request half of an ECC server-side keygen that exports the new + * key back to the client. + * + * Serializes and sends an ephemeral keygen request that asks the server to + * generate a new ECC key pair on the specified curve and return its DER + * encoding to the client (the server does not retain the key). Does NOT wait + * for a reply. + * + * @param[in] ctx Client context. + * @param[in] size Size of the key to generate in bytes. + * @param[in] curveId wolfCrypt curve identifier. + * @return WH_ERROR_OK on success, WH_ERROR_BADARGS for invalid args, or a + * negative error from the transport. + */ +int wh_Client_EccMakeExportKeyRequest(whClientContext* ctx, int size, + int curveId); + +/** + * @brief Async response half of an ECC server-side keygen that exports the new + * key back to the client. + * + * Single-shot RecvResponse; returns WH_ERROR_NOTREADY if the server has not + * yet replied. On success, deserializes the DER blob returned by the server + * into the supplied wolfCrypt ecc_key. + * + * @param[in] ctx Client context. + * @param[out] key Pointer to a wolfCrypt ECC key structure that will be + * populated with the new key pair. Must not be NULL. + */ +int wh_Client_EccMakeExportKeyResponse(whClientContext* ctx, ecc_key* key); + #endif /* HAVE_ECC */ #ifdef HAVE_ED25519