diff --git a/src/wh_client_crypto.c b/src/wh_client_crypto.c index ed614d2a0..e5cefb5d6 100644 --- a/src/wh_client_crypto.c +++ b/src/wh_client_crypto.c @@ -2117,8 +2117,15 @@ int wh_Client_EccMakeExportKey(whClientContext* ctx, int size, int curveId, return ret; } -int wh_Client_EccSharedSecretRequest(whClientContext* ctx, whKeyId prv_key_id, - whKeyId pub_key_id) +/* Build and send an ECDH shared-secret request. + * + * flags & WH_NVM_FLAGS_EPHEMERAL selects the export mode (secret returned to + * the client). Otherwise the secret is cached on the server with `flags` + * and `label`, stored at `out_key_id` (WH_KEYID_ERASED -> server allocates). */ +static int _EccSharedSecretRequest(whClientContext* ctx, whKeyId prv_key_id, + whKeyId pub_key_id, uint32_t options, + whNvmFlags flags, whKeyId out_key_id, + const uint8_t* label, uint16_t label_len) { whMessageCrypto_EcdhRequest* req = NULL; uint8_t* dataPtr = NULL; @@ -2130,6 +2137,12 @@ int wh_Client_EccSharedSecretRequest(whClientContext* ctx, whKeyId prv_key_id, if (WH_KEYID_ISERASED(prv_key_id) || WH_KEYID_ISERASED(pub_key_id)) { return WH_ERROR_BADARGS; } + if ((label_len > 0) && (label == NULL)) { + return WH_ERROR_BADARGS; + } + if (label_len > WH_NVM_LABEL_LEN) { + label_len = WH_NVM_LABEL_LEN; + } req_len = sizeof(whMessageCrypto_GenericRequestHeader) + sizeof(*req); if (req_len > WOLFHSM_CFG_COMM_DATA_LEN) { @@ -2145,16 +2158,26 @@ int wh_Client_EccSharedSecretRequest(whClientContext* ctx, whKeyId prv_key_id, dataPtr, WC_PK_TYPE_ECDH, ctx->cryptoAffinity); memset(req, 0, sizeof(*req)); - req->options = 0; + req->options = options; req->privateKeyId = prv_key_id; req->publicKeyId = pub_key_id; + req->flags = flags; + req->keyId = out_key_id; + if ((label != NULL) && (label_len > 0)) { + memcpy(req->label, label, label_len); + } 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) +/* Receive and parse an ECDH shared-secret response. + * + * If `out` is non-NULL the response is treated as export mode: the secret is + * copied to `out` and `*inout_size` is updated. If `out_key_id` is non-NULL + * the response is treated as cache mode: the assigned keyId is returned. */ +static int _EccSharedSecretResponse(whClientContext* ctx, uint8_t* out, + uint16_t* inout_size, whKeyId* out_key_id) { int ret; uint16_t group; @@ -2186,6 +2209,9 @@ int wh_Client_EccSharedSecretResponse(whClientContext* ctx, uint8_t* out, if (res_len < hdr_sz || res->sz > (res_len - hdr_sz)) { return WH_ERROR_ABORTED; } + if (out_key_id != NULL) { + *out_key_id = (whKeyId)res->keyId; + } if (inout_size != NULL) { if ((out != NULL) && (res->sz > *inout_size)) { /* Output buffer too small. Report required size and fail @@ -2202,34 +2228,64 @@ int wh_Client_EccSharedSecretResponse(whClientContext* ctx, uint8_t* out, return ret; } -int wh_Client_EccSharedSecret(whClientContext* ctx, ecc_key* priv_key, - ecc_key* pub_key, uint8_t* out, - uint16_t* inout_size) +int wh_Client_EccSharedSecretRequest(whClientContext* ctx, whKeyId prv_key_id, + whKeyId pub_key_id) { - int ret = WH_ERROR_OK; - uint8_t* dataPtr = NULL; - whMessageCrypto_EcdhRequest* req = NULL; + return _EccSharedSecretRequest(ctx, prv_key_id, pub_key_id, 0, + WH_NVM_FLAGS_EPHEMERAL, WH_KEYID_ERASED, + NULL, 0); +} - /* Transaction state */ - whKeyId prv_key_id; - int prv_evict = 0; - whKeyId pub_key_id; - int pub_evict = 0; +int wh_Client_EccSharedSecretResponse(whClientContext* ctx, uint8_t* out, + uint16_t* inout_size) +{ + return _EccSharedSecretResponse(ctx, out, inout_size, 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))) { +int wh_Client_EccSharedSecretCacheKeyRequest( + whClientContext* ctx, whKeyId prv_key_id, whKeyId pub_key_id, + whKeyId out_key_id, whNvmFlags flags, const uint8_t* label, + uint16_t label_len) +{ + if (flags & WH_NVM_FLAGS_EPHEMERAL) { + return WH_ERROR_BADARGS; + } + return _EccSharedSecretRequest(ctx, prv_key_id, pub_key_id, 0, flags, + out_key_id, label, label_len); +} + +int wh_Client_EccSharedSecretCacheKeyResponse(whClientContext* ctx, + whKeyId* out_key_id) +{ + if ((ctx == NULL) || (out_key_id == NULL)) { return WH_ERROR_BADARGS; } + return _EccSharedSecretResponse(ctx, NULL, NULL, out_key_id); +} + +/* Common path for blocking ECDH shared-secret APIs. + * + * Handles auto-importing client-local input keys to the server cache (with + * EVICT* options on the request so the server cleans them up), sending the + * request, polling the response, and tearing down on error. */ +static int _EccSharedSecretBlocking(whClientContext* ctx, ecc_key* priv_key, + ecc_key* pub_key, whNvmFlags flags, + uint8_t* out, uint16_t* inout_size, + whKeyId* inout_key_id, const uint8_t* label, + uint16_t label_len) +{ + int ret = WH_ERROR_OK; + whKeyId prv_key_id = WH_KEYID_ERASED; + whKeyId pub_key_id = WH_KEYID_ERASED; + int prv_evict = 0; + int pub_evict = 0; pub_key_id = WH_DEVCTX_TO_KEYID(pub_key->devCtx); - if ((ret == WH_ERROR_OK) && WH_KEYID_ISERASED(pub_key_id)) { - /* Must import the key to the server and evict it afterwards */ + if (WH_KEYID_ISERASED(pub_key_id)) { uint8_t keyLabel[] = "TempEccDh-pub"; - whNvmFlags flags = WH_NVM_FLAGS_USAGE_DERIVE; + whNvmFlags imp_flags = WH_NVM_FLAGS_USAGE_DERIVE; - ret = wh_Client_EccImportKey(ctx, pub_key, &pub_key_id, flags, + ret = wh_Client_EccImportKey(ctx, pub_key, &pub_key_id, imp_flags, sizeof(keyLabel), keyLabel); if (ret == WH_ERROR_OK) { pub_evict = 1; @@ -2238,11 +2294,10 @@ int wh_Client_EccSharedSecret(whClientContext* ctx, ecc_key* priv_key, prv_key_id = WH_DEVCTX_TO_KEYID(priv_key->devCtx); if ((ret == WH_ERROR_OK) && WH_KEYID_ISERASED(prv_key_id)) { - /* Must import the key to the server and evict it afterwards */ uint8_t keyLabel[] = "TempEccDh-prv"; - whNvmFlags flags = WH_NVM_FLAGS_USAGE_DERIVE; + whNvmFlags imp_flags = WH_NVM_FLAGS_USAGE_DERIVE; - ret = wh_Client_EccImportKey(ctx, priv_key, &prv_key_id, flags, + ret = wh_Client_EccImportKey(ctx, priv_key, &prv_key_id, imp_flags, sizeof(keyLabel), keyLabel); if (ret == WH_ERROR_OK) { prv_evict = 1; @@ -2250,57 +2305,27 @@ int wh_Client_EccSharedSecret(whClientContext* ctx, ecc_key* priv_key, } if (ret == WH_ERROR_OK) { - /* Request Message*/ - uint16_t group = WH_MESSAGE_GROUP_CRYPTO; - uint16_t action = WC_ALGO_TYPE_PK; - uint16_t req_len = - sizeof(whMessageCrypto_GenericRequestHeader) + sizeof(*req); uint32_t options = 0; + whKeyId out_key_id = + (inout_key_id != NULL) ? *inout_key_id : WH_KEYID_ERASED; - /* Get data pointer from the context to use as request/response storage - */ - dataPtr = wh_CommClient_GetDataPtr(ctx->comm); - if (dataPtr == NULL) { - return WH_ERROR_BADARGS; + if (pub_evict != 0) { + options |= WH_MESSAGE_CRYPTO_ECDH_OPTIONS_EVICTPUB; + } + if (prv_evict != 0) { + options |= WH_MESSAGE_CRYPTO_ECDH_OPTIONS_EVICTPRV; } - /* Setup generic header and get pointer to request data */ - req = (whMessageCrypto_EcdhRequest*)_createCryptoRequest( - dataPtr, WC_PK_TYPE_ECDH, ctx->cryptoAffinity); - - if (req_len <= WOLFHSM_CFG_COMM_DATA_LEN) { - if (pub_evict != 0) { - options |= WH_MESSAGE_CRYPTO_ECDH_OPTIONS_EVICTPUB; - } - if (prv_evict != 0) { - options |= WH_MESSAGE_CRYPTO_ECDH_OPTIONS_EVICTPRV; - } - - memset(req, 0, sizeof(*req)); - req->options = options; - req->privateKeyId = prv_key_id; - req->publicKeyId = pub_key_id; - - /* Send Request */ - ret = wh_Client_SendRequest(ctx, group, action, req_len, - (uint8_t*)dataPtr); - WH_DEBUG_CLIENT_VERBOSE("req sent. priv:%u pub:%u\n", - (unsigned int)req->privateKeyId, - (unsigned int)req->publicKeyId); - if (ret == WH_ERROR_OK) { - /* Server will evict. Reset our flags */ - pub_evict = prv_evict = 0; + ret = _EccSharedSecretRequest(ctx, prv_key_id, pub_key_id, options, + flags, out_key_id, label, label_len); + if (ret == WH_ERROR_OK) { + /* Server will evict the temp-imported input keys */ + pub_evict = prv_evict = 0; - /* Poll shared Response */ - do { - ret = - wh_Client_EccSharedSecretResponse(ctx, out, inout_size); - } while (ret == WH_ERROR_NOTREADY); - WH_DEBUG_CLIENT_VERBOSE("resp packet recv. ret:%d\n", ret); - } - } - else { - ret = WH_ERROR_BADARGS; + do { + ret = _EccSharedSecretResponse(ctx, out, inout_size, + inout_key_id); + } while (ret == WH_ERROR_NOTREADY); } } @@ -2311,10 +2336,40 @@ int wh_Client_EccSharedSecret(whClientContext* ctx, ecc_key* priv_key, if (prv_evict != 0) { (void)wh_Client_KeyEvict(ctx, prv_key_id); } - WH_DEBUG_CLIENT_VERBOSE("ret:%d\n", ret); return ret; } +int wh_Client_EccSharedSecret(whClientContext* ctx, ecc_key* priv_key, + ecc_key* pub_key, uint8_t* out, + uint16_t* inout_size) +{ + if ((ctx == NULL) || (pub_key == NULL) || (priv_key == NULL) || + ((out != NULL) && (inout_size == NULL))) { + return WH_ERROR_BADARGS; + } + + return _EccSharedSecretBlocking(ctx, priv_key, pub_key, + WH_NVM_FLAGS_EPHEMERAL, out, inout_size, + NULL, NULL, 0); +} + +int wh_Client_EccSharedSecretCacheKey(whClientContext* ctx, ecc_key* priv_key, + ecc_key* pub_key, whKeyId* inout_key_id, + whNvmFlags flags, const uint8_t* label, + uint16_t label_len) +{ + if ((ctx == NULL) || (pub_key == NULL) || (priv_key == NULL) || + (inout_key_id == NULL)) { + return WH_ERROR_BADARGS; + } + if (flags & WH_NVM_FLAGS_EPHEMERAL) { + return WH_ERROR_BADARGS; + } + + return _EccSharedSecretBlocking(ctx, priv_key, pub_key, flags, NULL, NULL, + inout_key_id, label, label_len); +} + int wh_Client_EccSignRequest(whClientContext* ctx, whKeyId keyId, const uint8_t* hash, uint16_t hash_len) @@ -3001,31 +3056,168 @@ int wh_Client_Curve25519MakeExportKey(whClientContext* ctx, uint16_t size, key); } -int wh_Client_Curve25519SharedSecret(whClientContext* ctx, - curve25519_key* priv_key, - curve25519_key* pub_key, int endian, - uint8_t* out, uint16_t* out_size) +/* Build and send a Curve25519 shared-secret request. See + * _EccSharedSecretRequest for flags/keyId/label semantics — identical contract. + */ +static int +_Curve25519SharedSecretRequest(whClientContext* ctx, whKeyId prv_key_id, + whKeyId pub_key_id, int endian, uint32_t options, + whNvmFlags flags, whKeyId out_key_id, + const uint8_t* label, uint16_t label_len) { - int ret = WH_ERROR_OK; + whMessageCrypto_Curve25519Request* req = NULL; + uint8_t* dataPtr = NULL; + uint16_t req_len; - /* Transaction state */ - whKeyId prv_key_id; - int prv_evict = 0; - whKeyId pub_key_id; - int pub_evict = 0; + if (ctx == NULL) { + return WH_ERROR_BADARGS; + } + if (WH_KEYID_ISERASED(prv_key_id) || WH_KEYID_ISERASED(pub_key_id)) { + return WH_ERROR_BADARGS; + } + if ((label_len > 0) && (label == NULL)) { + return WH_ERROR_BADARGS; + } + if (label_len > WH_NVM_LABEL_LEN) { + label_len = WH_NVM_LABEL_LEN; + } + + 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_Curve25519Request*)_createCryptoRequest( + dataPtr, WC_PK_TYPE_CURVE25519, ctx->cryptoAffinity); + + memset(req, 0, sizeof(*req)); + req->options = options; + req->privateKeyId = prv_key_id; + req->publicKeyId = pub_key_id; + req->endian = endian; + req->flags = flags; + req->keyId = out_key_id; + if ((label != NULL) && (label_len > 0)) { + memcpy(req->label, label, label_len); + } + + return wh_Client_SendRequest(ctx, WH_MESSAGE_GROUP_CRYPTO, WC_ALGO_TYPE_PK, + req_len, dataPtr); +} + +/* Receive and parse a Curve25519 shared-secret response. See + * _EccSharedSecretResponse for out/out_key_id semantics. */ +static int _Curve25519SharedSecretResponse(whClientContext* ctx, uint8_t* out, + uint16_t* inout_size, + whKeyId* out_key_id) +{ + int ret; + uint16_t group; + uint16_t action; + uint16_t res_len = 0; + uint8_t* dataPtr; + whMessageCrypto_Curve25519Response* 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_CURVE25519, (uint8_t**)&res); + if (ret >= 0) { + uint8_t* res_out = (uint8_t*)(res + 1); + const size_t hdr_sz = + sizeof(whMessageCrypto_GenericResponseHeader) + sizeof(*res); + if (res_len < hdr_sz || res->sz > (res_len - hdr_sz)) { + return WH_ERROR_ABORTED; + } + if (out_key_id != NULL) { + *out_key_id = (whKeyId)res->keyId; + } + if (inout_size != NULL) { + if ((out != NULL) && (res->sz > *inout_size)) { + *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); + WH_DEBUG_VERBOSE_HEXDUMP("[client] X25519:", res_out, res->sz); + } + } + } + return ret; +} - if ((ctx == NULL) || (pub_key == NULL) || (priv_key == NULL)) { +int wh_Client_Curve25519SharedSecretRequest(whClientContext* ctx, + whKeyId prv_key_id, + whKeyId pub_key_id, int endian) +{ + return _Curve25519SharedSecretRequest(ctx, prv_key_id, pub_key_id, endian, + 0, WH_NVM_FLAGS_EPHEMERAL, + WH_KEYID_ERASED, NULL, 0); +} + +int wh_Client_Curve25519SharedSecretResponse(whClientContext* ctx, uint8_t* out, + uint16_t* out_size) +{ + return _Curve25519SharedSecretResponse(ctx, out, out_size, NULL); +} + +int wh_Client_Curve25519SharedSecretCacheKeyRequest( + whClientContext* ctx, whKeyId prv_key_id, whKeyId pub_key_id, int endian, + whKeyId out_key_id, whNvmFlags flags, const uint8_t* label, + uint16_t label_len) +{ + if (flags & WH_NVM_FLAGS_EPHEMERAL) { return WH_ERROR_BADARGS; } + return _Curve25519SharedSecretRequest(ctx, prv_key_id, pub_key_id, endian, + 0, flags, out_key_id, label, + label_len); +} + +int wh_Client_Curve25519SharedSecretCacheKeyResponse(whClientContext* ctx, + whKeyId* out_key_id) +{ + if ((ctx == NULL) || (out_key_id == NULL)) { + return WH_ERROR_BADARGS; + } + return _Curve25519SharedSecretResponse(ctx, NULL, NULL, out_key_id); +} + +static int _Curve25519SharedSecretBlocking( + whClientContext* ctx, curve25519_key* priv_key, curve25519_key* pub_key, + int endian, whNvmFlags flags, uint8_t* out, uint16_t* out_size, + whKeyId* inout_key_id, const uint8_t* label, uint16_t label_len) +{ + int ret = WH_ERROR_OK; + whKeyId prv_key_id = WH_KEYID_ERASED; + whKeyId pub_key_id = WH_KEYID_ERASED; + int prv_evict = 0; + int pub_evict = 0; pub_key_id = WH_DEVCTX_TO_KEYID(pub_key->devCtx); - if ((ret == WH_ERROR_OK) && WH_KEYID_ISERASED(pub_key_id)) { - /* Must import the key to the server and evict it afterwards */ + if (WH_KEYID_ISERASED(pub_key_id)) { uint8_t keyLabel[] = "TempX25519-pub"; - whNvmFlags flags = WH_NVM_FLAGS_USAGE_DERIVE; + whNvmFlags imp_flags = WH_NVM_FLAGS_USAGE_DERIVE; - ret = wh_Client_Curve25519ImportKey(ctx, pub_key, &pub_key_id, flags, - sizeof(keyLabel), keyLabel); + ret = wh_Client_Curve25519ImportKey( + ctx, pub_key, &pub_key_id, imp_flags, sizeof(keyLabel), keyLabel); if (ret == WH_ERROR_OK) { pub_evict = 1; } @@ -3033,124 +3225,82 @@ int wh_Client_Curve25519SharedSecret(whClientContext* ctx, prv_key_id = WH_DEVCTX_TO_KEYID(priv_key->devCtx); if ((ret == WH_ERROR_OK) && WH_KEYID_ISERASED(prv_key_id)) { - /* Must import the key to the server and evict it afterwards */ uint8_t keyLabel[] = "TempX25519-prv"; - whNvmFlags flags = WH_NVM_FLAGS_USAGE_DERIVE; + whNvmFlags imp_flags = WH_NVM_FLAGS_USAGE_DERIVE; - ret = wh_Client_Curve25519ImportKey(ctx, priv_key, &prv_key_id, flags, - sizeof(keyLabel), keyLabel); + ret = wh_Client_Curve25519ImportKey( + ctx, priv_key, &prv_key_id, imp_flags, sizeof(keyLabel), keyLabel); if (ret == WH_ERROR_OK) { prv_evict = 1; } } if (ret == WH_ERROR_OK) { - whMessageCrypto_Curve25519Request* req = NULL; - uint8_t* dataPtr = NULL; - 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(*req); + uint32_t options = 0; + whKeyId out_key_id = + (inout_key_id != NULL) ? *inout_key_id : WH_KEYID_ERASED; - /* Get data pointer from the context to use as request/response storage - */ - dataPtr = wh_CommClient_GetDataPtr(ctx->comm); - if (dataPtr == NULL) { - return WH_ERROR_BADARGS; + if (pub_evict != 0) { + options |= WH_MESSAGE_CRYPTO_CURVE25519_OPTIONS_EVICTPUB; + } + if (prv_evict != 0) { + options |= WH_MESSAGE_CRYPTO_CURVE25519_OPTIONS_EVICTPRV; } - memset((uint8_t*)dataPtr, 0, WOLFHSM_CFG_COMM_DATA_LEN); - - /* Setup generic header and get pointer to request data */ - req = (whMessageCrypto_Curve25519Request*)_createCryptoRequest( - dataPtr, WC_PK_TYPE_CURVE25519, ctx->cryptoAffinity); - - if (req_len <= WOLFHSM_CFG_COMM_DATA_LEN) { - if (pub_evict != 0) { - options |= WH_MESSAGE_CRYPTO_CURVE25519_OPTIONS_EVICTPUB; - } - if (prv_evict != 0) { - options |= WH_MESSAGE_CRYPTO_CURVE25519_OPTIONS_EVICTPRV; - } - - memset(req, 0, sizeof(*req)); - req->options = options; - req->privateKeyId = prv_key_id; - req->publicKeyId = pub_key_id; - req->endian = endian; - /* Send Request */ - ret = wh_Client_SendRequest(ctx, group, action, req_len, - (uint8_t*)dataPtr); - WH_DEBUG_CLIENT_VERBOSE("req sent. priv:%u pub:%u\n", - (unsigned int)req->privateKeyId, - (unsigned int)req->publicKeyId); - if (ret == WH_ERROR_OK) { - whMessageCrypto_Curve25519Response* res = NULL; - uint16_t res_len; - /* Server will evict. Reset our flags */ - pub_evict = prv_evict = 0; + ret = _Curve25519SharedSecretRequest(ctx, prv_key_id, pub_key_id, + endian, options, flags, out_key_id, + label, label_len); + if (ret == WH_ERROR_OK) { + pub_evict = prv_evict = 0; - /* Recv Response */ - do { - ret = wh_Client_RecvResponse(ctx, &group, &action, &res_len, - (uint8_t*)dataPtr); - } 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_CURVE25519, - (uint8_t**)&res); - /* wolfCrypt allows positive error codes on success in some - * scenarios */ - 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)) { - ret = WH_ERROR_ABORTED; - } - if (out_size != NULL) { - if ((ret >= 0) && - (out != NULL) && (res->sz > *out_size)) { - /* Output buffer too small. Report required size - * and fail rather than silently truncating - * X25519 key material. */ - ret = WH_ERROR_BUFFER_SIZE; - } - /* Give caller the required size, even on failure */ - *out_size = res->sz; - if ((ret >= 0) && (out != NULL) && (res->sz > 0)) { - memcpy(out, res_out, res->sz); - WH_DEBUG_VERBOSE_HEXDUMP("[client] X25519:", - res_out, res->sz); - } - } - } - } - } - } - else { - ret = WH_ERROR_BADARGS; + do { + ret = _Curve25519SharedSecretResponse(ctx, out, out_size, + inout_key_id); + } while (ret == WH_ERROR_NOTREADY); } } - /* Evict the keys manually on error */ if (pub_evict != 0) { (void)wh_Client_KeyEvict(ctx, pub_key_id); } if (prv_evict != 0) { (void)wh_Client_KeyEvict(ctx, prv_key_id); } - WH_DEBUG_CLIENT_VERBOSE("ret:%d\n", ret); return ret; } + +int wh_Client_Curve25519SharedSecret(whClientContext* ctx, + curve25519_key* priv_key, + curve25519_key* pub_key, int endian, + uint8_t* out, uint16_t* out_size) +{ + if ((ctx == NULL) || (pub_key == NULL) || (priv_key == NULL) || + ((out != NULL) && (out_size == NULL))) { + return WH_ERROR_BADARGS; + } + + return _Curve25519SharedSecretBlocking(ctx, priv_key, pub_key, endian, + WH_NVM_FLAGS_EPHEMERAL, out, + out_size, NULL, NULL, 0); +} + +int wh_Client_Curve25519SharedSecretCacheKey( + whClientContext* ctx, curve25519_key* priv_key, curve25519_key* pub_key, + int endian, whKeyId* inout_key_id, whNvmFlags flags, const uint8_t* label, + uint16_t label_len) +{ + if ((ctx == NULL) || (pub_key == NULL) || (priv_key == NULL) || + (inout_key_id == NULL)) { + return WH_ERROR_BADARGS; + } + if (flags & WH_NVM_FLAGS_EPHEMERAL) { + return WH_ERROR_BADARGS; + } + + return _Curve25519SharedSecretBlocking(ctx, priv_key, pub_key, endian, + flags, NULL, NULL, inout_key_id, + label, label_len); +} #endif /* HAVE_CURVE25519 */ #ifdef HAVE_ED25519 diff --git a/src/wh_message_crypto.c b/src/wh_message_crypto.c index 526c23fa6..3e5bafc27 100644 --- a/src/wh_message_crypto.c +++ b/src/wh_message_crypto.c @@ -388,6 +388,11 @@ int wh_MessageCrypto_TranslateEcdhRequest( WH_T32(magic, dest, src, options); WH_T32(magic, dest, src, privateKeyId); WH_T32(magic, dest, src, publicKeyId); + WH_T32(magic, dest, src, flags); + WH_T32(magic, dest, src, keyId); + if (src != dest) { + memcpy(dest->label, src->label, sizeof(src->label)); + } return 0; } @@ -400,6 +405,7 @@ int wh_MessageCrypto_TranslateEcdhResponse( return WH_ERROR_BADARGS; } WH_T32(magic, dest, src, sz); + WH_T32(magic, dest, src, keyId); return 0; } @@ -525,6 +531,11 @@ int wh_MessageCrypto_TranslateCurve25519Request( WH_T32(magic, dest, src, privateKeyId); WH_T32(magic, dest, src, publicKeyId); WH_T32(magic, dest, src, endian); + WH_T32(magic, dest, src, flags); + WH_T32(magic, dest, src, keyId); + if (src != dest) { + memcpy(dest->label, src->label, sizeof(src->label)); + } return 0; } @@ -537,6 +548,7 @@ int wh_MessageCrypto_TranslateCurve25519Response( return WH_ERROR_BADARGS; } WH_T32(magic, dest, src, sz); + WH_T32(magic, dest, src, keyId); return 0; } diff --git a/src/wh_server_crypto.c b/src/wh_server_crypto.c index 4240c4a97..fe4378c7f 100644 --- a/src/wh_server_crypto.c +++ b/src/wh_server_crypto.c @@ -984,12 +984,15 @@ static int _HandleEccSharedSecret(whServerContext* ctx, uint16_t magic, uint16_t inSize, void* cryptoDataOut, uint16_t* outSize) { - (void)inSize; - int ret = WH_ERROR_OK; ecc_key pub_key[1]; ecc_key prv_key[1]; whMessageCrypto_EcdhRequest req; + whKeyId out_key_id = WH_KEYID_ERASED; + + if (inSize < sizeof(whMessageCrypto_EcdhRequest)) { + return WH_ERROR_BADARGS; + } /* Translate request */ ret = wh_MessageCrypto_TranslateEcdhRequest( @@ -1006,6 +1009,8 @@ static int _HandleEccSharedSecret(whServerContext* ctx, uint16_t magic, WH_KEYTYPE_CRYPTO, ctx->comm->client_id, req.publicKeyId); whKeyId prv_key_id = wh_KeyId_TranslateFromClient( WH_KEYTYPE_CRYPTO, ctx->comm->client_id, req.privateKeyId); + whNvmFlags flags = (whNvmFlags)req.flags; + int cache = !(flags & WH_NVM_FLAGS_EPHEMERAL); /* Validate key usage policy for key derivation (private key) */ if (!WH_KEYID_ISERASED(prv_key_id)) { @@ -1047,6 +1052,41 @@ static int _HandleEccSharedSecret(whServerContext* ctx, uint16_t magic, } wc_ecc_free(pub_key); } + + /* If caching, move the secret out of the response buffer into a cache + * slot and return only its keyId. */ + if ((ret == WH_ERROR_OK) && cache) { + out_key_id = wh_KeyId_TranslateFromClient( + WH_KEYTYPE_CRYPTO, ctx->comm->client_id, req.keyId); + /* Hold the NVM lock so id allocation and cache import are atomic + * with respect to other server contexts under THREADSAFE. */ + ret = WH_SERVER_NVM_LOCK(ctx); + if (ret == WH_ERROR_OK) { + if (WH_KEYID_ISERASED(out_key_id)) { + ret = wh_Server_KeystoreGetUniqueId(ctx, &out_key_id); + } + if (ret == WH_ERROR_OK) { + ret = wh_Server_KeyCacheImportRaw(ctx, res_out, res_len, + out_key_id, flags, + WH_NVM_LABEL_LEN, req.label); + } + (void)WH_SERVER_NVM_UNLOCK(ctx); + } /* WH_SERVER_NVM_LOCK() */ + /* Scrub the secret from the response buffer regardless of import + * success/failure. */ + memset(res_out, 0, res_len); + /* If the cached output id collides with an auto-imported input id, + * suppress the matching eviction so cleanup does not delete the + * just-cached secret. */ + if (ret == WH_ERROR_OK) { + if (evict_pub && (out_key_id == pub_key_id)) { + evict_pub = 0; + } + if (evict_prv && (out_key_id == prv_key_id)) { + evict_prv = 0; + } + } + } cleanup: if (evict_pub) { /* User requested to evict from cache, even if the call failed */ @@ -1058,12 +1098,22 @@ static int _HandleEccSharedSecret(whServerContext* ctx, uint16_t magic, } if (ret == 0) { whMessageCrypto_EcdhResponse res; - res.sz = res_len; + uint16_t payload_len; + if (cache) { + res.sz = 0; + res.keyId = wh_KeyId_TranslateToClient(out_key_id); + payload_len = 0; + } + else { + res.sz = res_len; + res.keyId = 0; + payload_len = (uint16_t)res_len; + } wh_MessageCrypto_TranslateEcdhResponse( magic, &res, (whMessageCrypto_EcdhResponse*)cryptoDataOut); - *outSize = sizeof(whMessageCrypto_EcdhResponse) + res_len; + *outSize = sizeof(whMessageCrypto_EcdhResponse) + payload_len; } return ret; } @@ -1321,11 +1371,10 @@ static int _HandleRng(whServerContext* ctx, uint16_t magic, int devId, } #endif /* WC_NO_RNG */ -#ifdef HAVE_HKDF -int wh_Server_HkdfKeyCacheImport(whServerContext* ctx, const uint8_t* keyData, - uint32_t keySize, whKeyId keyId, - whNvmFlags flags, uint16_t label_len, - uint8_t* label) +int wh_Server_KeyCacheImportRaw(whServerContext* ctx, const uint8_t* keyData, + uint32_t keySize, whKeyId keyId, + whNvmFlags flags, uint16_t label_len, + uint8_t* label) { int ret = WH_ERROR_OK; uint8_t* cacheBuf; @@ -1340,12 +1389,8 @@ int wh_Server_HkdfKeyCacheImport(whServerContext* ctx, const uint8_t* keyData, ret = wh_Server_KeystoreGetCacheSlotChecked(ctx, keyId, keySize, &cacheBuf, &cacheMeta); if (ret == WH_ERROR_OK) { - /* Copy the key data to cache buffer */ memcpy(cacheBuf, keyData, keySize); - } - if (ret == WH_ERROR_OK) { - /* Set metadata */ cacheMeta->id = keyId; cacheMeta->len = keySize; cacheMeta->flags = flags; @@ -1359,6 +1404,16 @@ int wh_Server_HkdfKeyCacheImport(whServerContext* ctx, const uint8_t* keyData, return ret; } +#ifdef HAVE_HKDF +int wh_Server_HkdfKeyCacheImport(whServerContext* ctx, const uint8_t* keyData, + uint32_t keySize, whKeyId keyId, + whNvmFlags flags, uint16_t label_len, + uint8_t* label) +{ + return wh_Server_KeyCacheImportRaw(ctx, keyData, keySize, keyId, flags, + label_len, label); +} + #endif /* HAVE_HKDF */ #ifdef HAVE_CMAC_KDF @@ -1778,14 +1833,17 @@ static int _HandleCurve25519SharedSecret(whServerContext* ctx, uint16_t magic, uint16_t inSize, void* cryptoDataOut, uint16_t* outSize) { - (void)inSize; - int ret; curve25519_key priv[1] = {0}; curve25519_key pub[1] = {0}; whMessageCrypto_Curve25519Request req; whMessageCrypto_Curve25519Response res; + whKeyId out_key_id = WH_KEYID_ERASED; + + if (inSize < sizeof(whMessageCrypto_Curve25519Request)) { + return WH_ERROR_BADARGS; + } /* Translate request */ ret = wh_MessageCrypto_TranslateCurve25519Request( @@ -1803,6 +1861,8 @@ static int _HandleCurve25519SharedSecret(whServerContext* ctx, uint16_t magic, whKeyId prv_key_id = wh_KeyId_TranslateFromClient( WH_KEYTYPE_CRYPTO, ctx->comm->client_id, req.privateKeyId); int endian = req.endian; + whNvmFlags flags = (whNvmFlags)req.flags; + int cache = !(flags & WH_NVM_FLAGS_EPHEMERAL); /* Validate key usage policy for key derivation (private key) */ if (!WH_KEYID_ISERASED(prv_key_id)) { @@ -1846,6 +1906,41 @@ static int _HandleCurve25519SharedSecret(whServerContext* ctx, uint16_t magic, } wc_curve25519_free(priv); } + + /* If caching, move the secret out of the response buffer into a cache + * slot and return only its keyId. */ + if ((ret == WH_ERROR_OK) && cache) { + out_key_id = wh_KeyId_TranslateFromClient( + WH_KEYTYPE_CRYPTO, ctx->comm->client_id, req.keyId); + /* Hold the NVM lock so id allocation and cache import are atomic + * with respect to other server contexts under THREADSAFE. */ + ret = WH_SERVER_NVM_LOCK(ctx); + if (ret == WH_ERROR_OK) { + if (WH_KEYID_ISERASED(out_key_id)) { + ret = wh_Server_KeystoreGetUniqueId(ctx, &out_key_id); + } + if (ret == WH_ERROR_OK) { + ret = wh_Server_KeyCacheImportRaw(ctx, res_out, res_len, + out_key_id, flags, + WH_NVM_LABEL_LEN, req.label); + } + (void)WH_SERVER_NVM_UNLOCK(ctx); + } /* WH_SERVER_NVM_LOCK() */ + /* Scrub the secret from the response buffer regardless of import + * success/failure. */ + memset(res_out, 0, res_len); + /* If the cached output id collides with an auto-imported input id, + * suppress the matching eviction so cleanup does not delete the + * just-cached secret. */ + if (ret == WH_ERROR_OK) { + if (evict_pub && (out_key_id == pub_key_id)) { + evict_pub = 0; + } + if (evict_prv && (out_key_id == prv_key_id)) { + evict_prv = 0; + } + } + } cleanup: if (evict_pub) { /* User requested to evict from cache, even if the call failed */ @@ -1856,13 +1951,23 @@ static int _HandleCurve25519SharedSecret(whServerContext* ctx, uint16_t magic, (void)wh_Server_KeystoreEvictKey(ctx, prv_key_id); } if (ret == 0) { - res.sz = res_len; + uint16_t payload_len; + if (cache) { + res.sz = 0; + res.keyId = wh_KeyId_TranslateToClient(out_key_id); + payload_len = 0; + } + else { + res.sz = res_len; + res.keyId = 0; + payload_len = (uint16_t)res_len; + } wh_MessageCrypto_TranslateCurve25519Response( magic, &res, (whMessageCrypto_Curve25519Response*)cryptoDataOut); - *outSize = sizeof(whMessageCrypto_Curve25519Response) + res_len; + *outSize = sizeof(whMessageCrypto_Curve25519Response) + payload_len; } return ret; } diff --git a/test/wh_test_check_struct_padding.c b/test/wh_test_check_struct_padding.c index 5d8ceaeee..0d1c388a9 100644 --- a/test/wh_test_check_struct_padding.c +++ b/test/wh_test_check_struct_padding.c @@ -102,6 +102,7 @@ whMessageCrypto_RsaRequest pkRsaReq; whMessageCrypto_RsaGetSizeRequest pkRsaGetSizeReq; whMessageCrypto_EccKeyGenRequest pkEckgReq; whMessageCrypto_EcdhRequest pkEcdhReq; +whMessageCrypto_Curve25519Request pkCurve25519Req; whMessageCrypto_EccSignRequest pkEccSignReq; whMessageCrypto_EccVerifyRequest pkEccVerifyReq; whMessageCrypto_EccCheckRequest pkEccCheckReq; @@ -114,6 +115,7 @@ whMessageCrypto_RsaResponse pkRsaRes; whMessageCrypto_RsaGetSizeResponse pkRsaGetSizeRes; whMessageCrypto_EccKeyGenResponse pkEckgRes; whMessageCrypto_EcdhResponse pkEcdhRes; +whMessageCrypto_Curve25519Response pkCurve25519Res; whMessageCrypto_EccSignResponse pkEccSignRes; whMessageCrypto_EccVerifyResponse pkEccVerifyRes; whMessageCrypto_EccCheckResponse pkEccCheckRes; diff --git a/test/wh_test_crypto.c b/test/wh_test_crypto.c index 6b8d4ff6b..f7db01f1c 100644 --- a/test/wh_test_crypto.c +++ b/test/wh_test_crypto.c @@ -2450,6 +2450,346 @@ static int whTest_CryptoEccSharedSecretAsync_OneCurve(whClientContext* ctx, } return ret; } + +/** + * Test the cache-mode ECDH shared-secret API. + * + * For each curve: + * 1. Generate two HSM ECC keys. + * 2. Compute the shared secret in export mode and capture the secret bytes. + * 3. Compute the same shared secret in cache mode (USAGE_DERIVE) and read it + * back via wh_Client_KeyExport. The cached bytes must match the export-mode + * secret exactly. + * 4. Verify the BADARGS guards: EPHEMERAL flag, NULL inout id. + * 5. Repeat the round-trip via the async CacheKeyRequest/Response pair. + */ +static int whTest_CryptoEccSharedSecretCacheKey_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 pubB[1] = {0}; + uint8_t pubBx[ECC_MAXSIZE] = {0}; + uint8_t pubBy[ECC_MAXSIZE] = {0}; + word32 pubBxLen = 0; + word32 pubByLen = 0; + uint8_t exportSecret[ECC_MAXSIZE] = {0}; + uint16_t exportSecretLen = sizeof(exportSecret); + uint8_t cachedSecret[ECC_MAXSIZE] = {0}; + uint16_t cachedSecretLen = sizeof(cachedSecret); + uint8_t labelOut[WH_NVM_LABEL_LEN] = {0}; + whKeyId privAId = WH_KEYID_ERASED; + whKeyId privBId = WH_KEYID_ERASED; + whKeyId pubBId = WH_KEYID_ERASED; + whKeyId secretId = WH_KEYID_ERASED; + int keyAInit = 0; + int keyBInit = 0; + int pubBInit = 0; + uint8_t secretLabel[] = "TestEcdhCachedSecret"; + int ret = WH_ERROR_OK; + + WH_TEST_PRINT(" Testing cache-mode ECDH %s curve...\n", name); + + pubBxLen = pubByLen = keySize; + + /* Generate two keys A and B, then import both private and public halves */ + 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 lbl[] = "TestEcdhCachePrivA"; + privAId = WH_KEYID_ERASED; + ret = wh_Client_EccImportKey( + ctx, keyA, &privAId, WH_NVM_FLAGS_USAGE_DERIVE, sizeof(lbl), lbl); + } + 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 lbl[] = "TestEcdhCachePrivB"; + privBId = WH_KEYID_ERASED; + ret = wh_Client_EccImportKey( + ctx, keyB, &privBId, WH_NVM_FLAGS_USAGE_DERIVE, sizeof(lbl), lbl); + } + if (ret == 0) { + ret = + wc_ecc_export_public_raw(keyB, pubBx, &pubBxLen, pubBy, &pubByLen); + } + 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) { + uint8_t lbl[] = "TestEcdhCachePubB"; + ret = wh_Client_EccImportKey( + ctx, pubB, &pubBId, WH_NVM_FLAGS_USAGE_DERIVE, sizeof(lbl), lbl); + } + + /* Reference: compute the secret in export mode using (privA, pubB) */ + if (ret == 0) { + ret = wh_Client_EccSharedSecretRequest(ctx, privAId, pubBId); + if (ret == WH_ERROR_OK) { + do { + ret = wh_Client_EccSharedSecretResponse(ctx, exportSecret, + &exportSecretLen); + } while (ret == WH_ERROR_NOTREADY); + } + } + + /* Cache mode: compute the same secret with (privA, pubB), get a keyId */ + if (ret == 0) { + secretId = WH_KEYID_ERASED; + ret = wh_Client_EccSharedSecretCacheKeyRequest( + ctx, privAId, pubBId, secretId, WH_NVM_FLAGS_USAGE_DERIVE, + secretLabel, sizeof(secretLabel)); + if (ret == WH_ERROR_OK) { + do { + ret = wh_Client_EccSharedSecretCacheKeyResponse(ctx, &secretId); + } while (ret == WH_ERROR_NOTREADY); + } + if ((ret == WH_ERROR_OK) && WH_KEYID_ISERASED(secretId)) { + WH_ERROR_PRINT( + "%s: server returned erased keyId from cache-mode ECDH\n", + name); + ret = WH_ERROR_ABORTED; + } + } + + /* Read back the cached secret and compare with the export-mode reference */ + if (ret == 0) { + cachedSecretLen = sizeof(cachedSecret); + ret = wh_Client_KeyExport(ctx, secretId, labelOut, sizeof(labelOut), + cachedSecret, &cachedSecretLen); + if (ret != WH_ERROR_OK) { + WH_ERROR_PRINT( + "%s: failed to export cached ECDH secret (keyId=0x%x): %d\n", + name, (unsigned)secretId, ret); + } + } + if (ret == 0) { + if ((cachedSecretLen != exportSecretLen) || + (memcmp(cachedSecret, exportSecret, exportSecretLen) != 0)) { + WH_ERROR_PRINT("%s: cached ECDH secret does not match export\n", + name); + ret = WH_ERROR_ABORTED; + } + if (memcmp(labelOut, secretLabel, sizeof(secretLabel)) != 0) { + WH_ERROR_PRINT("%s: cached ECDH secret label mismatch\n", name); + ret = WH_ERROR_ABORTED; + } + } + if (!WH_KEYID_ISERASED(secretId)) { + (void)wh_Client_KeyEvict(ctx, secretId); + secretId = WH_KEYID_ERASED; + } + + /* Cache mode with caller-supplied keyId: server must honor the slot */ + if (ret == 0) { + whKeyId requestedId = 0x42; + whKeyId returnedId = requestedId; + uint8_t suppliedCached[ECC_MAXSIZE] = {0}; + uint16_t suppliedCachedLen = sizeof(suppliedCached); + uint8_t suppliedLabelOut[WH_NVM_LABEL_LEN] = {0}; + + ret = wh_Client_EccSharedSecretCacheKeyRequest( + ctx, privAId, pubBId, requestedId, WH_NVM_FLAGS_USAGE_DERIVE, + secretLabel, sizeof(secretLabel)); + if (ret == WH_ERROR_OK) { + do { + ret = + wh_Client_EccSharedSecretCacheKeyResponse(ctx, &returnedId); + } while (ret == WH_ERROR_NOTREADY); + } + if ((ret == WH_ERROR_OK) && (returnedId != requestedId)) { + WH_ERROR_PRINT("%s: caller-supplied keyId not honored " + "(asked 0x%x, got 0x%x)\n", + name, (unsigned)requestedId, (unsigned)returnedId); + ret = WH_ERROR_ABORTED; + } + if (ret == 0) { + ret = wh_Client_KeyExport(ctx, returnedId, suppliedLabelOut, + sizeof(suppliedLabelOut), suppliedCached, + &suppliedCachedLen); + } + if (ret == 0) { + if ((suppliedCachedLen != exportSecretLen) || + (memcmp(suppliedCached, exportSecret, exportSecretLen) != 0)) { + WH_ERROR_PRINT( + "%s: caller-supplied-keyId cached secret does not match " + "reference\n", + name); + ret = WH_ERROR_ABORTED; + } + } + if (!WH_KEYID_ISERASED(returnedId)) { + (void)wh_Client_KeyEvict(ctx, returnedId); + } + } + + /* Cache mode with NONEXPORTABLE: export must be rejected */ + if (ret == 0) { + whKeyId nonExportId = WH_KEYID_ERASED; + uint8_t dummyBuf[ECC_MAXSIZE] = {0}; + uint16_t dummyBufLen = sizeof(dummyBuf); + uint8_t dummyLabel[WH_NVM_LABEL_LEN] = {0}; + + ret = wh_Client_EccSharedSecretCacheKeyRequest( + ctx, privAId, pubBId, nonExportId, + WH_NVM_FLAGS_NONEXPORTABLE | WH_NVM_FLAGS_USAGE_DERIVE, secretLabel, + sizeof(secretLabel)); + if (ret == WH_ERROR_OK) { + do { + ret = wh_Client_EccSharedSecretCacheKeyResponse(ctx, + &nonExportId); + } while (ret == WH_ERROR_NOTREADY); + } + if ((ret == WH_ERROR_OK) && WH_KEYID_ISERASED(nonExportId)) { + WH_ERROR_PRINT("%s: NONEXPORTABLE cache returned erased keyId\n", + name); + ret = WH_ERROR_ABORTED; + } + if (ret == 0) { + int rc = + wh_Client_KeyExport(ctx, nonExportId, dummyLabel, + sizeof(dummyLabel), dummyBuf, &dummyBufLen); + if (rc != WH_ERROR_ACCESS) { + WH_ERROR_PRINT( + "%s: KeyExport on NONEXPORTABLE secret returned %d " + "(expected WH_ERROR_ACCESS)\n", + name, rc); + ret = WH_ERROR_ABORTED; + } + } + if (!WH_KEYID_ISERASED(nonExportId)) { + (void)wh_Client_KeyEvict(ctx, nonExportId); + } + } + + /* Guard: EPHEMERAL flag must be rejected client-side */ + if (ret == 0) { + whKeyId tmp = WH_KEYID_ERASED; + int rc = wh_Client_EccSharedSecretCacheKey( + ctx, keyA, pubB, &tmp, + WH_NVM_FLAGS_EPHEMERAL | WH_NVM_FLAGS_USAGE_DERIVE, NULL, 0); + if (rc != WH_ERROR_BADARGS) { + WH_ERROR_PRINT( + "%s: CacheKey with EPHEMERAL returned %d (expected BADARGS)\n", + name, rc); + ret = -1; + } + } + /* Guard: NULL inout_key_id */ + if (ret == 0) { + int rc = wh_Client_EccSharedSecretCacheKey( + ctx, keyA, pubB, NULL, WH_NVM_FLAGS_USAGE_DERIVE, NULL, 0); + if (rc != WH_ERROR_BADARGS) { + WH_ERROR_PRINT("%s: CacheKey NULL inout returned %d (expected " + "BADARGS)\n", + name, rc); + ret = -1; + } + } + + /* Blocking wrapper path with auto-imported local keys */ + if (ret == 0) { + ecc_key keyC[1] = {0}; + ecc_key keyD[1] = {0}; + whKeyId blockId = WH_KEYID_ERASED; + uint8_t blockRefSecret[ECC_MAXSIZE] = {0}; + uint16_t blockRefSecretLen = sizeof(blockRefSecret); + uint8_t blockCachedSecret[ECC_MAXSIZE] = {0}; + uint16_t blockCachedSecretLen = sizeof(blockCachedSecret); + uint8_t blockLabelOut[WH_NVM_LABEL_LEN] = {0}; + + ret = wc_ecc_init_ex(keyC, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_ecc_make_key(rng, keySize, keyC); + } + if (ret == 0) { + ret = wc_ecc_init_ex(keyD, NULL, INVALID_DEVID); + } + if (ret == 0) { + ret = wc_ecc_make_key(rng, keySize, keyD); + } + /* Reference: export-mode blocking wrapper auto-imports the same keys */ + if (ret == 0) { + ret = wh_Client_EccSharedSecret(ctx, keyC, keyD, blockRefSecret, + &blockRefSecretLen); + } + if (ret == 0) { + ret = wh_Client_EccSharedSecretCacheKey( + ctx, keyC, keyD, &blockId, WH_NVM_FLAGS_USAGE_DERIVE, NULL, 0); + } + if ((ret == 0) && WH_KEYID_ISERASED(blockId)) { + WH_ERROR_PRINT("%s: blocking CacheKey returned erased keyId\n", + name); + ret = WH_ERROR_ABORTED; + } + if (ret == 0) { + ret = wh_Client_KeyExport(ctx, blockId, blockLabelOut, + sizeof(blockLabelOut), blockCachedSecret, + &blockCachedSecretLen); + if (ret != WH_ERROR_OK) { + WH_ERROR_PRINT("%s: failed to export blocking-cached secret " + "(keyId=0x%x): %d\n", + name, (unsigned)blockId, ret); + } + } + if (ret == 0) { + if ((blockCachedSecretLen != blockRefSecretLen) || + (memcmp(blockCachedSecret, blockRefSecret, blockRefSecretLen) != + 0)) { + WH_ERROR_PRINT( + "%s: blocking-wrapper cached secret does not match " + "reference\n", + name); + ret = WH_ERROR_ABORTED; + } + } + if (!WH_KEYID_ISERASED(blockId)) { + (void)wh_Client_KeyEvict(ctx, blockId); + } + wc_ecc_free(keyD); + wc_ecc_free(keyC); + } + + if (ret == 0) { + WH_TEST_PRINT(" cache ECDH %s: PASS\n", name); + } + + /* Cleanup */ + if (!WH_KEYID_ISERASED(pubBId)) { + (void)wh_Client_KeyEvict(ctx, pubBId); + } + 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 (keyBInit) { + wc_ecc_free(keyB); + } + if (keyAInit) { + wc_ecc_free(keyA); + } + return ret; +} #endif /* HAVE_ECC_DHE */ /** @@ -2717,6 +3057,10 @@ static int whTest_CryptoEccAsync(whClientContext* ctx, WC_RNG* rng) ret = whTest_CryptoEccSharedSecretAsync_OneCurve( ctx, rng, WH_TEST_ECC_P256_KEY_SIZE, ECC_SECP256R1, "P-256"); } + if (ret == 0) { + ret = whTest_CryptoEccSharedSecretCacheKey_OneCurve( + ctx, rng, WH_TEST_ECC_P256_KEY_SIZE, ECC_SECP256R1, "P-256"); + } #endif if (ret == 0) { ret = whTest_CryptoEccMakeKeyAsync_OneCurve( @@ -2734,6 +3078,10 @@ static int whTest_CryptoEccAsync(whClientContext* ctx, WC_RNG* rng) ret = whTest_CryptoEccSharedSecretAsync_OneCurve( ctx, rng, WH_TEST_ECC_P384_KEY_SIZE, ECC_SECP384R1, "P-384"); } + if (ret == 0) { + ret = whTest_CryptoEccSharedSecretCacheKey_OneCurve( + ctx, rng, WH_TEST_ECC_P384_KEY_SIZE, ECC_SECP384R1, "P-384"); + } #endif if (ret == 0) { ret = whTest_CryptoEccMakeKeyAsync_OneCurve( @@ -2751,6 +3099,10 @@ static int whTest_CryptoEccAsync(whClientContext* ctx, WC_RNG* rng) ret = whTest_CryptoEccSharedSecretAsync_OneCurve( ctx, rng, WH_TEST_ECC_P521_KEY_SIZE, ECC_SECP521R1, "P-521"); } + if (ret == 0) { + ret = whTest_CryptoEccSharedSecretCacheKey_OneCurve( + ctx, rng, WH_TEST_ECC_P521_KEY_SIZE, ECC_SECP521R1, "P-521"); + } #endif if (ret == 0) { ret = whTest_CryptoEccMakeKeyAsync_OneCurve( @@ -3417,6 +3769,329 @@ static int whTest_CryptoCurve25519(whClientContext* ctx, int devId, WC_RNG* rng) return ret; } +/** + * Test the cache-mode X25519 shared-secret API. + * + * 1. Generate two X25519 keys on the server, plus a public-only copy of B. + * 2. Compute the secret in export mode via the async Request/Response pair and + * capture the bytes. + * 3. Compute the same secret in cache mode (USAGE_DERIVE), read it back, and + * confirm the bytes match. + * 4. Exercise the blocking CacheKey wrapper with locally-generated keys so the + * auto-import and eviction path is covered. + * 5. Verify BADARGS guards. + */ +static int whTest_CryptoCurve25519SharedSecretCacheKey(whClientContext* ctx, + int devId, WC_RNG* rng) +{ + int ret = 0; + curve25519_key key_a[1] = {0}; + curve25519_key key_b[1] = {0}; + curve25519_key pubB[1] = {0}; + uint8_t exportSecret[CURVE25519_KEYSIZE] = {0}; + uint16_t exportSecretLen = sizeof(exportSecret); + uint8_t cachedSecret[CURVE25519_KEYSIZE] = {0}; + uint16_t cachedSecretLen = sizeof(cachedSecret); + uint8_t labelOut[WH_NVM_LABEL_LEN] = {0}; + uint8_t secretLabel[] = "TestX25519CachedSecret"; + whKeyId privAId = WH_KEYID_ERASED; + whKeyId privBId = WH_KEYID_ERASED; + whKeyId pubBId = WH_KEYID_ERASED; + whKeyId secretId = WH_KEYID_ERASED; + int pubBInit = 0; + int key_size = CURVE25519_KEYSIZE; + + WH_TEST_PRINT("Testing cache-mode CURVE25519...\n"); + + ret = wc_curve25519_init_ex(key_a, NULL, devId); + if (ret == 0) { + ret = wc_curve25519_init_ex(key_b, NULL, devId); + } + if (ret == 0) { + ret = wh_Client_Curve25519MakeCacheKey( + ctx, (uint16_t)key_size, &privAId, WH_NVM_FLAGS_USAGE_DERIVE, + (const uint8_t*)"X25519CachePrivA", 16); + } + if (ret == 0) { + ret = wh_Client_Curve25519MakeCacheKey( + ctx, (uint16_t)key_size, &privBId, WH_NVM_FLAGS_USAGE_DERIVE, + (const uint8_t*)"X25519CachePrivB", 16); + } + if (ret == 0) { + ret = wh_Client_Curve25519SetKeyId(key_a, privAId); + } + if (ret == 0) { + ret = wh_Client_Curve25519SetKeyId(key_b, privBId); + } + + /* Public-only copy of B, imported as its own cache slot, so shared-secret + * calls receive a real (private, public-only) pair instead of two private + * keyIds. */ + if (ret == 0) { + ret = wc_curve25519_init_ex(pubB, NULL, INVALID_DEVID); + if (ret == 0) { + pubBInit = 1; + ret = wh_Client_Curve25519ExportPublicKey(ctx, privBId, pubB, 0, + NULL); + } + } + if (ret == 0) { + uint8_t lbl[] = "X25519CachePubB"; + ret = wh_Client_Curve25519ImportKey( + ctx, pubB, &pubBId, WH_NVM_FLAGS_USAGE_DERIVE, sizeof(lbl), lbl); + } + + /* Export-mode reference via the async Request/Response pair. Doubles as + * direct coverage for wh_Client_Curve25519SharedSecretRequest/Response. */ + if (ret == 0) { + ret = wh_Client_Curve25519SharedSecretRequest(ctx, privAId, pubBId, + EC25519_LITTLE_ENDIAN); + if (ret == WH_ERROR_OK) { + do { + ret = wh_Client_Curve25519SharedSecretResponse( + ctx, exportSecret, &exportSecretLen); + } while (ret == WH_ERROR_NOTREADY); + } + } + + /* Cache mode via the async pair */ + if (ret == 0) { + secretId = WH_KEYID_ERASED; + ret = wh_Client_Curve25519SharedSecretCacheKeyRequest( + ctx, privAId, pubBId, EC25519_LITTLE_ENDIAN, secretId, + WH_NVM_FLAGS_USAGE_DERIVE, secretLabel, sizeof(secretLabel)); + if (ret == WH_ERROR_OK) { + do { + ret = wh_Client_Curve25519SharedSecretCacheKeyResponse( + ctx, &secretId); + } while (ret == WH_ERROR_NOTREADY); + } + if ((ret == 0) && WH_KEYID_ISERASED(secretId)) { + WH_ERROR_PRINT( + "X25519: server returned erased keyId from cache mode\n"); + ret = WH_ERROR_ABORTED; + } + } + + /* Read back cached secret and compare with export-mode reference */ + if (ret == 0) { + cachedSecretLen = sizeof(cachedSecret); + ret = wh_Client_KeyExport(ctx, secretId, labelOut, sizeof(labelOut), + cachedSecret, &cachedSecretLen); + } + if (ret == 0) { + if ((cachedSecretLen != exportSecretLen) || + (memcmp(cachedSecret, exportSecret, exportSecretLen) != 0)) { + WH_ERROR_PRINT( + "X25519: cached secret does not match export-mode secret\n"); + ret = WH_ERROR_ABORTED; + } + if (memcmp(labelOut, secretLabel, sizeof(secretLabel)) != 0) { + WH_ERROR_PRINT("X25519: cached secret label mismatch\n"); + ret = WH_ERROR_ABORTED; + } + } + if (!WH_KEYID_ISERASED(secretId)) { + (void)wh_Client_KeyEvict(ctx, secretId); + secretId = WH_KEYID_ERASED; + } + + /* Cache mode with caller-supplied keyId: server must honor the slot */ + if (ret == 0) { + whKeyId requestedId = 0x42; + whKeyId returnedId = requestedId; + uint8_t suppliedCached[CURVE25519_KEYSIZE] = {0}; + uint16_t suppliedCachedLen = sizeof(suppliedCached); + uint8_t suppliedLabelOut[WH_NVM_LABEL_LEN] = {0}; + + ret = wh_Client_Curve25519SharedSecretCacheKeyRequest( + ctx, privAId, pubBId, EC25519_LITTLE_ENDIAN, requestedId, + WH_NVM_FLAGS_USAGE_DERIVE, secretLabel, sizeof(secretLabel)); + if (ret == WH_ERROR_OK) { + do { + ret = wh_Client_Curve25519SharedSecretCacheKeyResponse( + ctx, &returnedId); + } while (ret == WH_ERROR_NOTREADY); + } + if ((ret == WH_ERROR_OK) && (returnedId != requestedId)) { + WH_ERROR_PRINT("X25519: caller-supplied keyId not honored " + "(asked 0x%x, got 0x%x)\n", + (unsigned)requestedId, (unsigned)returnedId); + ret = WH_ERROR_ABORTED; + } + if (ret == 0) { + ret = wh_Client_KeyExport(ctx, returnedId, suppliedLabelOut, + sizeof(suppliedLabelOut), suppliedCached, + &suppliedCachedLen); + } + if (ret == 0) { + if ((suppliedCachedLen != exportSecretLen) || + (memcmp(suppliedCached, exportSecret, exportSecretLen) != 0)) { + WH_ERROR_PRINT( + "X25519: caller-supplied-keyId cached secret does not " + "match reference\n"); + ret = WH_ERROR_ABORTED; + } + } + if (!WH_KEYID_ISERASED(returnedId)) { + (void)wh_Client_KeyEvict(ctx, returnedId); + } + } + + /* Cache mode with NONEXPORTABLE: export must be rejected */ + if (ret == 0) { + whKeyId nonExportId = WH_KEYID_ERASED; + uint8_t dummyBuf[CURVE25519_KEYSIZE] = {0}; + uint16_t dummyBufLen = sizeof(dummyBuf); + uint8_t dummyLabel[WH_NVM_LABEL_LEN] = {0}; + + ret = wh_Client_Curve25519SharedSecretCacheKeyRequest( + ctx, privAId, pubBId, EC25519_LITTLE_ENDIAN, nonExportId, + WH_NVM_FLAGS_NONEXPORTABLE | WH_NVM_FLAGS_USAGE_DERIVE, secretLabel, + sizeof(secretLabel)); + if (ret == WH_ERROR_OK) { + do { + ret = wh_Client_Curve25519SharedSecretCacheKeyResponse( + ctx, &nonExportId); + } while (ret == WH_ERROR_NOTREADY); + } + if ((ret == WH_ERROR_OK) && WH_KEYID_ISERASED(nonExportId)) { + WH_ERROR_PRINT( + "X25519: NONEXPORTABLE cache returned erased keyId\n"); + ret = WH_ERROR_ABORTED; + } + if (ret == 0) { + int rc = + wh_Client_KeyExport(ctx, nonExportId, dummyLabel, + sizeof(dummyLabel), dummyBuf, &dummyBufLen); + if (rc != WH_ERROR_ACCESS) { + WH_ERROR_PRINT( + "X25519: KeyExport on NONEXPORTABLE secret returned %d " + "(expected WH_ERROR_ACCESS)\n", + rc); + ret = WH_ERROR_ABORTED; + } + } + if (!WH_KEYID_ISERASED(nonExportId)) { + (void)wh_Client_KeyEvict(ctx, nonExportId); + } + } + + /* Guards */ + if (ret == 0) { + whKeyId tmp = WH_KEYID_ERASED; + int rc = wh_Client_Curve25519SharedSecretCacheKey( + ctx, key_a, key_b, EC25519_LITTLE_ENDIAN, &tmp, + WH_NVM_FLAGS_EPHEMERAL | WH_NVM_FLAGS_USAGE_DERIVE, NULL, 0); + if (rc != WH_ERROR_BADARGS) { + WH_ERROR_PRINT( + "X25519: CacheKey with EPHEMERAL returned %d (expected " + "BADARGS)\n", + rc); + ret = -1; + } + } + if (ret == 0) { + int rc = wh_Client_Curve25519SharedSecretCacheKey( + ctx, key_a, key_b, EC25519_LITTLE_ENDIAN, NULL, + WH_NVM_FLAGS_USAGE_DERIVE, NULL, 0); + if (rc != WH_ERROR_BADARGS) { + WH_ERROR_PRINT( + "X25519: CacheKey NULL inout returned %d (expected BADARGS)\n", + rc); + ret = -1; + } + } + + /* Blocking wrapper path with auto-imported local keys */ + if (ret == 0) { + curve25519_key keyC[1] = {0}; + curve25519_key keyD[1] = {0}; + int keyCInit = 0; + int keyDInit = 0; + whKeyId blockId = WH_KEYID_ERASED; + uint8_t blockRefSecret[CURVE25519_KEYSIZE] = {0}; + uint16_t blockRefSecretLen = sizeof(blockRefSecret); + uint8_t blockCachedSecret[CURVE25519_KEYSIZE] = {0}; + uint16_t blockCachedSecretLen = sizeof(blockCachedSecret); + uint8_t blockLabelOut[WH_NVM_LABEL_LEN] = {0}; + + ret = wc_curve25519_init_ex(keyC, NULL, INVALID_DEVID); + if (ret == 0) { + keyCInit = 1; + ret = wc_curve25519_make_key(rng, (word32)key_size, keyC); + } + if (ret == 0) { + ret = wc_curve25519_init_ex(keyD, NULL, INVALID_DEVID); + } + if (ret == 0) { + keyDInit = 1; + ret = wc_curve25519_make_key(rng, (word32)key_size, keyD); + } + /* Reference: blocking export wrapper auto-imports the same local keys + */ + if (ret == 0) { + ret = wh_Client_Curve25519SharedSecret( + ctx, keyC, keyD, EC25519_LITTLE_ENDIAN, blockRefSecret, + &blockRefSecretLen); + } + if (ret == 0) { + ret = wh_Client_Curve25519SharedSecretCacheKey( + ctx, keyC, keyD, EC25519_LITTLE_ENDIAN, &blockId, + WH_NVM_FLAGS_USAGE_DERIVE, NULL, 0); + } + if ((ret == 0) && WH_KEYID_ISERASED(blockId)) { + WH_ERROR_PRINT("X25519: blocking CacheKey returned erased keyId\n"); + ret = WH_ERROR_ABORTED; + } + if (ret == 0) { + ret = wh_Client_KeyExport(ctx, blockId, blockLabelOut, + sizeof(blockLabelOut), blockCachedSecret, + &blockCachedSecretLen); + } + if (ret == 0) { + if ((blockCachedSecretLen != blockRefSecretLen) || + (memcmp(blockCachedSecret, blockRefSecret, blockRefSecretLen) != + 0)) { + WH_ERROR_PRINT( + "X25519: blocking-wrapper cached secret does not match " + "reference\n"); + ret = WH_ERROR_ABORTED; + } + } + if (!WH_KEYID_ISERASED(blockId)) { + (void)wh_Client_KeyEvict(ctx, blockId); + } + if (keyDInit) { + wc_curve25519_free(keyD); + } + if (keyCInit) { + wc_curve25519_free(keyC); + } + } + + /* Cleanup */ + if (!WH_KEYID_ISERASED(pubBId)) { + (void)wh_Client_KeyEvict(ctx, pubBId); + } + 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_curve25519_free(pubB); + } + wc_curve25519_free(key_b); + wc_curve25519_free(key_a); + + if (ret == 0) { + WH_TEST_PRINT("CURVE25519 cache-mode SUCCESS\n"); + } + return ret; +} + static int whTest_CryptoCurve25519ExportPublic(whClientContext* ctx, int devId, WC_RNG* rng) { @@ -13333,6 +14008,14 @@ int whTest_CryptoClientConfig(whClientConfig* config) if (ret == 0) { ret = whTest_CryptoCurve25519(client, WH_DEV_ID, rng); } + if (ret == 0) { + ret = + whTest_CryptoCurve25519SharedSecretCacheKey(client, WH_DEV_ID, rng); + if (ret != 0) { + WH_ERROR_PRINT( + "Curve25519 shared-secret cache-key test failed: %d\n", ret); + } + } if (ret == 0) { ret = whTest_CryptoCurve25519ExportPublic(client, WH_DEV_ID, rng); if (ret != 0) { diff --git a/wolfhsm/wh_client_crypto.h b/wolfhsm/wh_client_crypto.h index 704dad68a..27ca89f49 100644 --- a/wolfhsm/wh_client_crypto.h +++ b/wolfhsm/wh_client_crypto.h @@ -313,6 +313,61 @@ int wh_Client_Curve25519SharedSecret(whClientContext* ctx, curve25519_key* priv_key, curve25519_key* pub_key, int endian, uint8_t* out, uint16_t *out_size); +/** + * @brief Send a request to compute an X25519 shared secret that will be + * returned to the client. + * + * The matching response is retrieved with + * wh_Client_Curve25519SharedSecretResponse. Both input keys must already be + * cached on the server. + */ +int wh_Client_Curve25519SharedSecretRequest(whClientContext* ctx, + whKeyId prv_key_id, + whKeyId pub_key_id, int endian); + +/** + * @brief Retrieve the response to a wh_Client_Curve25519SharedSecretRequest. + */ +int wh_Client_Curve25519SharedSecretResponse(whClientContext* ctx, uint8_t* out, + uint16_t* out_size); + +/** + * @brief Compute an X25519 shared secret and cache it on the server. + * + * On return, *inout_key_id holds the keyId the secret is stored under (the + * server allocates one when *inout_key_id is WH_KEYID_ERASED on entry). + * `flags` must not include WH_NVM_FLAGS_EPHEMERAL. + * + * @param[in] ctx Pointer to the client context + * @param[in] priv_key Private key (cached on the server or local) + * @param[in] pub_key Public key (cached on the server or local) + * @param[in] endian EC25519_BIG_ENDIAN or EC25519_LITTLE_ENDIAN + * @param[in,out] inout_key_id Cache slot id (in) / assigned id (out) + * @param[in] flags whNvmFlags applied to the cached secret + * @param[in] label Optional label for the cached secret + * @param[in] label_len Length of `label`, up to WH_NVM_LABEL_LEN + */ +int wh_Client_Curve25519SharedSecretCacheKey( + whClientContext* ctx, curve25519_key* priv_key, curve25519_key* pub_key, + int endian, whKeyId* inout_key_id, whNvmFlags flags, const uint8_t* label, + uint16_t label_len); + +/** + * @brief Async variant: send the request half of SharedSecretCacheKey. + * Both input keys must already be cached. + */ +int wh_Client_Curve25519SharedSecretCacheKeyRequest( + whClientContext* ctx, whKeyId prv_key_id, whKeyId pub_key_id, int endian, + whKeyId out_key_id, whNvmFlags flags, const uint8_t* label, + uint16_t label_len); + +/** + * @brief Async variant: retrieve the response of SharedSecretCacheKeyRequest. + * On success, *out_key_id receives the assigned cache id. + */ +int wh_Client_Curve25519SharedSecretCacheKeyResponse(whClientContext* ctx, + whKeyId* out_key_id); + #endif /* HAVE_CURVE25519 */ #ifdef HAVE_ECC @@ -695,6 +750,42 @@ int wh_Client_EccSharedSecretRequest(whClientContext* ctx, whKeyId prv_key_id, int wh_Client_EccSharedSecretResponse(whClientContext* ctx, uint8_t* out, uint16_t* inout_size); +/** + * @brief Compute an ECDH shared secret and cache it on the server. + * + * On return, *inout_key_id holds the keyId the secret is stored under (the + * server allocates one when *inout_key_id is WH_KEYID_ERASED on entry). + * `flags` must not include WH_NVM_FLAGS_EPHEMERAL. + * + * @param[in] ctx Client context + * @param[in] priv_key Private ECC key (cached on the server or local) + * @param[in] pub_key Public ECC key (cached on the server or local) + * @param[in,out] inout_key_id Cache slot id (in) / assigned id (out) + * @param[in] flags whNvmFlags applied to the cached secret + * @param[in] label Optional label for the cached secret + * @param[in] label_len Length of `label`, up to WH_NVM_LABEL_LEN + */ +int wh_Client_EccSharedSecretCacheKey(whClientContext* ctx, ecc_key* priv_key, + ecc_key* pub_key, whKeyId* inout_key_id, + whNvmFlags flags, const uint8_t* label, + uint16_t label_len); + +/** + * @brief Async variant: send the request half of SharedSecretCacheKey. + * Both input keys must already be cached on the server. + */ +int wh_Client_EccSharedSecretCacheKeyRequest( + whClientContext* ctx, whKeyId prv_key_id, whKeyId pub_key_id, + whKeyId out_key_id, whNvmFlags flags, const uint8_t* label, + uint16_t label_len); + +/** + * @brief Async variant: retrieve the response of SharedSecretCacheKeyRequest. + * On success, *out_key_id receives the assigned cache id. + */ +int wh_Client_EccSharedSecretCacheKeyResponse(whClientContext* ctx, + whKeyId* out_key_id); + /** * @brief Async request half of an ECC server-side keygen that caches the new * key in the server. diff --git a/wolfhsm/wh_message_crypto.h b/wolfhsm/wh_message_crypto.h index 11699de3e..2ff2e3f15 100644 --- a/wolfhsm/wh_message_crypto.h +++ b/wolfhsm/wh_message_crypto.h @@ -505,12 +505,19 @@ typedef struct { #define WH_MESSAGE_CRYPTO_ECDH_OPTIONS_EVICTPRV (1 << 1) uint32_t privateKeyId; uint32_t publicKeyId; + uint32_t flags; /* whNvmFlags. EPHEMERAL -> return secret to client. + Otherwise cache on server. */ + uint32_t keyId; /* Requested cache slot, or WH_KEYID_ERASED to + have the server allocate one. Only used on the + cache path. */ + uint8_t label[WH_NVM_LABEL_LEN]; } whMessageCrypto_EcdhRequest; /* ECDH Response */ typedef struct { - uint32_t sz; - /* Data follows: + uint32_t sz; /* >0: secret bytes follow. 0: secret was cached. */ + uint32_t keyId; /* Assigned cache id when sz==0; 0 otherwise. */ + /* Data follows when sz > 0: * uint8_t out[sz]; */ } whMessageCrypto_EcdhResponse; @@ -638,13 +645,17 @@ typedef struct { uint32_t privateKeyId; uint32_t publicKeyId; uint32_t endian; + uint32_t flags; /* whNvmFlags. EPHEMERAL -> return secret to client. + Otherwise cache on server. */ + uint32_t keyId; /* Requested cache slot, or WH_KEYID_ERASED. */ + uint8_t label[WH_NVM_LABEL_LEN]; } whMessageCrypto_Curve25519Request; /* Curve25519 Response */ typedef struct { - uint32_t sz; - uint8_t WH_PAD[4]; - /* Data follows: + uint32_t sz; /* >0: secret bytes follow. 0: secret was cached. */ + uint32_t keyId; /* Assigned cache id when sz==0; 0 otherwise. */ + /* Data follows when sz > 0: * uint8_t out[sz]; */ } whMessageCrypto_Curve25519Response; diff --git a/wolfhsm/wh_server_crypto.h b/wolfhsm/wh_server_crypto.h index ef601daee..01ce0ea84 100644 --- a/wolfhsm/wh_server_crypto.h +++ b/wolfhsm/wh_server_crypto.h @@ -114,6 +114,14 @@ int wh_Server_MlKemKeyCacheExport(whServerContext* ctx, whKeyId keyId, MlKemKey* key); #endif /* WOLFSSL_HAVE_MLKEM */ +/* Store raw key bytes into a server key cache slot with optional metadata. + * Used by KDF outputs (HKDF, CMAC-KDF) and key-agreement outputs + * (ECDH, X25519) when the caller asked for server-resident storage. */ +int wh_Server_KeyCacheImportRaw(whServerContext* ctx, const uint8_t* keyData, + uint32_t keySize, whKeyId keyId, + whNvmFlags flags, uint16_t label_len, + uint8_t* label); + #ifdef HAVE_HKDF /* Store HKDF output into a server key cache with optional metadata */ int wh_Server_HkdfKeyCacheImport(whServerContext* ctx, const uint8_t* keyData,