@@ -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. */
20302043static 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
26812764WOLFSSH_LOCAL byte CertTypeForId(byte id)
@@ -3000,10 +3083,15 @@ static int CertStoreSlotConflict(const WOLFSSH_CTX* ctx, word32 destIdx,
30003083
30013084
30023085static 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) {
0 commit comments