Skip to content

Commit ae2e991

Browse files
committed
Add ExportPubKey support for ML-KEM
1 parent 99ef557 commit ae2e991

11 files changed

Lines changed: 604 additions & 6 deletions

File tree

CLAUDE.md

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
wolfHSM is a portable client-server framework for hardware cryptography, non-volatile memory (NVM), and isolated secure processing. The server runs in a trusted environment; clients communicate via a message-based protocol. wolfCrypt APIs are automatically offloaded to the server as remote procedure calls. Targeted at automotive HSM-enabled microcontrollers but runs on any platform with a secure execution environment.
8+
9+
## Build Commands
10+
11+
All builds use Make. wolfSSL must be available at `../../wolfssl` relative to this repo (override with `WOLFSSL_DIR=`).
12+
13+
```bash
14+
# Build and run tests (from repo root or test/)
15+
cd test && make -j && make run
16+
17+
# Common build flags (combine as needed)
18+
make DEBUG=1 # Basic debug output
19+
make DEBUG_VERBOSE=1 # Verbose debug (implies DEBUG)
20+
make ASAN=1 # Address Sanitizer
21+
make TSAN=1 # Thread Sanitizer (requires THREADSAFE=1)
22+
make THREADSAFE=1 # Thread-safe mode with locking
23+
make DMA=1 # DMA support
24+
make SHE=1 # AUTOSAR SHE extensions
25+
make TLS=1 # TLS transport
26+
make COVERAGE=1 # Code coverage instrumentation
27+
make TESTWOLFCRYPT=1 # Run wolfCrypt test suite as client
28+
make CLIENT_ONLY=1 # Client-only (connects to remote server)
29+
make STRESS=1 # Stress testing (requires THREADSAFE=1)
30+
31+
# Coverage report
32+
cd test && make coverage # Builds, runs, generates ../coverage/index.html
33+
34+
# Static analysis
35+
make scan # scan-build (excludes wolfSSL/wolfCrypt)
36+
37+
# Benchmarks
38+
cd benchmark && make -j && make run
39+
40+
# Examples
41+
cd examples/posix/wh_posix_server && make
42+
cd examples/posix/wh_posix_client && make
43+
44+
# NVM tool
45+
cd tools/whnvmtool && make && make test
46+
```
47+
48+
## Code Formatting
49+
50+
Uses clang-format-15 (CI enforced). 4-space indent, 80-column limit, braces on new line after functions only, `else` on new line. Pointer alignment left (`int* p`). Run: `clang-format-15 -i <file>`.
51+
52+
## Architecture
53+
54+
### Client-Server Model
55+
56+
- **Client** (`src/wh_client*.c`, `wolfhsm/wh_client*.h`): Sends requests to the server. Registers wolfCrypt crypto callbacks so wolfCrypt calls are transparently offloaded. Device IDs: `WH_DEV_ID` (0x5748534D) for standard ops, `WH_DEV_ID_DMA` (0x57444D41) for DMA ops.
57+
- **Server** (`src/wh_server*.c`, `wolfhsm/wh_server*.h`): Dispatches incoming requests to handlers for crypto, NVM, keystore, SHE, certificates, image management, counters, and custom callbacks.
58+
- **Communication** (`src/wh_comm.c`, `wolfhsm/wh_comm.h`): 8-byte header + variable payload (default 1280 bytes, configurable via `WOLFHSM_CFG_COMM_DATA_LEN`). Message groups: COMM, NVM, KEY, CRYPTO, IMAGE, PKCS11, SHE, COUNTER, CUSTOM, CRYPTO_DMA, CERT.
59+
60+
### Transport Layer
61+
62+
Pluggable transport backends behind a common interface:
63+
- `wh_transport_mem` — shared memory buffer (used in tests)
64+
- POSIX port: shared memory (pthread), TCP sockets, TLS over TCP
65+
66+
### Non-Volatile Memory (NVM)
67+
68+
Log-based flash storage with crash recovery (`wh_nvm_flash_log.c`). Objects have metadata (ID, access, flags, label). RAM flash simulator (`wh_flash_ramsim.c`) used for testing.
69+
70+
### Key Management
71+
72+
RAM key cache + persistent NVM storage. Keys have access control (per-client), usage policies (encrypt/decrypt/sign/verify/wrap/derive), and flags (non-exportable, sensitive, non-modifiable, etc.).
73+
74+
### Platform Ports
75+
76+
`port/` contains platform-specific implementations. `port/posix/` is the reference port (flash sim, transport, locks, logging). `port/skeleton/` is a template for new ports.
77+
78+
## Configuration
79+
80+
Three-layer configuration (lowest to highest priority):
81+
82+
1. **`wolfhsm/wh_settings.h`** — defaults for all `WOLFHSM_CFG_*` macros
83+
2. **`wolfhsm_cfg.h`** — per-project overrides (included when `WOLFHSM_CFG` is defined). Test version at `test/config/wolfhsm_cfg.h`
84+
3. **Compiler `-D` flags** — set via Makefile variables
85+
86+
wolfCrypt is configured separately via `user_settings.h` (test version at `test/config/user_settings.h`).
87+
88+
## Testing
89+
90+
Tests are in `test/`. Each module has its own `wh_test_<module>.c` file with a corresponding header. The main driver is `wh_test.c`. Tests must be portable (no system dependencies) except POSIX-specific tests gated by `WOLFHSM_CFG_TEST_POSIX`.
91+
92+
## Error Codes
93+
94+
Defined in `wolfhsm/wh_error.h`:
95+
- General: -2000 to -2010 (BADARGS, NOTREADY, ABORTED, TIMEOUT, etc.)
96+
- NVM: -2100 to -2105 (LOCKED, ACCESS, NOTFOUND, NOSPACE, etc.)
97+
- SHE: -2200 to -2211
98+
99+
All wolfHSM functions return `int` where 0 is success (`WH_ERROR_OK`) and negative values are errors.

benchmark/config/user_settings.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,6 @@ extern "C" {
135135

136136
/* Dilithium Options */
137137
#define HAVE_DILITHIUM
138-
#define WOLFSSL_WC_DILITHIUM /* use wolfCrypt implementation, not libOQS */
139138
#define WOLFSSL_SHA3
140139
#define WOLFSSL_SHAKE128
141140
#define WOLFSSL_SHAKE256
@@ -157,7 +156,6 @@ extern "C" {
157156

158157
/* ML-KEM Options */
159158
#define WOLFSSL_HAVE_MLKEM
160-
#define WOLFSSL_WC_MLKEM
161159

162160
/** Composite features */
163161
#define HAVE_HKDF

docs/src-ja/chapter05.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,8 +320,12 @@ int wh_Client_Curve25519ExportPublicKey(whClientContext* ctx, whKeyId keyId,
320320
curve25519_key* key, uint16_t label_len, uint8_t* label);
321321
int wh_Client_MlDsaExportPublicKey(whClientContext* ctx, whKeyId keyId,
322322
MlDsaKey* key, uint16_t label_len, uint8_t* label);
323+
int wh_Client_MlKemExportPublicKey(whClientContext* ctx, whKeyId keyId,
324+
MlKemKey* key, uint16_t label_len, uint8_t* label);
323325
```
324326

327+
DERベースのヘルパーとは異なり、ML-KEMは公開鍵を生のワイヤフォーマットバイト列として送信します(サイズは`WC_ML_KEM_MAX_PUBLIC_KEY_SIZE`)。クライアント側のデシリアライザはサポートされているレベルを順に試行します。汎用APIである`wh_Client_KeyExportPublic[Dma]`を直接呼び出す場合は、対応するアルゴリズムセレクタ`WH_KEY_ALGO_MLKEM`を使用します。
328+
325329
例: HSM上でRSAの鍵ペアを生成し秘密鍵を`NONEXPORTABLE`でマークしたうえで、クライアント側で公開鍵を取得し、HSMがキャッシュされた秘密鍵で生成した署名を検証する。
326330

327331
```c
@@ -354,6 +358,8 @@ int wh_Client_KeyExportPublicDma(whClientContext* c, whKeyId keyId,
354358
355359
int wh_Client_MlDsaExportPublicKeyDma(whClientContext* ctx, whKeyId keyId,
356360
MlDsaKey* key, uint16_t label_len, uint8_t* label);
361+
int wh_Client_MlKemExportPublicKeyDma(whClientContext* ctx, whKeyId keyId,
362+
MlKemKey* key, uint16_t label_len, uint8_t* label);
357363
```
358364

359365
`wh_Client_KeyExportPublicDma`は汎用トランスポートで、呼び出し側は生の公開鍵DERを受け取って自身でデシリアライズします。`wh_Client_MlDsaExportPublicKeyDma`は既存の`wh_Client_MlDsaExportKeyDma`に対応するML-DSA専用ヘルパーで、ML-DSAの大きな公開鍵DERを通信バッファにコピーせずに済ませたい場合に使用できます。

docs/src/chapter05.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,8 +313,16 @@ int wh_Client_Curve25519ExportPublicKey(whClientContext* ctx, whKeyId keyId,
313313
curve25519_key* key, uint16_t label_len, uint8_t* label);
314314
int wh_Client_MlDsaExportPublicKey(whClientContext* ctx, whKeyId keyId,
315315
MlDsaKey* key, uint16_t label_len, uint8_t* label);
316+
int wh_Client_MlKemExportPublicKey(whClientContext* ctx, whKeyId keyId,
317+
MlKemKey* key, uint16_t label_len, uint8_t* label);
316318
```
317319
320+
Unlike the DER-based helpers, ML-KEM emits the raw wire-format public key
321+
bytes (sized by `WC_ML_KEM_MAX_PUBLIC_KEY_SIZE`); the client deserializer
322+
probes the supported levels. `WH_KEY_ALGO_MLKEM` is the matching algo
323+
selector when calling the generic `wh_Client_KeyExportPublic[Dma]` API
324+
directly.
325+
318326
Example: generate an RSA keypair on the HSM with the private key marked
319327
`NONEXPORTABLE`, obtain the public key on the client, and verify a signature
320328
the HSM produced using the cached private key.
@@ -359,6 +367,8 @@ int wh_Client_KeyExportPublicDma(whClientContext* c, whKeyId keyId,
359367

360368
int wh_Client_MlDsaExportPublicKeyDma(whClientContext* ctx, whKeyId keyId,
361369
MlDsaKey* key, uint16_t label_len, uint8_t* label);
370+
int wh_Client_MlKemExportPublicKeyDma(whClientContext* ctx, whKeyId keyId,
371+
MlKemKey* key, uint16_t label_len, uint8_t* label);
362372
```
363373
364374
`wh_Client_KeyExportPublicDma` is the generic transport — callers receive

examples/posix/wh_posix_server/user_settings.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,6 @@ extern "C" {
131131

132132
/* Dilithium Options */
133133
#define HAVE_DILITHIUM
134-
#define WOLFSSL_WC_DILITHIUM /* use wolfCrypt implementation, not libOQS */
135134
#define WOLFSSL_SHA3
136135
#define WOLFSSL_SHAKE128
137136
#define WOLFSSL_SHAKE256
@@ -153,7 +152,6 @@ extern "C" {
153152

154153
/* ML-KEM Options */
155154
#define WOLFSSL_HAVE_MLKEM
156-
#define WOLFSSL_WC_MLKEM
157155

158156
/** Composite features */
159157
#define HAVE_HKDF

src/wh_client_crypto.c

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8196,6 +8196,26 @@ int wh_Client_MlKemExportKey(whClientContext* ctx, whKeyId keyId, MlKemKey* key,
81968196
return ret;
81978197
}
81988198

8199+
int wh_Client_MlKemExportPublicKey(whClientContext* ctx, whKeyId keyId,
8200+
MlKemKey* key, uint16_t label_len,
8201+
uint8_t* label)
8202+
{
8203+
int ret;
8204+
byte buffer[WC_ML_KEM_MAX_PUBLIC_KEY_SIZE] = {0};
8205+
uint16_t buffer_len = sizeof(buffer);
8206+
8207+
if ((ctx == NULL) || WH_KEYID_ISERASED(keyId) || (key == NULL)) {
8208+
return WH_ERROR_BADARGS;
8209+
}
8210+
8211+
ret = wh_Client_KeyExportPublic(ctx, keyId, WH_KEY_ALGO_MLKEM, label,
8212+
label_len, buffer, &buffer_len);
8213+
if (ret == WH_ERROR_OK) {
8214+
ret = wh_Crypto_MlKemDeserializeKey(buffer, buffer_len, key);
8215+
}
8216+
return ret;
8217+
}
8218+
81998219
static int _MlKemMakeKey(whClientContext* ctx, int level,
82008220
whKeyId* inout_key_id, whNvmFlags flags,
82018221
uint16_t label_len, uint8_t* label, MlKemKey* key)
@@ -8614,6 +8634,27 @@ int wh_Client_MlKemExportKeyDma(whClientContext* ctx, whKeyId keyId,
86148634
return ret;
86158635
}
86168636

8637+
int wh_Client_MlKemExportPublicKeyDma(whClientContext* ctx, whKeyId keyId,
8638+
MlKemKey* key, uint16_t label_len,
8639+
uint8_t* label)
8640+
{
8641+
int ret;
8642+
byte buffer[WC_ML_KEM_MAX_PUBLIC_KEY_SIZE] = {0};
8643+
uint16_t buffer_len = sizeof(buffer);
8644+
8645+
if ((ctx == NULL) || WH_KEYID_ISERASED(keyId) || (key == NULL)) {
8646+
return WH_ERROR_BADARGS;
8647+
}
8648+
8649+
ret = wh_Client_KeyExportPublicDma(ctx, keyId, WH_KEY_ALGO_MLKEM, buffer,
8650+
buffer_len, label, label_len,
8651+
&buffer_len);
8652+
if (ret == WH_ERROR_OK) {
8653+
ret = wh_Crypto_MlKemDeserializeKey(buffer, buffer_len, key);
8654+
}
8655+
return ret;
8656+
}
8657+
86178658
static int _MlKemMakeKeyDma(whClientContext* ctx, int level,
86188659
whKeyId* inout_key_id, whNvmFlags flags,
86198660
uint16_t label_len, uint8_t* label, MlKemKey* key)

src/wh_server_keystore.c

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@
6767
#ifdef HAVE_DILITHIUM
6868
#include "wolfssl/wolfcrypt/dilithium.h"
6969
#endif
70+
#ifdef WOLFSSL_HAVE_MLKEM
71+
#include "wolfssl/wolfcrypt/wc_mlkem.h"
72+
#endif
7073

7174
static int _FindInCache(whServerContext* server, whKeyId keyId, int* out_index,
7275
int* out_big, uint8_t** out_buffer,
@@ -541,6 +544,47 @@ static int _ExportCurve25519PublicKey(whServerContext* server, whKeyId keyId,
541544
}
542545
#endif
543546

547+
#ifdef WOLFSSL_HAVE_MLKEM
548+
static int _ExportMlkemPublicKey(whServerContext* server, whKeyId keyId,
549+
uint8_t* out, uint16_t* outSz)
550+
{
551+
int ret = WH_ERROR_OK;
552+
MlKemKey key[1];
553+
word32 pubSize;
554+
/* Pick the lowest compiled-in level as the initial hint;
555+
* wh_Crypto_MlKemDeserializeKey (called via
556+
* wh_Server_MlKemKeyCacheExport) probes the remaining enabled levels. */
557+
#ifndef WOLFSSL_NO_ML_KEM_512
558+
const int initLevel = WC_ML_KEM_512;
559+
#elif !defined(WOLFSSL_NO_ML_KEM_768)
560+
const int initLevel = WC_ML_KEM_768;
561+
#else
562+
const int initLevel = WC_ML_KEM_1024;
563+
#endif
564+
565+
ret = wc_MlKemKey_Init(key, initLevel, NULL, INVALID_DEVID);
566+
if (ret == 0) {
567+
ret = wh_Server_MlKemKeyCacheExport(server, keyId, key);
568+
if (ret == 0) {
569+
ret = wc_MlKemKey_PublicKeySize(key, &pubSize);
570+
if (ret == 0) {
571+
if ((uint32_t)pubSize > (uint32_t)*outSz) {
572+
ret = WH_ERROR_NOSPACE;
573+
}
574+
else {
575+
ret = wc_MlKemKey_EncodePublicKey(key, out, pubSize);
576+
if (ret == 0) {
577+
*outSz = (uint16_t)pubSize;
578+
}
579+
}
580+
}
581+
}
582+
wc_MlKemKey_Free(key);
583+
}
584+
return ret;
585+
}
586+
#endif /* WOLFSSL_HAVE_MLKEM */
587+
544588
int wh_Server_KeystoreGetUniqueId(whServerContext* server, whNvmId* inout_id)
545589
{
546590
int ret = WH_ERROR_OK;
@@ -2088,6 +2132,12 @@ int wh_Server_HandleKeyRequest(whServerContext* server, uint16_t magic,
20882132
stage, &stageMax);
20892133
break;
20902134
#endif /* HAVE_CURVE25519 */
2135+
#ifdef WOLFSSL_HAVE_MLKEM
2136+
case WH_KEY_ALGO_MLKEM:
2137+
ret = _ExportMlkemPublicKey(server, serverKeyId,
2138+
stage, &stageMax);
2139+
break;
2140+
#endif /* WOLFSSL_HAVE_MLKEM */
20912141
default:
20922142
ret = WH_ERROR_BADARGS;
20932143
break;
@@ -2285,6 +2335,12 @@ int wh_Server_HandleKeyRequest(whServerContext* server, uint16_t magic,
22852335
serverKeyId, out, &max_der);
22862336
break;
22872337
#endif /* HAVE_CURVE25519 */
2338+
#ifdef WOLFSSL_HAVE_MLKEM
2339+
case WH_KEY_ALGO_MLKEM:
2340+
ret = _ExportMlkemPublicKey(server, serverKeyId,
2341+
out, &max_der);
2342+
break;
2343+
#endif /* WOLFSSL_HAVE_MLKEM */
22882344
default:
22892345
ret = WH_ERROR_BADARGS;
22902346
break;

test/config/user_settings.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,14 +134,12 @@
134134

135135
/* Dilithium Options */
136136
#define HAVE_DILITHIUM
137-
#define WOLFSSL_WC_DILITHIUM /* use wolfCrypt implementation, not libOQS */
138137
#define WOLFSSL_SHA3
139138
#define WOLFSSL_SHAKE128
140139
#define WOLFSSL_SHAKE256
141140

142141
/* ML-KEM Options */
143142
#define WOLFSSL_HAVE_MLKEM
144-
#define WOLFSSL_WC_MLKEM
145143

146144
/* Ed25519 Options */
147145
#define HAVE_ED25519

0 commit comments

Comments
 (0)