An ML-DSA-65 host key loads into wolfsshd and ssh-mldsa-65 is negotiated by both peers, but the handshake then fails server-side while building the KEXDH reply. The result is that an ML-DSA host key can be loaded and advertised but cannot sign the KEX, so no client can complete a handshake against it.
I'm reporting rather than patching — the fix looks like it belongs either in wolfCrypt (derive/retain the public key) or in how wolfSSH obtains the host public key, and that seemed like a maintainer call rather than something to guess at in a signing path.
Environment
- wolfSSH
master @ 53047bd
- wolfSSL
master @ 793608e (5.9.2)
- Linux 6.x (CachyOS), gcc 16.1.1
- wolfSSH configured with
--enable-sshd --enable-shell --enable-scp --enable-sftp --enable-fwd
Reproduction
- Generate an ML-DSA-65 host key with wolfCLU:
wolfssl genkey ml-dsa -level 3 -out h -output priv
- Point a
wolfsshd config's HostKey at it and run wolfsshd -D -d -f <config>
- Connect with the wolfSSH example client:
examples/client/client -h 127.0.0.1 -p <port> -u <user> -P <pass>
Observed
[SSHD] Adding name : ssh-mldsa-65
DKI: Server Host Key Algorithms
GNL: name ID ssh-mldsa-65 matches ssh-mldsa-65 # client offered it too
Using ML-DSA Host key
Leaving SendKexDhReply(), ret = -173 # BAD_FUNC_ARG
[SSHD] Failed to accept WOLFSSH connection ... error -1001
Root cause
The failure is in the ML-DSA block of SendKexDhReply() (src/internal.c, ~L13264-13279). wc_MlDsaKey_PrivateKeyDecode() succeeds, but the immediately following wc_MlDsaKey_ExportPubRaw() returns BAD_FUNC_ARG: the key decoded from a private-key file has no public-key component available to export, and wolfCrypt does not derive it here.
This reproduces standalone against wolfSSL alone, with no wolfSSH involved — Init / SetParams(3) / PrivateKeyDecode / ExportPubRaw on the same key file, mirroring what SendKexDhReply() does:
key file h.der, 4060 bytes, first byte 0x30
Init ret=0
SetParams(3) ret=0
PrivateKeyDecode ret=0 scratch=4060
ExportPubRaw ret=-173 qSz=1952 <-- BAD_FUNC_ARG
Reproduced with both a -output priv DER key and a -output keypair PKCS#8 PEM key.
Questions
- Should
wc_MlDsaKey_ExportPubRaw() derive the public key from a decoded private key, or should wolfSSH load/keep the public half separately for the host key?
- Is a specific key-file encoding (a private key with the embedded public key) expected for ML-DSA host keys? If so, which wolfCLU invocation produces it?
Happy to test a patch against this setup.
Found while testing #1118 (loading PKCS#8 PEM host keys in wolfsshd). That PR is independent — it fixes key loading; this is the separate failure that occurs afterward, and it reproduces with DER host keys on unpatched master too.
An ML-DSA-65 host key loads into
wolfsshdandssh-mldsa-65is negotiated by both peers, but the handshake then fails server-side while building the KEXDH reply. The result is that an ML-DSA host key can be loaded and advertised but cannot sign the KEX, so no client can complete a handshake against it.I'm reporting rather than patching — the fix looks like it belongs either in wolfCrypt (derive/retain the public key) or in how wolfSSH obtains the host public key, and that seemed like a maintainer call rather than something to guess at in a signing path.
Environment
master@53047bdmaster@793608e(5.9.2)--enable-sshd --enable-shell --enable-scp --enable-sftp --enable-fwdReproduction
wolfssl genkey ml-dsa -level 3 -out h -output privwolfsshdconfig'sHostKeyat it and runwolfsshd -D -d -f <config>examples/client/client -h 127.0.0.1 -p <port> -u <user> -P <pass>Observed
Root cause
The failure is in the ML-DSA block of
SendKexDhReply()(src/internal.c, ~L13264-13279).wc_MlDsaKey_PrivateKeyDecode()succeeds, but the immediately followingwc_MlDsaKey_ExportPubRaw()returnsBAD_FUNC_ARG: the key decoded from a private-key file has no public-key component available to export, and wolfCrypt does not derive it here.This reproduces standalone against wolfSSL alone, with no wolfSSH involved —
Init/SetParams(3)/PrivateKeyDecode/ExportPubRawon the same key file, mirroring whatSendKexDhReply()does:Reproduced with both a
-output privDER key and a-output keypairPKCS#8 PEM key.Questions
wc_MlDsaKey_ExportPubRaw()derive the public key from a decoded private key, or should wolfSSH load/keep the public half separately for the host key?Happy to test a patch against this setup.
Found while testing #1118 (loading PKCS#8 PEM host keys in
wolfsshd). That PR is independent — it fixes key loading; this is the separate failure that occurs afterward, and it reproduces with DER host keys on unpatchedmastertoo.