Skip to content

Commit 1245712

Browse files
author
Emma Stensland
committed
mldsa: cache the host public key on the CTX slot
1 parent c24261b commit 1245712

3 files changed

Lines changed: 152 additions & 15 deletions

File tree

‎src/internal.c‎

Lines changed: 139 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1510,6 +1510,9 @@ void CtxResourceFree(WOLFSSH_CTX* ctx)
15101510
ctx->privateKey[i].key = NULL;
15111511
ctx->privateKey[i].keySz = 0;
15121512
}
1513+
#ifndef WOLFSSH_NO_MLDSA
1514+
ClearMlDsaHostPubKey(&ctx->privateKey[i], ctx->heap);
1515+
#endif
15131516
#ifdef WOLFSSH_CERTS
15141517
if (ctx->privateKey[i].cert != NULL) {
15151518
WFREE(ctx->privateKey[i].cert, ctx->heap, DYNTYPE_CERT);
@@ -2025,6 +2028,16 @@ static int IsCompositeMlDsaId(byte id)
20252028
#endif
20262029

20272030

2031+
/* Free a private-key IdentifyAsn1Key() result. NULL safe. */
2032+
static void FreeKeySignature(WS_KeySignature* key, void* heap)
2033+
{
2034+
if (key != NULL) {
2035+
wolfSSH_KEY_clean(key);
2036+
WFREE(key, heap, DYNTYPE_PRIVKEY);
2037+
}
2038+
}
2039+
2040+
20282041
#ifndef WOLFSSH_NO_MLDSA
20292042
/* Nonzero when keyId is an ML-DSA level wolfSSH was built without. */
20302043
static int MlDsaIdDisabled(byte keyId)
@@ -2676,6 +2689,76 @@ WOLFSSH_LOCAL void RefreshPublicKeyAlgo(WOLFSSH_CTX* ctx)
26762689
}
26772690

26782691

2692+
#ifndef WOLFSSH_NO_MLDSA
2693+
static INLINE int KeyIdToMlDsaLevel(byte id);
2694+
2695+
/* Also used by CommitCertStoreSlot() in ssh.c. */
2696+
WOLFSSH_LOCAL void ClearMlDsaHostPubKey(WOLFSSH_PVT_KEY* pvtKey, void* heap)
2697+
{
2698+
if (pvtKey->mldsaPub != NULL) {
2699+
WFREE(pvtKey->mldsaPub, heap, DYNTYPE_PUBKEY);
2700+
pvtKey->mldsaPub = NULL;
2701+
pvtKey->mldsaPubSz = 0;
2702+
}
2703+
}
2704+
2705+
2706+
/* Export the raw public key of the key IdentifyAsn1Key() decoded, for the
2707+
* slot cache. Caller owns *pubOut. No-op for a non-ML-DSA keyId. */
2708+
static int ExportMlDsaHostPubKey(byte keyId, WS_KeySignature* keySig,
2709+
void* heap, byte** pubOut, word32* pubOutSz)
2710+
{
2711+
MlDsaKey* key;
2712+
int ret;
2713+
int pubLen = 0;
2714+
word32 pubSz;
2715+
byte* pub;
2716+
2717+
if (KeyIdToMlDsaLevel(keyId) < 0) {
2718+
return WS_SUCCESS;
2719+
}
2720+
/* KEX needs the cache. Defensive: the public API always passes a
2721+
* matching one. */
2722+
if (keySig == NULL || keySig->keyId != keyId) {
2723+
WLOG(WS_LOG_ERROR, "ExportMlDsaHostPubKey: ML-DSA decoded key "
2724+
"missing or does not match");
2725+
return WS_BAD_ARGUMENT;
2726+
}
2727+
key = &keySig->ks.mldsa.key;
2728+
2729+
ret = wc_MlDsaKey_GetPubLen(key, &pubLen);
2730+
if (ret != 0 || pubLen <= 0) {
2731+
WLOG(WS_LOG_ERROR,
2732+
"ExportMlDsaHostPubKey: ML-DSA public key length failed %d",
2733+
ret);
2734+
return WS_CRYPTO_FAILED;
2735+
}
2736+
pubSz = (word32)pubLen;
2737+
2738+
pub = (byte*)WMALLOC(pubSz, heap, DYNTYPE_PUBKEY);
2739+
if (pub == NULL) {
2740+
return WS_MEMORY_E;
2741+
}
2742+
2743+
ret = wc_MlDsaKey_ExportPubRaw(key, pub, &pubSz);
2744+
if (ret != 0) {
2745+
WLOG(WS_LOG_ERROR,
2746+
"ExportMlDsaHostPubKey: ML-DSA public key export failed %d",
2747+
ret);
2748+
WFREE(pub, heap, DYNTYPE_PUBKEY);
2749+
return WS_CRYPTO_FAILED;
2750+
}
2751+
2752+
*pubOut = pub;
2753+
*pubOutSz = pubSz;
2754+
2755+
return WS_SUCCESS;
2756+
}
2757+
2758+
2759+
#endif /* !WOLFSSH_NO_MLDSA */
2760+
2761+
26792762
#ifdef WOLFSSH_CERTS
26802763

26812764
WOLFSSH_LOCAL byte CertTypeForId(byte id)
@@ -3000,10 +3083,15 @@ static int CertStoreSlotConflict(const WOLFSSH_CTX* ctx, word32 destIdx,
30003083

30013084

30023085
static int SetHostPrivateKey(WOLFSSH_CTX* ctx,
3003-
byte keyId, byte* der, word32 derSz, int dynamicType)
3086+
byte keyId, byte* der, word32 derSz, int dynamicType,
3087+
WS_KeySignature* keySig)
30043088
{
30053089
word32 destIdx = 0;
30063090
int ret = WS_SUCCESS;
3091+
#ifndef WOLFSSH_NO_MLDSA
3092+
byte* mldsaPub = NULL;
3093+
word32 mldsaPubSz = 0;
3094+
#endif
30073095

30083096
/* Look for the specified keyId. Add it if not present,
30093097
* replace it if present. Call UpdateHostCertificate().
@@ -3044,6 +3132,13 @@ static int SetHostPrivateKey(WOLFSSH_CTX* ctx,
30443132
WFREE(der, ctx->heap, dynamicType);
30453133
ret = WS_UNIMPLEMENTED_E;
30463134
}
3135+
/* Before the slot is touched, so a failure leaves the CTX as it was. */
3136+
else if ((ret = ExportMlDsaHostPubKey(keyId, keySig, ctx->heap,
3137+
&mldsaPub, &mldsaPubSz)) != WS_SUCCESS) {
3138+
/* der not taken on this path; free it to avoid a leak */
3139+
WS_FORCEZERO(der, derSz);
3140+
WFREE(der, ctx->heap, dynamicType);
3141+
}
30473142
#endif
30483143
else {
30493144
WOLFSSH_PVT_KEY* pvtKey = ctx->privateKey + destIdx;
@@ -3067,6 +3162,12 @@ static int SetHostPrivateKey(WOLFSSH_CTX* ctx,
30673162
pvtKey->isTpm = 0;
30683163
#endif
30693164

3165+
#ifndef WOLFSSH_NO_MLDSA
3166+
ClearMlDsaHostPubKey(pvtKey, ctx->heap);
3167+
pvtKey->mldsaPub = mldsaPub;
3168+
pvtKey->mldsaPubSz = mldsaPubSz;
3169+
#endif
3170+
30703171
#ifdef WOLFSSH_CERTS
30713172
if (ret == WS_SUCCESS) {
30723173
ret = UpdateHostCertificates(ctx, destIdx, WOLFSSH_MAX_PVT_KEYS);
@@ -3078,6 +3179,7 @@ static int SetHostPrivateKey(WOLFSSH_CTX* ctx,
30783179
}
30793180

30803181
WOLFSSH_UNUSED(dynamicType);
3182+
WOLFSSH_UNUSED(keySig);
30813183

30823184
return ret;
30833185
}
@@ -3130,6 +3232,9 @@ int wolfSSH_SetHostTpmKey(WOLFSSH_CTX* ctx, byte keyId)
31303232
pvtKey->key = NULL;
31313233
pvtKey->keySz = 0;
31323234
pvtKey->isTpm = 1;
3235+
#ifndef WOLFSSH_NO_MLDSA
3236+
ClearMlDsaHostPubKey(pvtKey, ctx->heap);
3237+
#endif
31333238
#ifdef WOLFSSH_WINDOWS_CERT_STORE
31343239
/* Defensive only: the else-if above already rejects a cert-store
31353240
* slot, so this can only clear a slot in a state no writer
@@ -3506,10 +3611,13 @@ int wolfSSH_ProcessBuffer(WOLFSSH_CTX* ctx,
35063611
/* Maybe decrypt */
35073612

35083613
if (type == BUFTYPE_PRIVKEY) {
3614+
/* Decoded ML-DSA key for SetHostPrivateKey()'s cache; else NULL. */
3615+
WS_KeySignature* keySig = NULL;
3616+
35093617
if (format == WOLFSSH_FORMAT_OPENSSH)
35103618
ret = IdentifyOpenSshKey(der, derSz, ctx->heap);
35113619
else
3512-
ret = IdentifyAsn1Key(der, derSz, 1, ctx->heap, NULL);
3620+
ret = IdentifyAsn1Key(der, derSz, 1, ctx->heap, &keySig);
35133621
if (ret < 0) {
35143622
if (der != NULL) {
35153623
WS_FORCEZERO(der, derSz);
@@ -3518,6 +3626,14 @@ int wolfSSH_ProcessBuffer(WOLFSSH_CTX* ctx,
35183626
return ret;
35193627
}
35203628
keyId = (byte)ret;
3629+
#ifndef WOLFSSH_NO_MLDSA
3630+
/* Only the ML-DSA slot cache needs the decoded key. */
3631+
if (KeyIdToMlDsaLevel(keyId) < 0)
3632+
#endif
3633+
{
3634+
FreeKeySignature(keySig, ctx->heap);
3635+
keySig = NULL;
3636+
}
35213637
/* Only composite parsers can walk the stored openssh-key-v1
35223638
* envelope; reject other key types now instead of at handshake. */
35233639
if (format == WOLFSSH_FORMAT_OPENSSH
@@ -3529,7 +3645,8 @@ int wolfSSH_ProcessBuffer(WOLFSSH_CTX* ctx,
35293645
WFREE(der, heap, dynamicType);
35303646
return WS_UNIMPLEMENTED_E;
35313647
}
3532-
ret = SetHostPrivateKey(ctx, keyId, der, derSz, dynamicType);
3648+
ret = SetHostPrivateKey(ctx, keyId, der, derSz, dynamicType, keySig);
3649+
FreeKeySignature(keySig, ctx->heap);
35333650
}
35343651
#ifdef WOLFSSH_CERTS
35353652
else if (type == BUFTYPE_CERT) {
@@ -16412,18 +16529,25 @@ static int SendKexGetSigningKey(WOLFSSH* ssh,
1641216529
&sigKeyBlock_ptr->sk.mldsa.key,
1641316530
ssh->ctx->privateKey[keyIdx].key,
1641416531
ssh->ctx->privateKey[keyIdx].keySz, &scratch);
16415-
#ifdef WOLFSSH_HAVE_MLDSA_DERIVE_PUB
16416-
/* The exporters below are pure accessors; a private-only host
16417-
* key needs its public half derived first. */
16418-
if (ret == 0 && !sigKeyBlock_ptr->sk.mldsa.key.pubKeySet)
16419-
ret = wc_MlDsaKey_MakePublicKey(
16420-
&sigKeyBlock_ptr->sk.mldsa.key);
16421-
#endif
16422-
if (ret == 0)
16423-
ret = wc_MlDsaKey_ExportPubRaw(
16424-
&sigKeyBlock_ptr->sk.mldsa.key,
16425-
sigKeyBlock_ptr->sk.mldsa.q,
16426-
&sigKeyBlock_ptr->sk.mldsa.qSz);
16532+
/* Use the cached public key; deriving it is a keygen per KEX.
16533+
* x509v3 sends the certificate instead. A missing cache is
16534+
* defensive: the load refuses a key it cannot cache. */
16535+
if (ret == 0 && !isCert) {
16536+
const WOLFSSH_PVT_KEY* pvtKey =
16537+
&ssh->ctx->privateKey[keyIdx];
16538+
16539+
if (pvtKey->mldsaPub != NULL &&
16540+
pvtKey->mldsaPubSz <= sigKeyBlock_ptr->sk.mldsa.qSz) {
16541+
WMEMCPY(sigKeyBlock_ptr->sk.mldsa.q, pvtKey->mldsaPub,
16542+
pvtKey->mldsaPubSz);
16543+
sigKeyBlock_ptr->sk.mldsa.qSz = pvtKey->mldsaPubSz;
16544+
}
16545+
else {
16546+
WLOG(WS_LOG_ERROR, "SendKexGetSigningKey: ML-DSA host "
16547+
"key has no cached public key");
16548+
ret = WS_INVALID_STATE_E;
16549+
}
16550+
}
1642716551

1642816552
/* Hash in raw public key only for non-cert path. */
1642916553
if (!isCert) {

‎src/ssh.c‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4145,6 +4145,9 @@ static void CommitCertStoreSlot(WOLFSSH_CTX* ctx, CertStoreSlot* slot)
41454145
pvtKey->key = NULL;
41464146
pvtKey->keySz = 0;
41474147
}
4148+
#ifndef WOLFSSH_NO_MLDSA
4149+
ClearMlDsaHostPubKey(pvtKey, heap);
4150+
#endif
41484151
if (pvtKey->cert != NULL) {
41494152
WFREE(pvtKey->cert, heap, DYNTYPE_CERT);
41504153
}

‎wolfssh/internal.h‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -879,6 +879,11 @@ typedef struct WOLFSSH_PVT_KEY {
879879
byte publicKeyFmt;
880880
/* Public key format for the private key. Note, some public key
881881
* formats are used with multiple public key signing algorithms. */
882+
#ifndef WOLFSSH_NO_MLDSA
883+
byte* mldsaPub;
884+
/* Raw ML-DSA public key, exported once at load time. Owned by CTX. */
885+
word32 mldsaPubSz;
886+
#endif /* !WOLFSSH_NO_MLDSA */
882887
#ifdef WOLFSSH_TPM
883888
byte isTpm;
884889
/* When set, the host key material lives in the TPM and key/keySz are
@@ -893,6 +898,11 @@ typedef struct WOLFSSH_PVT_KEY {
893898
#endif /* WOLFSSH_WINDOWS_CERT_STORE */
894899
} WOLFSSH_PVT_KEY;
895900

901+
#ifndef WOLFSSH_NO_MLDSA
902+
/* Free a slot's cached ML-DSA public key. Every slot writer calls it. */
903+
WOLFSSH_LOCAL void ClearMlDsaHostPubKey(WOLFSSH_PVT_KEY* pvtKey, void* heap);
904+
#endif
905+
896906
#ifdef WOLFSSH_WINDOWS_CERT_STORE
897907
/* Returns 1 when the value is exactly one assigned CERT_SYSTEM_STORE_*
898908
* location with no control flags set. Defined in certman.c. */

0 commit comments

Comments
 (0)