diff --git a/README.md b/README.md index 554f1298f..07f9ac61e 100644 --- a/README.md +++ b/README.md @@ -451,22 +451,30 @@ The wolfSSH client and server will automatically negotiate using Curve25519. POST-QUANTUM ============ -wolfSSH now supports the post-quantum algorithm ML-KEM (formerly known as -Kyber). It uses the ML-KEM-768 parameter set and is hybridized with ECDHE over -the P-256 ECC curve. +wolfSSH supports both post-quantum key exchange via ML-KEM (formerly known as +Kyber) and post-quantum signature verification via ML-DSA (formerly known as +Dilithium). -In order to use this key exchange you must build and install wolfSSL on your -system. Here is an example of an effective configuration: +* **ML-KEM**: Uses the ML-KEM-768 parameter set hybridized with ECDHE over the + P-256 ECC curve. +* **ML-DSA**: Supports ML-DSA-44, ML-DSA-65, and ML-DSA-87 parameter sets for + both server host keys and client public key authentication. When built with + certificate support, ML-DSA X.509 certificates (`x509v3-ssh-mldsa-44`, + `x509v3-ssh-mldsa-65`, and `x509v3-ssh-mldsa-87`) are also supported. - $ ./configure --enable-wolfssh --enable-mlkem +In order to use these algorithms you must build and install wolfSSL with +support for them. Here is an example of an effective configuration: -After that, simply configure and build wolfssh as usual: + $ ./configure --enable-wolfssh --enable-mlkem --enable-mldsa + +After that, configure and build wolfSSH as usual: $ ./configure $ make all The wolfSSH client and server will automatically negotiate using ML-KEM-768 -hybridized with ECDHE over the P-256 ECC curve. +hybridized with ECDHE over the P-256 ECC curve and ML-DSA for host keys/client +public key authentication. $ ./examples/echoserver/echoserver -f @@ -476,7 +484,7 @@ On the client side, you will see the following output: Server said: Hello, wolfSSH! -If you want to see interoperability with OpenQauntumSafe's fork of OpenSSH, you +If you want to see interoperability with OpenQuantumSafe's fork of OpenSSH, you can build and execute the fork while the echoserver is running. Download the release from here: @@ -498,6 +506,7 @@ NOTE: when prompted, enter the password which is "upthehill". You can type a line of text and when you press enter, the line will be echoed back. Use CTRL-C to terminate the connection. + CERTIFICATE SUPPORT =================== diff --git a/apps/wolfsshd/auth.c b/apps/wolfsshd/auth.c index 50225637a..be2a7892f 100644 --- a/apps/wolfsshd/auth.c +++ b/apps/wolfsshd/auth.c @@ -105,7 +105,18 @@ struct WOLFSSHD_AUTH { #endif #ifndef MAX_LINE_SZ - #define MAX_LINE_SZ 900 + /* Sized to hold the largest authorized_keys entry. */ + #ifndef WOLFSSH_NO_MLDSA + #ifndef WOLFSSH_NO_MLDSA87 + #define MAX_LINE_SZ ((WC_MLDSA_87_PUB_KEY_SIZE + 2) / 3 * 4 + 640) + #elif !defined(WOLFSSH_NO_MLDSA65) + #define MAX_LINE_SZ ((WC_MLDSA_65_PUB_KEY_SIZE + 2) / 3 * 4 + 640) + #else + #define MAX_LINE_SZ ((WC_MLDSA_44_PUB_KEY_SIZE + 2) / 3 * 4 + 640) + #endif + #else + #define MAX_LINE_SZ 900 + #endif #endif #ifndef MAX_PATH_SZ #define MAX_PATH_SZ 80 @@ -173,14 +184,7 @@ static int CheckAuthKeysLine(char* line, word32 lineSz, const byte* key, word32 keyCandSz = 0; char* last = NULL; - enum { - #ifdef WOLFSSH_CERTS - NUM_ALLOWED_TYPES = 9 - #else - NUM_ALLOWED_TYPES = 5 - #endif - }; - static const char* allowedTypes[NUM_ALLOWED_TYPES] = { + static const char* allowedTypes[] = { "ssh-rsa", "ssh-ed25519", "ecdsa-sha2-nistp256", @@ -192,7 +196,31 @@ static int CheckAuthKeysLine(char* line, word32 lineSz, const byte* key, "x509v3-ecdsa-sha2-nistp384", "x509v3-ecdsa-sha2-nistp521", #endif + #ifndef WOLFSSH_NO_MLDSA + #ifndef WOLFSSH_NO_MLDSA44 + "ssh-mldsa-44", + #endif + #ifndef WOLFSSH_NO_MLDSA65 + "ssh-mldsa-65", + #endif + #ifndef WOLFSSH_NO_MLDSA87 + "ssh-mldsa-87", + #endif + #ifdef WOLFSSH_CERTS + #ifndef WOLFSSH_NO_MLDSA44 + "x509v3-ssh-mldsa-44", + #endif + #ifndef WOLFSSH_NO_MLDSA65 + "x509v3-ssh-mldsa-65", + #endif + #ifndef WOLFSSH_NO_MLDSA87 + "x509v3-ssh-mldsa-87", + #endif + #endif + #endif }; + const int NUM_ALLOWED_TYPES = + (int)(sizeof(allowedTypes) / sizeof(allowedTypes[0])); int typeOk = 0; int i; diff --git a/examples/echoserver/echoserver.c b/examples/echoserver/echoserver.c index 0d0e44f27..5616a647b 100644 --- a/examples/echoserver/echoserver.c +++ b/examples/echoserver/echoserver.c @@ -1777,6 +1777,123 @@ static int load_key_ed25519(byte* buf, word32 bufSz) #endif /* WOLFSSH_NO_ED25519 */ +#ifndef WOLFSSH_NO_MLDSA44 +static int load_key_mldsa44(byte* buf, word32 bufSz) +{ + word32 sz = 0; +#ifndef NO_FILESYSTEM + sz = load_file("./keys/server-key-mldsa44.der", buf, &bufSz); +#else + (void)buf; (void)bufSz; +#endif + return sz; +} +#endif /* WOLFSSH_NO_MLDSA44 */ + +#ifndef WOLFSSH_NO_MLDSA65 +static int load_key_mldsa65(byte* buf, word32 bufSz) +{ + word32 sz = 0; +#ifndef NO_FILESYSTEM + sz = load_file("./keys/server-key-mldsa65.der", buf, &bufSz); +#else + (void)buf; (void)bufSz; +#endif + return sz; +} +#endif /* WOLFSSH_NO_MLDSA65 */ + +#ifndef WOLFSSH_NO_MLDSA87 +static int load_key_mldsa87(byte* buf, word32 bufSz) +{ + word32 sz = 0; +#ifndef NO_FILESYSTEM + sz = load_file("./keys/server-key-mldsa87.der", buf, &bufSz); +#else + (void)buf; (void)bufSz; +#endif + return sz; +} +#endif /* WOLFSSH_NO_MLDSA87 */ + + +#ifndef WOLFSSH_NO_MLDSA +static int LoadMlDsaHostKeys(WOLFSSH_CTX* ctx, const char* keyList) +{ + byte* mldsaBuf; + int loaded = 0; + + mldsaBuf = (byte*)WMALLOC(MLDSA_MAX_BOTH_KEY_DER_SIZE, NULL, 0); + if (mldsaBuf == NULL) { + fprintf(stderr, "Couldn't allocate ML-DSA key load buffer.\n"); + return -1; + } + + #ifndef WOLFSSH_NO_MLDSA44 + if (WSTRSTR(keyList, "mldsa-44") != NULL) { + int mldsaSz = load_key_mldsa44(mldsaBuf, MLDSA_MAX_BOTH_KEY_DER_SIZE); + if (mldsaSz <= 0) { + fprintf(stderr, "Couldn't load ML-DSA-44 key file.\n"); + WFREE(mldsaBuf, NULL, 0); + return -1; + } + if (wolfSSH_CTX_UsePrivateKey_buffer(ctx, mldsaBuf, (word32)mldsaSz, + WOLFSSH_FORMAT_ASN1) < 0) { + fprintf(stderr, "Couldn't use ML-DSA-44 key buffer.\n"); + WFREE(mldsaBuf, NULL, 0); + return -1; + } + loaded++; + } + #endif /* WOLFSSH_NO_MLDSA44 */ + + #ifndef WOLFSSH_NO_MLDSA65 + if (WSTRSTR(keyList, "mldsa-65") != NULL) { + int mldsaSz = load_key_mldsa65(mldsaBuf, MLDSA_MAX_BOTH_KEY_DER_SIZE); + if (mldsaSz <= 0) { + fprintf(stderr, "Couldn't load ML-DSA-65 key file.\n"); + WFREE(mldsaBuf, NULL, 0); + return -1; + } + if (wolfSSH_CTX_UsePrivateKey_buffer(ctx, mldsaBuf, (word32)mldsaSz, + WOLFSSH_FORMAT_ASN1) < 0) { + fprintf(stderr, "Couldn't use ML-DSA-65 key buffer.\n"); + WFREE(mldsaBuf, NULL, 0); + return -1; + } + loaded++; + } + #endif /* WOLFSSH_NO_MLDSA65 */ + + #ifndef WOLFSSH_NO_MLDSA87 + if (WSTRSTR(keyList, "mldsa-87") != NULL) { + int mldsaSz = load_key_mldsa87(mldsaBuf, MLDSA_MAX_BOTH_KEY_DER_SIZE); + if (mldsaSz <= 0) { + fprintf(stderr, "Couldn't load ML-DSA-87 key file.\n"); + WFREE(mldsaBuf, NULL, 0); + return -1; + } + if (wolfSSH_CTX_UsePrivateKey_buffer(ctx, mldsaBuf, (word32)mldsaSz, + WOLFSSH_FORMAT_ASN1) < 0) { + fprintf(stderr, "Couldn't use ML-DSA-87 key buffer.\n"); + WFREE(mldsaBuf, NULL, 0); + return -1; + } + loaded++; + } + #endif /* WOLFSSH_NO_MLDSA87 */ + + WFREE(mldsaBuf, NULL, 0); + if (loaded == 0) { + fprintf(stderr, "ML-DSA key list '%s' matched no supported level.\n", + keyList); + return -1; + } + return 0; +} +#endif /* WOLFSSH_NO_MLDSA */ + + typedef struct StrList { const char* str; struct StrList* next; @@ -3108,7 +3225,8 @@ THREAD_RETURN WOLFSSH_THREAD echoserver_test(void* args) if (tpmHostKeyPath != NULL) { if (EchoserverInitTpmHostKey(ctx, tpmHostKeyPath, ECHOSERVER_TPM_KEY_AUTH_DEFAULT) != 0) { - ES_ERROR("Couldn't load TPM host key from %s.\n", tpmHostKeyPath); + ES_ERROR("Couldn't load TPM host key from %s.\n", + tpmHostKeyPath); } loadDefaultHostKeys = 0; } @@ -3151,6 +3269,19 @@ THREAD_RETURN WOLFSSH_THREAD echoserver_test(void* args) #endif /* WOLFSSH_NO_ED25519 */ } + /* Load only the ML-DSA levels requested in keyList; loading all levels + * unconditionally would force mldsa negotiation on non-mldsa tests. */ + #ifndef WOLFSSH_NO_MLDSA + if (keyList != NULL && WSTRSTR(keyList, "mldsa") != NULL) { + if (LoadMlDsaHostKeys(ctx, keyList) != 0) { + #ifdef WOLFSSH_SMALL_STACK + WFREE(keyLoadBuf, NULL, 0); + #endif + ES_ERROR("Error loading ML-DSA host keys.\n"); + } + } + #endif /* WOLFSSH_NO_MLDSA */ + #ifndef NO_FILESYSTEM if (userPubKey) { byte* userBuf = NULL; diff --git a/keys/include.am b/keys/include.am index fd82f10c5..0a17590d5 100644 --- a/keys/include.am +++ b/keys/include.am @@ -24,5 +24,7 @@ EXTRA_DIST+= \ keys/server-key.pem keys/fred-key.der keys/fred-key.pem \ keys/id_ecdsa keys/id_ecdsa.pub keys/id_rsa keys/id_rsa.pub \ keys/renewcerts.sh keys/renewcerts.cnf \ - keys/server-key-ed25519.der keys/server-key-ed25519.pem + keys/server-key-ed25519.der keys/server-key-ed25519.pem \ + keys/server-key-mldsa44.der keys/server-key-mldsa65.der \ + keys/server-key-mldsa87.der diff --git a/keys/server-key-mldsa44.der b/keys/server-key-mldsa44.der new file mode 100644 index 000000000..4d3eba3b5 Binary files /dev/null and b/keys/server-key-mldsa44.der differ diff --git a/keys/server-key-mldsa65.der b/keys/server-key-mldsa65.der new file mode 100644 index 000000000..1b4d1681b Binary files /dev/null and b/keys/server-key-mldsa65.der differ diff --git a/keys/server-key-mldsa87.der b/keys/server-key-mldsa87.der new file mode 100644 index 000000000..ddf545e57 Binary files /dev/null and b/keys/server-key-mldsa87.der differ diff --git a/src/internal.c b/src/internal.c index dfb92d728..98be556a1 100644 --- a/src/internal.c +++ b/src/internal.c @@ -60,6 +60,10 @@ #include #endif +#ifndef WOLFSSH_NO_MLDSA + #include +#endif + #ifdef NO_INLINE #include #else @@ -133,6 +137,14 @@ WOLFSSH_NO_SSH_RSA_SHA1 Set when RSA or SHA1 are disabled. Set to disable use of RSA server authentication. + WOLFSSH_NO_MLDSA + Set when MLDSA is disabled and/or not included in wolfssl downloaded. + WOLFSSH_NO_MLDSA44 + Set for ML-DSA-44. + WOLFSSH_NO_MLDSA65 + Set for ML-DSA-65. + WOLFSSH_NO_MLDSA87 + Set for ML-DSA-87. WOLFSSH_NO_ECDSA Set when ECC is disabled. Set to disable use of ECDSA server and user authentication. @@ -491,6 +503,9 @@ const char* GetErrorString(int err) case WS_ED25519_E: return "Ed25519 buffer error"; + case WS_MLDSA_E: + return "ML-DSA error"; + case WS_AUTH_PENDING: return "userauth is still pending (callback would block)"; @@ -627,8 +642,8 @@ static void HandshakeInfoFree(HandshakeInfo* hs, void* heap) } #endif #ifndef WOLFSSH_NO_ECDH - /* privKey is a union; the Curve25519+ML-KEM case also sets - * useEccMlKem but generates a curve25519 key, so free it below. */ + /* privKey is a union; Curve25519+ML-KEM sets useEccMlKem but uses + * a curve25519 key, freed in the curve25519 block below. */ if (hs->useEcc || (hs->useEccMlKem && !hs->useCurve25519MlKem)) { wc_ecc_free(&hs->privKey.ecc); } @@ -972,14 +987,46 @@ static const char cannedKexAlgoNames[] = #ifndef WOLFSSH_NO_ED25519 static const char cannedKeyAlgoEd25519Name[] = "ssh-ed25519"; #endif +#ifdef WOLFSSH_CERTS +#ifndef WOLFSSH_NO_MLDSA44 + static const char cannedKeyAlgoX509Mldsa44Names[] = "x509v3-ssh-mldsa-44"; +#endif +#ifndef WOLFSSH_NO_MLDSA65 + static const char cannedKeyAlgoX509Mldsa65Names[] = "x509v3-ssh-mldsa-65"; +#endif +#ifndef WOLFSSH_NO_MLDSA87 + static const char cannedKeyAlgoX509Mldsa87Names[] = "x509v3-ssh-mldsa-87"; +#endif +#endif /* WOLFSSH_CERTS */ +/* ML-DSA listed first (post-quantum priority), then ECDSA, ED25519, RSA. */ static const char cannedKeyAlgoNames[] = +#ifndef WOLFSSH_NO_MLDSA87 + "ssh-mldsa-87," +#endif +#ifndef WOLFSSH_NO_MLDSA65 + "ssh-mldsa-65," +#endif +#ifndef WOLFSSH_NO_MLDSA44 + "ssh-mldsa-44," +#endif +#ifdef WOLFSSH_CERTS + #ifndef WOLFSSH_NO_MLDSA87 + "x509v3-ssh-mldsa-87," + #endif + #ifndef WOLFSSH_NO_MLDSA65 + "x509v3-ssh-mldsa-65," + #endif + #ifndef WOLFSSH_NO_MLDSA44 + "x509v3-ssh-mldsa-44," + #endif +#endif /* WOLFSSH_CERTS */ #ifndef WOLFSSH_NO_ED25519 "ssh-ed25519," #endif /* WOLFSSH_NO_ED25519 */ #ifndef WOLFSSH_NO_RSA_SHA2_256 "rsa-sha2-256," -#endif/* WOLFSSH_NO_RSA_SHA2_256 */ +#endif /* WOLFSSH_NO_RSA_SHA2_256 */ #ifndef WOLFSSH_NO_RSA_SHA2_512 "rsa-sha2-512," #endif /* WOLFSSH_NO_RSA_SHA2_512 */ @@ -1416,7 +1463,7 @@ void SshResourceFree(WOLFSSH* ssh, void* heap) ssh->scpBasePathSz = 0; } #if !defined(WOLFSSH_SCP_USER_CALLBACKS) && !defined(NO_FILESYSTEM) - /* free any send-side directory stack left by an aborted recursive transfer */ + /* free send-side dir stack from any aborted recursive transfer */ ScpSendCtxFreeDirs(ssh->fs, &ssh->scpSendCbCtx, heap); #endif #endif @@ -1448,7 +1495,8 @@ void SshResourceFree(WOLFSSH* ssh, void* heap) void wolfSSH_KEY_clean(WS_KeySignature* key) { if (key != NULL) { - if (key->keyId == ID_SSH_RSA) { + if (key->keyId == ID_SSH_RSA || + key->keyId == ID_X509V3_SSH_RSA) { #ifndef WOLFSSH_NO_RSA wc_FreeRsaKey(&key->ks.rsa.key); #endif @@ -1458,9 +1506,22 @@ void wolfSSH_KEY_clean(WS_KeySignature* key) wc_ed25519_free(&key->ks.ed25519.key); #endif } +#ifndef WOLFSSH_NO_MLDSA + else if (key->keyId == ID_MLDSA44 || + key->keyId == ID_MLDSA65 || + key->keyId == ID_MLDSA87 || + key->keyId == ID_X509V3_MLDSA44 || + key->keyId == ID_X509V3_MLDSA65 || + key->keyId == ID_X509V3_MLDSA87) { + wc_MlDsaKey_Free(&key->ks.mldsa.key); + } +#endif else if (key->keyId == ID_ECDSA_SHA2_NISTP256 || key->keyId == ID_ECDSA_SHA2_NISTP384 || - key->keyId == ID_ECDSA_SHA2_NISTP521) { + key->keyId == ID_ECDSA_SHA2_NISTP521 || + key->keyId == ID_X509V3_ECDSA_SHA2_NISTP256 || + key->keyId == ID_X509V3_ECDSA_SHA2_NISTP384 || + key->keyId == ID_X509V3_ECDSA_SHA2_NISTP521) { #ifndef WOLFSSH_NO_ECDSA wc_ecc_free(&key->ks.ecc.key); #endif @@ -1470,7 +1531,8 @@ void wolfSSH_KEY_clean(WS_KeySignature* key) /* - * Identifies the flavor of an ASN.1 key, RSA or ECDSA, and returns the key + * Identifies the flavor of an ASN.1 key, RSA or ECDSA or MLDSA, and returns + * the key * type ID. The process is to decode the key as if it was RSA and if that * fails try to load it as if ECDSA. Both public and private keys can be * decoded. For RSA keys, the key format is described as "ssh-rsa". @@ -1489,6 +1551,10 @@ int IdentifyAsn1Key(const byte* in, word32 inSz, int isPrivate, void* heap, word32 idx; int ret; int dynType = isPrivate ? DYNTYPE_PRIVKEY : DYNTYPE_PUBKEY; +#ifndef WOLFSSH_NO_MLDSA + byte mlDsaLevel = 0; + int mlDsaInit = 0; +#endif WOLFSSH_UNUSED(dynType); if (pkey != NULL) { @@ -1566,6 +1632,77 @@ int IdentifyAsn1Key(const byte* in, word32 inSz, int isPrivate, void* heap, } } #endif /* WOLFSSH_NO_ECDSA */ +#ifndef WOLFSSH_NO_MLDSA + if (key->keyId == ID_UNKNOWN) { + idx = 0; + mlDsaLevel = 0; + mlDsaInit = 0; + ret = wc_MlDsaKey_Init(&key->ks.mldsa.key, heap, INVALID_DEVID); + if (ret == 0) { + mlDsaInit = 1; + if (isPrivate) { + ret = wc_MlDsaKey_PrivateKeyDecode(&key->ks.mldsa.key, + in, inSz, &idx); + } + else { + /* PublicKeyDecode auto-detects level from SPKI OID. */ + ret = wc_MlDsaKey_PublicKeyDecode(&key->ks.mldsa.key, + in, inSz, &idx); + if (ret != 0) { + /* Length-only fallback for local key/cert loading + * (wolfSSH_ReadKey_buffer_ex / IdentifyCert) when SPKI + * OID decode fails: wc_MlDsaKey_ImportPubRaw accepts an + * ML-DSA blob of exactly 1312/1952/2592 bytes and tags + * it ID_MLDSA44/65/87. Not used on the remote user-auth + * path; do not wire this probe into remote auth assuming + * it cryptographically validates the key. */ + struct { byte level; byte id; } kProbe[3]; + word32 nProbe = 0, li; +#ifndef WOLFSSH_NO_MLDSA44 + kProbe[nProbe].level = WC_ML_DSA_44; + kProbe[nProbe].id = ID_MLDSA44; + nProbe++; +#endif +#ifndef WOLFSSH_NO_MLDSA65 + kProbe[nProbe].level = WC_ML_DSA_65; + kProbe[nProbe].id = ID_MLDSA65; + nProbe++; +#endif +#ifndef WOLFSSH_NO_MLDSA87 + kProbe[nProbe].level = WC_ML_DSA_87; + kProbe[nProbe].id = ID_MLDSA87; + nProbe++; +#endif + for (li = 0; li < nProbe; li++) { + wc_MlDsaKey_Free(&key->ks.mldsa.key); + mlDsaInit = 0; + if (wc_MlDsaKey_Init(&key->ks.mldsa.key, heap, + INVALID_DEVID) != 0) + break; + mlDsaInit = 1; + if (wc_MlDsaKey_SetParams(&key->ks.mldsa.key, + kProbe[li].level) == 0 && + wc_MlDsaKey_ImportPubRaw( + &key->ks.mldsa.key, in, inSz) == 0) { + key->keyId = kProbe[li].id; + ret = 0; + break; + } + } + } + } + } + if (ret == 0 && key->keyId == ID_UNKNOWN && + wc_MlDsaKey_GetParams(&key->ks.mldsa.key, + &mlDsaLevel) == 0) { + if (mlDsaLevel == WC_ML_DSA_44) key->keyId = ID_MLDSA44; + else if (mlDsaLevel == WC_ML_DSA_65) key->keyId = ID_MLDSA65; + else if (mlDsaLevel == WC_ML_DSA_87) key->keyId = ID_MLDSA87; + } + if (mlDsaInit && (key->keyId == ID_UNKNOWN || ret != 0)) + wc_MlDsaKey_Free(&key->ks.mldsa.key); + } +#endif /* WOLFSSH_NO_MLDSA */ #if !defined(WOLFSSH_NO_ED25519) if (key->keyId == ID_UNKNOWN) { idx = 0; @@ -1814,6 +1951,42 @@ static int GetOpenSshKeyEd25519(ed25519_key* key, } #endif +#ifndef WOLFSSH_NO_MLDSA +/* Parse OpenSSH ML-DSA private key blob; see GetOpenSshKeyPublicMlDsa. */ +static int GetOpenSshKeyMlDsa(MlDsaKey* key, + const byte* buf, word32 len, word32* idx, byte level) +{ + const byte *pub = NULL; + const byte *priv = NULL; + word32 pubSz = 0; + word32 privSz = 0; + int ret; + + ret = wc_MlDsaKey_Init(key, key->heap, INVALID_DEVID); + if (ret == 0) { + ret = wc_MlDsaKey_SetParams(key, level); + if (ret != 0) { + wc_MlDsaKey_Free(key); + return WS_CRYPTO_FAILED; + } + } + else { + return WS_CRYPTO_FAILED; + } + + ret = GetStringRef(&pubSz, &pub, buf, len, idx); + if (ret == WS_SUCCESS) + ret = GetStringRef(&privSz, &priv, buf, len, idx); + if (ret == WS_SUCCESS) + ret = wc_MlDsaKey_ImportKey(key, priv, privSz, pub, pubSz); + if (ret != 0) { + wc_MlDsaKey_Free(key); + ret = WS_KEY_FORMAT_E; + } + return ret; +} +#endif + #ifdef WOLFSSH_TPM #ifndef WOLFSSH_NO_ECDSA @@ -1842,6 +2015,37 @@ static int GetOpenSshKeyPublicEd25519(ed25519_key* key, const byte* buf, return ret; } #endif +#ifndef WOLFSSH_NO_MLDSA +static int GetOpenSshKeyPublicMlDsa(MlDsaKey* key, const byte* buf, + word32 len, word32* idx, byte level) +{ + int ret; + const byte* pub = NULL; + word32 pubSz = 0; + + ret = wc_MlDsaKey_Init(key, key->heap, INVALID_DEVID); + if (ret == 0) { + ret = wc_MlDsaKey_SetParams(key, level); + if (ret != 0) { + wc_MlDsaKey_Free(key); + return WS_CRYPTO_FAILED; + } + } + else { + return WS_CRYPTO_FAILED; + } + + ret = GetStringRef(&pubSz, &pub, buf, len, idx); + if (ret == WS_SUCCESS) { + ret = wc_MlDsaKey_ImportPubRaw(key, pub, pubSz); + } + if (ret != 0) { + wc_MlDsaKey_Free(key); + ret = WS_CRYPTO_FAILED; + } + return ret; +} +#endif #ifndef WOLFSSH_NO_RSA static int GetOpenSshPublicKeyRsa(RsaKey* key, const byte* buf, word32 len, word32* idx) @@ -1889,6 +2093,20 @@ static int GetOpenSshPublicKey(WS_KeySignature *key, ret = GetOpenSshPublicKeyEcc(&key->ks.ecc.key, buf, len, idx); break; #endif + #ifndef WOLFSSH_NO_MLDSA + case ID_MLDSA44: + ret = GetOpenSshKeyPublicMlDsa(&key->ks.mldsa.key, buf, len, + idx, WC_ML_DSA_44); + break; + case ID_MLDSA65: + ret = GetOpenSshKeyPublicMlDsa(&key->ks.mldsa.key, buf, len, + idx, WC_ML_DSA_65); + break; + case ID_MLDSA87: + ret = GetOpenSshKeyPublicMlDsa(&key->ks.mldsa.key, buf, len, + idx, WC_ML_DSA_87); + break; + #endif #ifndef WOLFSSH_NO_ED25519 case ID_ED25519: ret = GetOpenSshKeyPublicEd25519(&key->ks.ed25519.key, buf, len, idx); @@ -1991,6 +2209,23 @@ static int GetOpenSshKey(WS_KeySignature *key, str, strSz, &subIdx); break; #endif + #ifndef WOLFSSH_NO_MLDSA + case ID_MLDSA44: + ret = GetOpenSshKeyMlDsa( + &key->ks.mldsa.key, + str, strSz, &subIdx, WC_ML_DSA_44); + break; + case ID_MLDSA65: + ret = GetOpenSshKeyMlDsa( + &key->ks.mldsa.key, + str, strSz, &subIdx, WC_ML_DSA_65); + break; + case ID_MLDSA87: + ret = GetOpenSshKeyMlDsa( + &key->ks.mldsa.key, + str, strSz, &subIdx, WC_ML_DSA_87); + break; + #endif #ifndef WOLFSSH_NO_ED25519 case ID_ED25519: ret = GetOpenSshKeyEd25519(&key->ks.ed25519.key, @@ -2037,7 +2272,7 @@ static int GetOpenSshKey(WS_KeySignature *key, /* - * Identifies the flavor of an OpenSSH key, RSA or ECDSA, and returns the + * Identifies the flavor of an OpenSSH key, RSA, ML-DSA, or ECDSA, and returns * key type ID. The process is to decode the key extracting the identifiers, * and try to decode the key as the type indicated type. For RSA keys, the * key format is described as "ssh-rsa". @@ -2084,7 +2319,7 @@ int IdentifyOpenSshKey(const byte* in, word32 inSz, void* heap) #ifdef WOLFSSH_CERTS /* - * Identifies the flavor of an X.509 certificate, RSA or ECDSA, and returns + * Identifies the flavor of an X.509 certificate, RSA, ML-DSA or ECDSA, returns * the key type ID. The process is to decode the certificate and pass the * public key to IdentifyAsn1Key. * @@ -2219,6 +2454,21 @@ static INLINE byte CertTypeForId(byte id) id = ID_X509V3_ECDSA_SHA2_NISTP521; break; #endif + #ifndef WOLFSSH_NO_MLDSA44 + case ID_MLDSA44: + id = ID_X509V3_MLDSA44; + break; + #endif + #ifndef WOLFSSH_NO_MLDSA65 + case ID_MLDSA65: + id = ID_X509V3_MLDSA65; + break; + #endif + #ifndef WOLFSSH_NO_MLDSA87 + case ID_MLDSA87: + id = ID_X509V3_MLDSA87; + break; + #endif } WOLFSSH_UNUSED(id); @@ -2435,9 +2685,7 @@ static int SetHostPrivateKey(WOLFSSH_CTX* ctx, #ifdef WOLFSSH_TPM -/* Registers a host key whose private material lives in the TPM. The slot - * carries only the key type so KEX can advertise it; signing and K_S come - * from ctx->tpmKey. */ +/* Register a TPM host key type for KEX; signing uses ctx->tpmKey. */ int wolfSSH_SetHostTpmKey(WOLFSSH_CTX* ctx, byte keyId) { word32 destIdx = 0; @@ -2960,7 +3208,25 @@ static const NameIdPair NameIdMap[] = { #ifndef WOLFSSH_NO_ED25519 { ID_ED25519, TYPE_KEY, "ssh-ed25519" }, #endif +#ifndef WOLFSSH_NO_MLDSA44 + { ID_MLDSA44, TYPE_KEY, "ssh-mldsa-44" }, +#endif +#ifndef WOLFSSH_NO_MLDSA65 + { ID_MLDSA65, TYPE_KEY, "ssh-mldsa-65" }, +#endif +#ifndef WOLFSSH_NO_MLDSA87 + { ID_MLDSA87, TYPE_KEY, "ssh-mldsa-87" }, +#endif #ifdef WOLFSSH_CERTS +#ifndef WOLFSSH_NO_MLDSA44 + { ID_X509V3_MLDSA44, TYPE_KEY, "x509v3-ssh-mldsa-44" }, +#endif +#ifndef WOLFSSH_NO_MLDSA65 + { ID_X509V3_MLDSA65, TYPE_KEY, "x509v3-ssh-mldsa-65" }, +#endif +#ifndef WOLFSSH_NO_MLDSA87 + { ID_X509V3_MLDSA87, TYPE_KEY, "x509v3-ssh-mldsa-87" }, +#endif #ifndef WOLFSSH_NO_SSH_RSA_SHA1 { ID_X509V3_SSH_RSA, TYPE_KEY, "x509v3-ssh-rsa" }, #endif @@ -3999,8 +4265,18 @@ static int GetNameList(byte* idList, word32* idListSz, return ret; } +/* ML-DSA listed first (post-quantum priority), then ECDSA, ED25519, RSA. */ static const byte cannedKeyAlgoClient[] = { #ifdef WOLFSSH_CERTS + #ifndef WOLFSSH_NO_MLDSA87 + ID_X509V3_MLDSA87, + #endif + #ifndef WOLFSSH_NO_MLDSA65 + ID_X509V3_MLDSA65, + #endif + #ifndef WOLFSSH_NO_MLDSA44 + ID_X509V3_MLDSA44, + #endif #ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP521 ID_X509V3_ECDSA_SHA2_NISTP521, #endif @@ -4016,6 +4292,15 @@ static const byte cannedKeyAlgoClient[] = { #endif /* WOLFSSH_NO_SSH_RSA_SHA1 */ #endif /* WOLFSSH_NO_SHA1_SOFT_DISABLE */ #endif /* WOLFSSH_CERTS */ +#ifndef WOLFSSH_NO_MLDSA87 + ID_MLDSA87, +#endif +#ifndef WOLFSSH_NO_MLDSA65 + ID_MLDSA65, +#endif +#ifndef WOLFSSH_NO_MLDSA44 + ID_MLDSA44, +#endif #ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP521 ID_ECDSA_SHA2_NISTP521, #endif @@ -4025,6 +4310,9 @@ static const byte cannedKeyAlgoClient[] = { #ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP256 ID_ECDSA_SHA2_NISTP256, #endif +#ifndef WOLFSSH_NO_ED25519 + ID_ED25519, +#endif #ifndef WOLFSSH_NO_RSA_SHA2_512 ID_RSA_SHA2_512, #endif @@ -4036,9 +4324,6 @@ static const byte cannedKeyAlgoClient[] = { ID_SSH_RSA, #endif /* WOLFSSH_NO_SSH_RSA_SHA1 */ #endif /* WOLFSSH_NO_SHA1_SOFT_DISABLE */ -#ifndef WOLFSSH_NO_ED25519 - ID_ED25519, -#endif }; static const word32 cannedKeyAlgoClientSz = (word32)sizeof(cannedKeyAlgoClient); @@ -4240,6 +4525,27 @@ enum wc_HashType HashForId(byte id) #ifndef WOLFSSH_NO_ED25519 case ID_ED25519: return WC_HASH_TYPE_SHA512; +#endif +#ifndef WOLFSSH_NO_MLDSA44 + case ID_MLDSA44: + #ifdef WOLFSSH_CERTS + case ID_X509V3_MLDSA44: + #endif + return WC_HASH_TYPE_NONE; +#endif +#ifndef WOLFSSH_NO_MLDSA65 + case ID_MLDSA65: + #ifdef WOLFSSH_CERTS + case ID_X509V3_MLDSA65: + #endif + return WC_HASH_TYPE_NONE; +#endif +#ifndef WOLFSSH_NO_MLDSA87 + case ID_MLDSA87: + #ifdef WOLFSSH_CERTS + case ID_X509V3_MLDSA87: + #endif + return WC_HASH_TYPE_NONE; #endif /* SHA2-384 */ #ifndef WOLFSSH_NO_ECDH_SHA2_NISTP384 @@ -4376,6 +4682,32 @@ static INLINE byte AeadModeForId(byte id) } +#ifndef WOLFSSH_NO_MLDSA +static INLINE int KeyIdToMlDsaLevel(byte id) +{ + switch (id) { + case ID_MLDSA44: + #ifdef WOLFSSH_CERTS + case ID_X509V3_MLDSA44: + #endif + return WC_ML_DSA_44; + case ID_MLDSA65: + #ifdef WOLFSSH_CERTS + case ID_X509V3_MLDSA65: + #endif + return WC_ML_DSA_65; + case ID_MLDSA87: + #ifdef WOLFSSH_CERTS + case ID_X509V3_MLDSA87: + #endif + return WC_ML_DSA_87; + default: + return -1; + } +} +#endif + + static word32 AlgoListSz(const char* algoList) { word32 algoListSz; @@ -5096,6 +5428,7 @@ static int DoKexDhInit(WOLFSSH* ssh, byte* buf, word32 len, word32* idx) struct wolfSSH_sigKeyBlock { byte useRsa:1; byte useEcc:1; + byte useMlDsa:1; byte useEd25519:1; byte keyAllocated:1; word32 keySz; @@ -5110,6 +5443,11 @@ struct wolfSSH_sigKeyBlock { ecc_key key; } ecc; #endif +#ifndef WOLFSSH_NO_MLDSA + struct { + MlDsaKey key; + } mldsa; +#endif #ifndef WOLFSSH_NO_ED25519 struct { ed25519_key key; @@ -5305,6 +5643,56 @@ static int ParseEd25519PubKey(WOLFSSH *ssh, } #endif +#ifndef WOLFSSH_NO_MLDSA +/* Parse out a RAW ML-DSA public key from buffer */ +static int ParseMlDsaPubKey(WOLFSSH* ssh, + struct wolfSSH_sigKeyBlock* sigKeyBlock_ptr, + byte* pubKey, word32 pubKeySz, byte keyId) +{ + int ret; + const byte* pub; + word32 pubSz, pubKeyIdx = 0; + byte level; + + { + int mlLevel = KeyIdToMlDsaLevel(keyId); + if (mlLevel < 0) + return WS_INVALID_ALGO_ID; + level = (byte)mlLevel; + } + + ret = wc_MlDsaKey_Init(&sigKeyBlock_ptr->sk.mldsa.key, + ssh->ctx->heap, INVALID_DEVID); + if (ret == 0) { + ret = wc_MlDsaKey_SetParams(&sigKeyBlock_ptr->sk.mldsa.key, level); + if (ret != 0) { + wc_MlDsaKey_Free(&sigKeyBlock_ptr->sk.mldsa.key); + return WS_INVALID_ALGO_ID; + } + } + else { + return WS_INVALID_ALGO_ID; + } + + /* skip the algo name string */ + ret = GetSkip(pubKey, pubKeySz, &pubKeyIdx); + if (ret == WS_SUCCESS) + ret = GetStringRef(&pubSz, &pub, pubKey, pubKeySz, &pubKeyIdx); + if (ret == WS_SUCCESS) + ret = wc_MlDsaKey_ImportPubRaw(&sigKeyBlock_ptr->sk.mldsa.key, + pub, pubSz); + if (ret == 0) { + sigKeyBlock_ptr->keyAllocated = 1; + /* keySz intentionally not set */ + } + else { + wc_MlDsaKey_Free(&sigKeyBlock_ptr->sk.mldsa.key); + ret = WS_INVALID_ALGO_ID; + } + + return ret; +} +#endif #ifdef WOLFSSH_CERTS /* finds the leaf certificate and optionally the bounds of the cert chain, @@ -5534,6 +5922,52 @@ static int ParseRSAPubKeyCert(WOLFSSH *ssh, return ret; } + +#ifndef WOLFSSH_NO_MLDSA +/* Parse ML-DSA public key from an X.509 certificate blob. */ +static int ParseMlDsaPubKeyCert(WOLFSSH *ssh, + struct wolfSSH_sigKeyBlock *sigKeyBlock_ptr, byte *pubKey, + word32 pubKeySz, byte keyId) +{ + int ret; + byte* der = NULL; + word32 derSz, idx = 0; + int error; + int initDone = 0; + byte level; + + { + int mlLevel = KeyIdToMlDsaLevel(keyId); + if (mlLevel < 0) + return WS_INVALID_ALGO_ID; + level = (byte)mlLevel; + } + + ret = ParsePubKeyCert(ssh, pubKey, pubKeySz, &der, &derSz); + if (ret == WS_SUCCESS) { + error = wc_MlDsaKey_Init(&sigKeyBlock_ptr->sk.mldsa.key, ssh->ctx->heap, + INVALID_DEVID); + if (error == 0) { + initDone = 1; + error = wc_MlDsaKey_SetParams(&sigKeyBlock_ptr->sk.mldsa.key, + level); + } + if (error == 0) + error = wc_MlDsaKey_PublicKeyDecode(&sigKeyBlock_ptr->sk.mldsa.key, + der, derSz, &idx); + if (error == 0) { + sigKeyBlock_ptr->keyAllocated = 1; + } + else { + if (initDone) + wc_MlDsaKey_Free(&sigKeyBlock_ptr->sk.mldsa.key); + ret = WS_INVALID_ALGO_ID; + } + WFREE(der, NULL, 0); + } + return ret; +} +#endif #endif /* WOLFSSH_CERTS */ @@ -5580,6 +6014,25 @@ static int ParsePubKey(WOLFSSH *ssh, ret = ParseEd25519PubKey(ssh, sigKeyBlock_ptr, pubKey, pubKeySz); break; +#ifndef WOLFSSH_NO_MLDSA + case ID_MLDSA44: + case ID_MLDSA65: + case ID_MLDSA87: + sigKeyBlock_ptr->useMlDsa = 1; + ret = ParseMlDsaPubKey(ssh, sigKeyBlock_ptr, pubKey, + pubKeySz, ssh->handshake->pubKeyId); + break; + #ifdef WOLFSSH_CERTS + case ID_X509V3_MLDSA44: + case ID_X509V3_MLDSA65: + case ID_X509V3_MLDSA87: + sigKeyBlock_ptr->useMlDsa = 1; + ret = ParseMlDsaPubKeyCert(ssh, sigKeyBlock_ptr, pubKey, + pubKeySz, ssh->handshake->pubKeyId); + break; + #endif +#endif + default: ret = WS_INVALID_ALGO_ID; } @@ -5588,10 +6041,7 @@ static int ParsePubKey(WOLFSSH *ssh, } -/* Initialize the key in the block for its negotiated type. Sets - * keyAllocated so FreePubKey() releases it on every path, including the - * callers' error paths. Counterpart to FreePubKey(); the caller sets the - * use* flag before calling. */ +/* Init negotiated key type; sets keyAllocated so FreePubKey() cleans up. */ static int InitPubKey(struct wolfSSH_sigKeyBlock *p, WOLFSSH *ssh) { int ret = WS_INVALID_ALGO_ID; @@ -5643,6 +6093,11 @@ static void FreePubKey(struct wolfSSH_sigKeyBlock *p) wc_ed25519_free(&p->sk.ed25519.key); #endif } + else if (p->useMlDsa) { + #ifndef WOLFSSH_NO_MLDSA + wc_MlDsaKey_Free(&p->sk.mldsa.key); + #endif + } p->keyAllocated = 0; } } @@ -5838,12 +6293,14 @@ static int KeyAgreeEcdhMlKem_client(WOLFSSH* ssh, byte hashId, #if !defined(WOLFSSH_NO_NISTP256_MLKEM768_SHA256) || \ !defined(WOLFSSH_NO_NISTP384_MLKEM1024_SHA384) ecc_key *key_ptr = NULL; + int eccKeyInited = 0; #ifndef WOLFSSH_SMALL_STACK ecc_key key_s; #endif #endif #ifndef WOLFSSH_NO_CURVE25519_MLKEM768_SHA256 curve25519_key *x25519_key_ptr = NULL; + int x25519KeyInited = 0; #ifndef WOLFSSH_SMALL_STACK curve25519_key x25519_key_s; #endif @@ -5901,6 +6358,8 @@ static int KeyAgreeEcdhMlKem_client(WOLFSSH* ssh, byte hashId, if (ret == 0) { ret = wc_curve25519_init(x25519_key_ptr); + if (ret == 0) + x25519KeyInited = 1; } if (ret == 0) { ret = wc_curve25519_check_public(f + length_ciphertext, @@ -5919,7 +6378,8 @@ static int KeyAgreeEcdhMlKem_client(WOLFSSH* ssh, byte hashId, &ssh->kSz, EC25519_LITTLE_ENDIAN); PRIVATE_KEY_LOCK(); } - wc_curve25519_free(x25519_key_ptr); + if (x25519KeyInited) + wc_curve25519_free(x25519_key_ptr); #ifdef WOLFSSH_SMALL_STACK if (x25519_key_ptr) { WFREE(x25519_key_ptr, ssh->ctx->heap, DYNTYPE_PRIVKEY); @@ -5945,6 +6405,8 @@ static int KeyAgreeEcdhMlKem_client(WOLFSSH* ssh, byte hashId, if (ret == 0) { ret = wc_ecc_init(key_ptr); + if (ret == 0) + eccKeyInited = 1; } #ifdef HAVE_WC_ECC_SET_RNG if (ret == 0) { @@ -5963,7 +6425,8 @@ static int KeyAgreeEcdhMlKem_client(WOLFSSH* ssh, byte hashId, &ssh->kSz); PRIVATE_KEY_LOCK(); } - wc_ecc_free(key_ptr); + if (eccKeyInited) + wc_ecc_free(key_ptr); #ifdef WOLFSSH_SMALL_STACK if (key_ptr) { WFREE(key_ptr, ssh->ctx->heap, DYNTYPE_PRIVKEY); @@ -6453,6 +6916,19 @@ static int DoKexDhReply(WOLFSSH* ssh, byte* buf, word32 len, word32* idx) ret = WS_ED25519_E; } #endif /* WOLFSSH_NO_ED25519 */ + } + else if (sigKeyBlock_ptr->useMlDsa) { +#ifndef WOLFSSH_NO_MLDSA + int res = 0; + ret = wc_MlDsaKey_VerifyCtx(&sigKeyBlock_ptr->sk.mldsa.key, + sig, sigSz, NULL, 0, ssh->h, ssh->hSz, &res); + if (ret != 0 || res != 1) { + WLOG(WS_LOG_DEBUG, + "DoKexDhReply: ML-DSA Signature Verify fail (%d)", + ret); + ret = WS_MLDSA_E; + } +#endif /* WOLFSSH_NO_MLDSA */ } else { ret = WS_INVALID_ALGO_ID; @@ -8119,59 +8595,264 @@ static int DoUserAuthRequestEccCert(WOLFSSH* ssh, WS_UserAuthData_PublicKey* pk, #endif /* ! WOLFSSH_NO_ECDSA */ -#ifndef WOLFSSH_NO_ED25519 -static int DoUserAuthRequestEd25519(WOLFSSH* ssh, - WS_UserAuthData_PublicKey* pk, WS_UserAuthData* authData) +#ifndef WOLFSSH_NO_MLDSA +#ifdef WOLFSSH_CERTS +/* Extract ML-DSA public key from X.509 cert blob and import into key. */ +static int DoUserAuthRequestMlDsaImportCertPubKey(WOLFSSH* ssh, + WS_UserAuthData_PublicKey* pk, MlDsaKey* key) +{ + DecodedCert* cert = NULL; + byte* pubRaw = NULL; + word32 pubKeySz = 0; + word32 idx = 0; + int ret = WS_SUCCESS; + + cert = (DecodedCert*)WMALLOC(sizeof(DecodedCert), ssh->ctx->heap, + DYNTYPE_CERT); + if (cert == NULL) + return WS_MEMORY_E; + + wc_InitDecodedCert(cert, pk->publicKey, pk->publicKeySz, ssh->ctx->heap); + ret = wc_ParseCert(cert, CA_TYPE, 0, NULL); + if (ret == 0) { + ret = wc_GetPubKeyDerFromCert(cert, NULL, &pubKeySz); + if (ret == WC_NO_ERR_TRACE(LENGTH_ONLY_E)) { + pubRaw = (byte*)WMALLOC(pubKeySz, ssh->ctx->heap, DYNTYPE_PUBKEY); + if (pubRaw == NULL) { + ret = WS_MEMORY_E; + } + else { + ret = wc_GetPubKeyDerFromCert(cert, pubRaw, &pubKeySz); + if (ret == 0) + ret = wc_MlDsaKey_PublicKeyDecode(key, pubRaw, pubKeySz, + &idx); + WFREE(pubRaw, ssh->ctx->heap, DYNTYPE_PUBKEY); + } + } + } + wc_FreeDecodedCert(cert); + WFREE(cert, ssh->ctx->heap, DYNTYPE_CERT); + return ret; +} +#endif /* WOLFSSH_CERTS */ + +/* Verify an ML-DSA (or ML-DSA cert) public-key user auth request. */ +static int DoUserAuthRequestMlDsa(WOLFSSH* ssh, + WS_UserAuthData_PublicKey* pk, WS_UserAuthData* authData, byte level, + byte isCert, word32 pubKeyBlobSz) { const byte* publicKeyType; - byte temp[32]; + const byte* pubRawRef = NULL; word32 publicKeyTypeSz = 0; - word32 sz, qSz; + word32 pubRawSz = 0; + word32 sigSz = 0; word32 i = 0; int ret = WS_SUCCESS; - ed25519_key *key_ptr = NULL; -#ifndef WOLFSSH_SMALL_STACK - ed25519_key s_key; -#endif + int wcRet = 0; + int mlDsaInit = 0; + MlDsaKey *key_ptr = NULL; + byte* checkData = NULL; + word32 checkDataSz = 0; - WLOG(WS_LOG_DEBUG, "Entering DoUserAuthRequestEd25519()"); + WLOG(WS_LOG_DEBUG, "Entering DoUserAuthRequestMlDsa()"); if (ssh == NULL || ssh->ctx == NULL || pk == NULL || authData == NULL) { ret = WS_BAD_ARGUMENT; } if (ret == WS_SUCCESS) { -#ifdef WOLFSSH_SMALL_STACK - key_ptr = (ed25519_key*)WMALLOC(sizeof(ed25519_key), ssh->ctx->heap, - DYNTYPE_PUBKEY); - if (key_ptr == NULL) - ret = WS_MEMORY_E; -#else - key_ptr = &s_key; -#endif + key_ptr = (MlDsaKey*)WMALLOC(sizeof(MlDsaKey), ssh->ctx->heap, + DYNTYPE_PUBKEY); + if (key_ptr == NULL) + ret = WS_MEMORY_E; } if (ret == WS_SUCCESS) { - ret = wc_ed25519_init_ex(key_ptr, ssh->ctx->heap, INVALID_DEVID); - if (ret == 0) { - ret = WS_SUCCESS; + wcRet = wc_MlDsaKey_Init(key_ptr, ssh->ctx->heap, INVALID_DEVID); + if (wcRet == 0) { + mlDsaInit = 1; + wcRet = wc_MlDsaKey_SetParams(key_ptr, level); + } + if (wcRet != 0) { + ret = WS_CRYPTO_FAILED; } } - /* First check that the public key's type matches the one we are - * expecting. */ - if (ret == WS_SUCCESS) - ret = GetSize(&publicKeyTypeSz, pk->publicKey, pk->publicKeySz, &i); - if (ret == WS_SUCCESS) { - publicKeyType = pk->publicKey + i; - i += publicKeyTypeSz; - if (publicKeyTypeSz != pk->publicKeyTypeSz - || WMEMCMP(publicKeyType, - pk->publicKeyType, publicKeyTypeSz) != 0) { - WLOG(WS_LOG_DEBUG, - "Public Key's type does not match public key type"); - ret = WS_INVALID_ALGO_ID; + if (isCert) { +#ifdef WOLFSSH_CERTS + ret = DoUserAuthRequestMlDsaImportCertPubKey(ssh, pk, key_ptr); +#else + ret = WS_INVALID_ALGO_ID; +#endif + } + else { + /* Check that the public key's type matches what we expect. */ + ret = GetSize(&publicKeyTypeSz, pk->publicKey, pk->publicKeySz, &i); + + if (ret == WS_SUCCESS) { + publicKeyType = pk->publicKey + i; + i += publicKeyTypeSz; + if (publicKeyTypeSz != pk->publicKeyTypeSz + || WMEMCMP(publicKeyType, + pk->publicKeyType, publicKeyTypeSz) != 0) { + WLOG(WS_LOG_DEBUG, + "Public Key's type does not match public key type"); + ret = WS_INVALID_ALGO_ID; + } + } + + if (ret == WS_SUCCESS) { + ret = GetStringRef(&pubRawSz, &pubRawRef, pk->publicKey, + pk->publicKeySz, &i); + if (ret == WS_SUCCESS) + ret = wc_MlDsaKey_ImportPubRaw(key_ptr, pubRawRef, + pubRawSz); + } + } + } + + if (ret != 0) { + WLOG(WS_LOG_DEBUG, "Could not decode public key (%d)", ret); + if (ret != WS_MEMORY_E && ret != WS_BAD_ARGUMENT && + ret != WS_INVALID_ALGO_ID) { + ret = WS_CRYPTO_FAILED; + } + } + + /* Verify signature */ + if (ret == WS_SUCCESS) { + i = 0; + ret = GetSize(&publicKeyTypeSz, pk->signature, pk->signatureSz, &i); + } + + if (ret == WS_SUCCESS) { + publicKeyType = pk->signature + i; + i += publicKeyTypeSz; + + /* Intentionally stricter than DoUserAuthRequestRsaCert (RFC 6187 + * Section 5): X.509 SSH is still draft with no settled "underlying + * algorithm name" wire convention, so require the inner signature + * type to match x509v3-ssh-mldsa-XX exactly until the draft settles. */ + if (publicKeyTypeSz != pk->publicKeyTypeSz + || WMEMCMP(publicKeyType, pk->publicKeyType, + publicKeyTypeSz) != 0) { + + WLOG(WS_LOG_DEBUG, + "Signature's type does not match public key type"); + ret = WS_INVALID_ALGO_ID; + } + } + + if (ret == WS_SUCCESS) { + ret = GetSize(&sigSz, pk->signature, pk->signatureSz, &i); + } + + if (ret == WS_SUCCESS) { + word32 dataToSignSz = authData->usernameSz + + authData->serviceNameSz + + authData->authNameSz + BOOLEAN_SZ + + pk->publicKeyTypeSz + pubKeyBlobSz + + (UINT32_SZ * 5); + checkDataSz = UINT32_SZ + ssh->sessionIdSz + MSG_ID_SZ + dataToSignSz; + checkData = (byte*)WMALLOC(checkDataSz, ssh->ctx->heap, DYNTYPE_TEMP); + if (checkData == NULL) { + ret = WS_MEMORY_E; + } + else { + word32 idx = 0; + c32toa(ssh->sessionIdSz, checkData + idx); + idx += UINT32_SZ; + WMEMCPY(checkData + idx, ssh->sessionId, ssh->sessionIdSz); + idx += ssh->sessionIdSz; + checkData[idx++] = MSGID_USERAUTH_REQUEST; + WMEMCPY(checkData + idx, pk->dataToSign, dataToSignSz); + } + } + + if (ret == WS_SUCCESS) { + int status = 0; + ret = wc_MlDsaKey_VerifyCtx(key_ptr, pk->signature + i, sigSz, + NULL, 0, checkData, checkDataSz, &status); + if (ret != 0) { + WLOG(WS_LOG_DEBUG, "DUARMlDsa: Signature Verify fail (%d)", ret); + ret = WS_CRYPTO_FAILED; + } + else if (status != 1) { + ret = WS_MLDSA_E; + } + } + + if (checkData != NULL) { + ForceZero(checkData, checkDataSz); + WFREE(checkData, ssh->ctx->heap, DYNTYPE_TEMP); + } + + if (key_ptr != NULL) { + if (mlDsaInit) + wc_MlDsaKey_Free(key_ptr); + WFREE(key_ptr, ssh->ctx->heap, DYNTYPE_PUBKEY); + } + + WLOG(WS_LOG_DEBUG, "Leaving DoUserAuthRequestMlDsa(), ret = %d", ret); + return ret; +} +#endif /* !WOLFSSH_NO_MLDSA */ + + +#ifndef WOLFSSH_NO_ED25519 +static int DoUserAuthRequestEd25519(WOLFSSH* ssh, + WS_UserAuthData_PublicKey* pk, WS_UserAuthData* authData) +{ + const byte* publicKeyType; + byte temp[32]; + word32 publicKeyTypeSz = 0; + word32 sz, qSz; + word32 i = 0; + int ret = WS_SUCCESS; + ed25519_key *key_ptr = NULL; +#ifndef WOLFSSH_SMALL_STACK + ed25519_key s_key; +#endif + + WLOG(WS_LOG_DEBUG, "Entering DoUserAuthRequestEd25519()"); + + if (ssh == NULL || ssh->ctx == NULL || pk == NULL || authData == NULL) { + ret = WS_BAD_ARGUMENT; + } + + if (ret == WS_SUCCESS) { +#ifdef WOLFSSH_SMALL_STACK + key_ptr = (ed25519_key*)WMALLOC(sizeof(ed25519_key), ssh->ctx->heap, + DYNTYPE_PUBKEY); + if (key_ptr == NULL) + ret = WS_MEMORY_E; +#else + key_ptr = &s_key; +#endif + } + + if (ret == WS_SUCCESS) { + ret = wc_ed25519_init_ex(key_ptr, ssh->ctx->heap, INVALID_DEVID); + if (ret == 0) { + ret = WS_SUCCESS; + } + } + + /* First check that the public key's type matches the one we are + * expecting. */ + if (ret == WS_SUCCESS) + ret = GetSize(&publicKeyTypeSz, pk->publicKey, pk->publicKeySz, &i); + + if (ret == WS_SUCCESS) { + publicKeyType = pk->publicKey + i; + i += publicKeyTypeSz; + if (publicKeyTypeSz != pk->publicKeyTypeSz + || WMEMCMP(publicKeyType, + pk->publicKeyType, publicKeyTypeSz) != 0) { + WLOG(WS_LOG_DEBUG, + "Public Key's type does not match public key type"); + ret = WS_INVALID_ALGO_ID; } } if (ret == WS_SUCCESS) { @@ -8269,7 +8950,8 @@ static int DoUserAuthRequestEd25519(WOLFSSH* ssh, } #endif /* !WOLFSSH_NO_ED25519 */ -#if !defined(WOLFSSH_NO_RSA) || !defined(WOLFSSH_NO_ECDSA) +#if !defined(WOLFSSH_NO_RSA) || !defined(WOLFSSH_NO_ECDSA) \ + || !defined(WOLFSSH_NO_ED25519) || !defined(WOLFSSH_NO_MLDSA) /* Utility for DoUserAuthRequest() */ static int DoUserAuthRequestPublicKey(WOLFSSH* ssh, WS_UserAuthData* authData, byte* buf, word32 len, word32* idx) @@ -8376,7 +9058,17 @@ static int DoUserAuthRequestPublicKey(WOLFSSH* ssh, WS_UserAuthData* authData, if (pkTypeId == ID_X509V3_SSH_RSA || pkTypeId == ID_X509V3_ECDSA_SHA2_NISTP256 || pkTypeId == ID_X509V3_ECDSA_SHA2_NISTP384 - || pkTypeId == ID_X509V3_ECDSA_SHA2_NISTP521) { + || pkTypeId == ID_X509V3_ECDSA_SHA2_NISTP521 + #ifndef WOLFSSH_NO_MLDSA44 + || pkTypeId == ID_X509V3_MLDSA44 + #endif + #ifndef WOLFSSH_NO_MLDSA65 + || pkTypeId == ID_X509V3_MLDSA65 + #endif + #ifndef WOLFSSH_NO_MLDSA87 + || pkTypeId == ID_X509V3_MLDSA87 + #endif + ) { byte *cert = NULL; word32 certSz = 0; @@ -8489,7 +9181,30 @@ static int DoUserAuthRequestPublicKey(WOLFSSH* ssh, WS_UserAuthData* authData, #else ret = WS_INVALID_ALGO_ID; #endif - } else { + } +#ifndef WOLFSSH_NO_MLDSA + else if (pkTypeId == ID_MLDSA44 || + pkTypeId == ID_MLDSA65 || + pkTypeId == ID_MLDSA87) { + int mlLevel = KeyIdToMlDsaLevel(pkTypeId); + if (mlLevel < 0) + ret = WS_INVALID_ALGO_ID; + else + ret = DoUserAuthRequestMlDsa(ssh, &authData->sf.publicKey, + authData, (byte)mlLevel, 0, pubKeyBlobSz); + } + else if (pkTypeId == ID_X509V3_MLDSA44 || + pkTypeId == ID_X509V3_MLDSA65 || + pkTypeId == ID_X509V3_MLDSA87) { + int mlLevel = KeyIdToMlDsaLevel(pkTypeId); + if (mlLevel < 0) + ret = WS_INVALID_ALGO_ID; + else + ret = DoUserAuthRequestMlDsa(ssh, &authData->sf.publicKey, + authData, (byte)mlLevel, 1, pubKeyBlobSz); + } +#endif + else { wc_HashAlg hash; byte digest[WC_MAX_DIGEST_SIZE]; word32 digestSz = 0; @@ -8624,7 +9339,7 @@ static int DoUserAuthRequestPublicKey(WOLFSSH* ssh, WS_UserAuthData* authData, WLOG(WS_LOG_DEBUG, "Leaving DoUserAuthRequestPublicKey(), ret = %d", ret); return ret; } -#endif +#endif /* !WOLFSSH_NO_RSA/ECDSA/ED25519/MLDSA */ static int DoUserAuthRequest(WOLFSSH* ssh, @@ -8679,7 +9394,8 @@ static int DoUserAuthRequest(WOLFSSH* ssh, ret = SendUserAuthKeyboardRequest(ssh, &authData); } #endif -#if !defined(WOLFSSH_NO_RSA) || !defined(WOLFSSH_NO_ECDSA) +#if !defined(WOLFSSH_NO_RSA) || !defined(WOLFSSH_NO_ECDSA) \ + || !defined(WOLFSSH_NO_ED25519) || !defined(WOLFSSH_NO_MLDSA) else if (authNameId == ID_USERAUTH_PUBLICKEY) { authData.sf.publicKey.dataToSign = buf + *idx; ret = DoUserAuthRequestPublicKey(ssh, &authData, buf, len, &begin); @@ -11624,7 +12340,7 @@ struct wolfSSH_sigKeyBlockFull { const char *primeName; word32 primeNameSz; } ecc; - +#endif #ifndef WOLFSSH_NO_ED25519 struct { ed25519_key key; @@ -11636,6 +12352,19 @@ struct wolfSSH_sigKeyBlockFull { byte qPad; } ed; #endif +#ifndef WOLFSSH_NO_MLDSA + struct { + MlDsaKey key; + /* Size to highest enabled ML-DSA level; qSz tracks actual. */ +#ifndef WOLFSSH_NO_MLDSA87 + byte q[WC_MLDSA_87_PUB_KEY_SIZE]; +#elif !defined(WOLFSSH_NO_MLDSA65) + byte q[WC_MLDSA_65_PUB_KEY_SIZE]; +#else + byte q[WC_MLDSA_44_PUB_KEY_SIZE]; +#endif + word32 qSz; + } mldsa; #endif } sk; }; @@ -11653,7 +12382,11 @@ struct wolfSSH_sigKeyBlockFull { #define KEX_F_SIZE (256 + 1) #endif -#define KEX_SIG_SIZE (512) +#ifdef WOLFSSH_NO_MLDSA + #define KEX_SIG_SIZE (512) +#else + #define KEX_SIG_SIZE MLDSA_MAX_SIG_SIZE +#endif #ifdef WOLFSSH_CERTS /* places RFC6187 style cert + ocsp into output buffer and advances idx @@ -11699,6 +12432,22 @@ static int BuildRFC6187Info(WOLFSSH* ssh, int pubKeyID, break; #endif + #ifndef WOLFSSH_NO_MLDSA44 + case ID_X509V3_MLDSA44: + publicKeyType = (const byte*)cannedKeyAlgoX509Mldsa44Names; + break; + #endif + #ifndef WOLFSSH_NO_MLDSA65 + case ID_X509V3_MLDSA65: + publicKeyType = (const byte*)cannedKeyAlgoX509Mldsa65Names; + break; + #endif + #ifndef WOLFSSH_NO_MLDSA87 + case ID_X509V3_MLDSA87: + publicKeyType = (const byte*)cannedKeyAlgoX509Mldsa87Names; + break; + #endif + default: return WS_BAD_ARGUMENT; } @@ -12093,6 +12842,89 @@ static int SendKexGetSigningKey(WOLFSSH* ssh, #endif #endif + #ifndef WOLFSSH_NO_MLDSA + #ifdef WOLFSSH_CERTS + case ID_X509V3_MLDSA44: + case ID_X509V3_MLDSA65: + case ID_X509V3_MLDSA87: + isCert = 1; + FALL_THROUGH; + #endif + case ID_MLDSA44: + case ID_MLDSA65: + case ID_MLDSA87: + { + byte level; + + WLOG(WS_LOG_DEBUG, "Using ML-DSA Host key"); + + { + int mlLevel = KeyIdToMlDsaLevel(sigKeyBlock_ptr->pubKeyId); + if (mlLevel < 0) { + ret = WS_INVALID_ALGO_ID; + break; + } + level = (byte)mlLevel; + } + + sigKeyBlock_ptr->sk.mldsa.qSz = + sizeof(sigKeyBlock_ptr->sk.mldsa.q); + + ret = wc_MlDsaKey_Init(&sigKeyBlock_ptr->sk.mldsa.key, + heap, INVALID_DEVID); + if (ret == 0) + ret = wc_MlDsaKey_SetParams(&sigKeyBlock_ptr->sk.mldsa.key, + level); + scratch = 0; + if (ret == 0) + ret = wc_MlDsaKey_PrivateKeyDecode( + &sigKeyBlock_ptr->sk.mldsa.key, + ssh->ctx->privateKey[keyIdx].key, + ssh->ctx->privateKey[keyIdx].keySz, &scratch); + if (ret == 0) + ret = wc_MlDsaKey_ExportPubRaw( + &sigKeyBlock_ptr->sk.mldsa.key, + sigKeyBlock_ptr->sk.mldsa.q, + &sigKeyBlock_ptr->sk.mldsa.qSz); + + /* Hash in raw public key only for non-cert path. */ + if (!isCert) { + /* Hash in the length of the public key block. */ + if (ret == 0) { + sigKeyBlock_ptr->sz = (LENGTH_SZ * 2) + + sigKeyBlock_ptr->pubKeyFmtNameSz + + sigKeyBlock_ptr->sk.mldsa.qSz; + c32toa(sigKeyBlock_ptr->sz, scratchLen); + ret = wc_HashUpdate(hash, hashId, + scratchLen, LENGTH_SZ); + } + /* Hash in the length of the key type string. */ + if (ret == 0) { + c32toa(sigKeyBlock_ptr->pubKeyFmtNameSz, scratchLen); + ret = wc_HashUpdate(hash, hashId, + scratchLen, LENGTH_SZ); + } + /* Hash in the key type string. */ + if (ret == 0) + ret = wc_HashUpdate(hash, hashId, + (byte*)sigKeyBlock_ptr->pubKeyFmtName, + sigKeyBlock_ptr->pubKeyFmtNameSz); + /* Hash in the length of the public key. */ + if (ret == 0) { + c32toa(sigKeyBlock_ptr->sk.mldsa.qSz, scratchLen); + ret = wc_HashUpdate(hash, hashId, + scratchLen, LENGTH_SZ); + } + /* Hash in the public key. */ + if (ret == 0) + ret = wc_HashUpdate(hash, hashId, + sigKeyBlock_ptr->sk.mldsa.q, + sigKeyBlock_ptr->sk.mldsa.qSz); + } + break; + } + #endif /* WOLFSSH_NO_MLDSA */ + default: ret = WS_INVALID_ALGO_ID; } @@ -12236,6 +13068,21 @@ static INLINE byte SigTypeForId(byte id) id = ID_ECDSA_SHA2_NISTP521; break; #endif + #ifndef WOLFSSH_NO_MLDSA44 + case ID_X509V3_MLDSA44: + id = ID_MLDSA44; + break; + #endif + #ifndef WOLFSSH_NO_MLDSA65 + case ID_X509V3_MLDSA65: + id = ID_MLDSA65; + break; + #endif + #ifndef WOLFSSH_NO_MLDSA87 + case ID_X509V3_MLDSA87: + id = ID_MLDSA87; + break; + #endif } #endif @@ -12600,6 +13447,8 @@ static int KeyAgreeEcdhMlKem_server(WOLFSSH* ssh, byte hashId, !defined(WOLFSSH_NO_NISTP384_MLKEM1024_SHA384) ecc_key* pubKey = NULL; ecc_key* privKey = NULL; + int pubKeyInited = 0; + int privKeyInited = 0; int primeId; #ifndef WOLFSSH_SMALL_STACK ecc_key eccKeys[2]; @@ -12608,6 +13457,8 @@ static int KeyAgreeEcdhMlKem_server(WOLFSSH* ssh, byte hashId, #ifndef WOLFSSH_NO_CURVE25519_MLKEM768_SHA256 curve25519_key* x25519PubKey = NULL; curve25519_key* x25519PrivKey = NULL; + int x25519PubKeyInited = 0; + int x25519PrivKeyInited = 0; #ifndef WOLFSSH_SMALL_STACK curve25519_key x25519Keys[2]; #endif @@ -12684,9 +13535,13 @@ static int KeyAgreeEcdhMlKem_server(WOLFSSH* ssh, byte hashId, if (ret == 0) { ret = wc_curve25519_init(x25519PubKey); + if (ret == 0) + x25519PubKeyInited = 1; } if (ret == 0) { ret = wc_curve25519_init(x25519PrivKey); + if (ret == 0) + x25519PrivKeyInited = 1; } if (ret == 0) { ret = wc_curve25519_check_public( @@ -12721,9 +13576,9 @@ static int KeyAgreeEcdhMlKem_server(WOLFSSH* ssh, byte hashId, PRIVATE_KEY_LOCK(); ssh->kSz = length_sharedsecret + tmp_kSz; } - if (x25519PrivKey) + if (x25519PrivKeyInited) wc_curve25519_free(x25519PrivKey); - if (x25519PubKey) + if (x25519PubKeyInited) wc_curve25519_free(x25519PubKey); #ifdef WOLFSSH_SMALL_STACK if (x25519PubKey) @@ -12762,9 +13617,13 @@ static int KeyAgreeEcdhMlKem_server(WOLFSSH* ssh, byte hashId, if (ret == 0) { ret = wc_ecc_init_ex(pubKey, ssh->ctx->heap, INVALID_DEVID); + if (ret == 0) + pubKeyInited = 1; } if (ret == 0) { ret = wc_ecc_init_ex(privKey, ssh->ctx->heap, INVALID_DEVID); + if (ret == 0) + privKeyInited = 1; } #ifdef HAVE_WC_ECC_SET_RNG if (ret == 0) { @@ -12796,9 +13655,9 @@ static int KeyAgreeEcdhMlKem_server(WOLFSSH* ssh, byte hashId, PRIVATE_KEY_LOCK(); ssh->kSz = length_sharedsecret + tmp_kSz; } - if (privKey) + if (privKeyInited) wc_ecc_free(privKey); - if (pubKey) + if (pubKeyInited) wc_ecc_free(pubKey); #ifdef WOLFSSH_SMALL_STACK if (pubKey) @@ -13139,6 +13998,24 @@ static int SignHEd25519(WOLFSSH* ssh, byte* sig, word32* sigSz, #endif /* WOLFSSH_NO_ED25519 */ +#ifndef WOLFSSH_NO_MLDSA +static int SignHMlDsa(WOLFSSH* ssh, byte* sig, word32* sigSz, + struct wolfSSH_sigKeyBlockFull *sigKey) +{ + int ret; + WLOG(WS_LOG_DEBUG, "Entering SignHMlDsa()"); + ret = wc_MlDsaKey_SignCtx(&sigKey->sk.mldsa.key, NULL, 0, + sig, sigSz, ssh->h, ssh->hSz, ssh->rng); + if (ret != 0) { + WLOG(WS_LOG_DEBUG, "SignHMlDsa: Bad ML-DSA Sign (error: %d)", ret); + ret = WS_MLDSA_E; + } + WLOG(WS_LOG_DEBUG, "Leaving SignHMlDsa(), ret = %d", ret); + return ret; +} +#endif + + static int SignH(WOLFSSH* ssh, byte* sig, word32* sigSz, struct wolfSSH_sigKeyBlockFull *sigKey) { @@ -13162,6 +14039,16 @@ static int SignH(WOLFSSH* ssh, byte* sig, word32* sigSz, case ID_ED25519: ret = SignHEd25519(ssh, sig, sigSz, sigKey); break; +#ifndef WOLFSSH_NO_MLDSA + case ID_MLDSA44: + case ID_MLDSA65: + case ID_MLDSA87: + case ID_X509V3_MLDSA44: + case ID_X509V3_MLDSA65: + case ID_X509V3_MLDSA87: + ret = SignHMlDsa(ssh, sig, sigSz, sigKey); + break; +#endif default: ret = WS_INVALID_ALGO_ID; } @@ -13198,7 +14085,9 @@ int SendKexDhReply(WOLFSSH* ssh) struct wolfSSH_sigKeyBlockFull *sigKeyBlock_ptr = NULL; #ifndef WOLFSSH_SMALL_STACK byte f_s[KEX_F_SIZE]; +#ifdef WOLFSSH_NO_MLDSA byte sig_s[KEX_SIG_SIZE]; +#endif #endif byte msgId = 0; byte useDh = 0; @@ -13225,15 +14114,20 @@ int SendKexDhReply(WOLFSSH* ssh) ret = WS_MEMORY_E; #else f_ptr = f_s; +#ifdef WOLFSSH_NO_MLDSA sig_ptr = sig_s; +#else + sig_ptr = (byte*)WMALLOC(KEX_SIG_SIZE, heap, DYNTYPE_BUFFER); + if (sig_ptr == NULL) + ret = WS_MEMORY_E; +#endif #endif sigKeyBlock_ptr = (struct wolfSSH_sigKeyBlockFull*)WMALLOC( sizeof(struct wolfSSH_sigKeyBlockFull), heap, DYNTYPE_PRIVKEY); if (sigKeyBlock_ptr == NULL) ret = WS_MEMORY_E; - - if (ret == WS_SUCCESS) { + else { WMEMSET(sigKeyBlock_ptr, 0, sizeof(struct wolfSSH_sigKeyBlockFull)); sigKeyBlock_ptr->pubKeyId = ID_NONE; } @@ -13439,14 +14333,26 @@ int SendKexDhReply(WOLFSSH* ssh) } if (sigKeyBlock_ptr != NULL) { - if (sigKeyBlock_ptr->pubKeyFmtId == ID_SSH_RSA) { + if (sigKeyBlock_ptr->pubKeyId == ID_SSH_RSA + || sigKeyBlock_ptr->pubKeyId == ID_RSA_SHA2_256 + || sigKeyBlock_ptr->pubKeyId == ID_RSA_SHA2_512 + #ifdef WOLFSSH_CERTS + || sigKeyBlock_ptr->pubKeyId == ID_X509V3_SSH_RSA + #endif + ) { #ifndef WOLFSSH_NO_RSA wc_FreeRsaKey(&sigKeyBlock_ptr->sk.rsa.key); #endif } - else if (sigKeyBlock_ptr->pubKeyFmtId == ID_ECDSA_SHA2_NISTP256 - || sigKeyBlock_ptr->pubKeyFmtId == ID_ECDSA_SHA2_NISTP384 - || sigKeyBlock_ptr->pubKeyFmtId == ID_ECDSA_SHA2_NISTP521) { + else if (sigKeyBlock_ptr->pubKeyId == ID_ECDSA_SHA2_NISTP256 + || sigKeyBlock_ptr->pubKeyId == ID_ECDSA_SHA2_NISTP384 + || sigKeyBlock_ptr->pubKeyId == ID_ECDSA_SHA2_NISTP521 + #ifdef WOLFSSH_CERTS + || sigKeyBlock_ptr->pubKeyId == ID_X509V3_ECDSA_SHA2_NISTP256 + || sigKeyBlock_ptr->pubKeyId == ID_X509V3_ECDSA_SHA2_NISTP384 + || sigKeyBlock_ptr->pubKeyId == ID_X509V3_ECDSA_SHA2_NISTP521 + #endif + ) { #ifndef WOLFSSH_NO_ECDSA wc_ecc_free(&sigKeyBlock_ptr->sk.ecc.key); #endif @@ -13456,6 +14362,19 @@ int SendKexDhReply(WOLFSSH* ssh) wc_ed25519_free(&sigKeyBlock_ptr->sk.ed.key); #endif } +#if !defined(WOLFSSH_NO_MLDSA) + else if (sigKeyBlock_ptr->pubKeyId == ID_MLDSA44 || + sigKeyBlock_ptr->pubKeyId == ID_MLDSA65 || + sigKeyBlock_ptr->pubKeyId == ID_MLDSA87 + #ifdef WOLFSSH_CERTS + || sigKeyBlock_ptr->pubKeyId == ID_X509V3_MLDSA44 + || sigKeyBlock_ptr->pubKeyId == ID_X509V3_MLDSA65 + || sigKeyBlock_ptr->pubKeyId == ID_X509V3_MLDSA87 + #endif + ) { + wc_MlDsaKey_Free(&sigKeyBlock_ptr->sk.mldsa.key); + } +#endif } if (ret == WS_SUCCESS) { @@ -13471,7 +14390,13 @@ int SendKexDhReply(WOLFSSH* ssh) if (sigKeyBlock_ptr->pubKeyFmtId == ID_X509V3_SSH_RSA || sigKeyBlock_ptr->pubKeyFmtId == ID_X509V3_ECDSA_SHA2_NISTP256 || sigKeyBlock_ptr->pubKeyFmtId == ID_X509V3_ECDSA_SHA2_NISTP384 - || sigKeyBlock_ptr->pubKeyFmtId == ID_X509V3_ECDSA_SHA2_NISTP521) { + || sigKeyBlock_ptr->pubKeyFmtId == ID_X509V3_ECDSA_SHA2_NISTP521 + #ifndef WOLFSSH_NO_MLDSA + || sigKeyBlock_ptr->pubKeyFmtId == ID_X509V3_MLDSA44 + || sigKeyBlock_ptr->pubKeyFmtId == ID_X509V3_MLDSA65 + || sigKeyBlock_ptr->pubKeyFmtId == ID_X509V3_MLDSA87 + #endif + ) { payloadSz = MSG_ID_SZ + (LENGTH_SZ * 2) + sigKeyBlock_ptr->sz + fSz + fPad + sigBlockSz; } @@ -13494,7 +14419,13 @@ int SendKexDhReply(WOLFSSH* ssh) if (sigKeyBlock_ptr->pubKeyFmtId == ID_X509V3_SSH_RSA || sigKeyBlock_ptr->pubKeyFmtId == ID_X509V3_ECDSA_SHA2_NISTP256 || sigKeyBlock_ptr->pubKeyFmtId == ID_X509V3_ECDSA_SHA2_NISTP384 - || sigKeyBlock_ptr->pubKeyFmtId == ID_X509V3_ECDSA_SHA2_NISTP521) { + || sigKeyBlock_ptr->pubKeyFmtId == ID_X509V3_ECDSA_SHA2_NISTP521 +#ifndef WOLFSSH_NO_MLDSA + || sigKeyBlock_ptr->pubKeyFmtId == ID_X509V3_MLDSA44 + || sigKeyBlock_ptr->pubKeyFmtId == ID_X509V3_MLDSA65 + || sigKeyBlock_ptr->pubKeyFmtId == ID_X509V3_MLDSA87 +#endif + ) { /* BuildRFC6187Info writes the complete K_S including * the outer length and key type name. Skip common header. */ } @@ -13570,11 +14501,30 @@ int SendKexDhReply(WOLFSSH* ssh) } break; +#ifndef WOLFSSH_NO_MLDSA + case ID_MLDSA44: + case ID_MLDSA65: + case ID_MLDSA87: + { + c32toa(sigKeyBlock_ptr->sk.mldsa.qSz, output + idx); + idx += LENGTH_SZ; + WMEMCPY(output + idx, sigKeyBlock_ptr->sk.mldsa.q, + sigKeyBlock_ptr->sk.mldsa.qSz); + idx += sigKeyBlock_ptr->sk.mldsa.qSz; + } + break; +#endif + #ifdef WOLFSSH_CERTS case ID_X509V3_SSH_RSA: case ID_X509V3_ECDSA_SHA2_NISTP256: case ID_X509V3_ECDSA_SHA2_NISTP384: case ID_X509V3_ECDSA_SHA2_NISTP521: +#ifndef WOLFSSH_NO_MLDSA + case ID_X509V3_MLDSA44: + case ID_X509V3_MLDSA65: + case ID_X509V3_MLDSA87: +#endif { ret = BuildRFC6187Info(ssh, sigKeyBlock_ptr->pubKeyId, ssh->ctx->privateKey[keyIdx].cert, @@ -13626,13 +14576,18 @@ int SendKexDhReply(WOLFSSH* ssh) PurgePacket(ssh); WLOG(WS_LOG_DEBUG, "Leaving SendKexDhReply(), ret = %d", ret); - if (sigKeyBlock_ptr) + if (sigKeyBlock_ptr) { + ForceZero(sigKeyBlock_ptr, sizeof(struct wolfSSH_sigKeyBlockFull)); WFREE(sigKeyBlock_ptr, heap, DYNTYPE_PRIVKEY); + } #ifdef WOLFSSH_SMALL_STACK if (f_ptr) WFREE(f_ptr, heap, DYNTYPE_BUFFER); if (sig_ptr) WFREE(sig_ptr, heap, DYNTYPE_BUFFER); +#elif !defined(WOLFSSH_NO_MLDSA) + if (sig_ptr) + WFREE(sig_ptr, heap, DYNTYPE_BUFFER); #endif WOLFSSH_UNUSED(heap); return ret; @@ -15953,8 +16908,252 @@ static int BuildUserAuthRequestEd25519(WOLFSSH* ssh, #endif /* WOLFSSH_NO_ED25519 */ +#ifndef WOLFSSH_NO_MLDSA +/* Load key to keySig and get signature payload size. */ +static int PrepareUserAuthRequestMlDsa(WOLFSSH* ssh, word32* payloadSz, + const WS_UserAuthData* authData, WS_KeySignature* keySig) +{ + int ret = WS_SUCCESS; + + WLOG(WS_LOG_DEBUG, "Entering PrepareUserAuthRequestMlDsa()"); + if (ssh == NULL || payloadSz == NULL || authData == NULL || keySig == NULL) + ret = WS_BAD_ARGUMENT; + + if (ret == WS_SUCCESS) { + word32 idx = 0; + byte level = 0; + byte expectedKeyId = keySig->keyId; + { + int mlLevel = KeyIdToMlDsaLevel(keySig->keyId); + if (mlLevel < 0) ret = WS_INVALID_ALGO_ID; + else level = (byte)mlLevel; + } + if (ret == WS_SUCCESS) { + int mlDsaInit = 0; + ret = wc_MlDsaKey_Init(&keySig->ks.mldsa.key, keySig->heap, + INVALID_DEVID); + if (ret == 0) { + mlDsaInit = 1; + ret = wc_MlDsaKey_SetParams(&keySig->ks.mldsa.key, level); + } + if (ret == 0) + ret = wc_MlDsaKey_PrivateKeyDecode(&keySig->ks.mldsa.key, + authData->sf.publicKey.privateKey, + authData->sf.publicKey.privateKeySz, &idx); + if (ret != 0) { + if (mlDsaInit) { + wc_MlDsaKey_Free(&keySig->ks.mldsa.key); + keySig->keyId = ID_NONE; + } + idx = 0; + ret = GetOpenSshKey(keySig, + authData->sf.publicKey.privateKey, + authData->sf.publicKey.privateKeySz, &idx); + if (ret == WS_SUCCESS && keySig->keyId != expectedKeyId) { + wolfSSH_KEY_clean(keySig); + keySig->keyId = ID_NONE; + ret = WS_KEY_FORMAT_E; + } + } + } + } + + if (ret == WS_SUCCESS) { + if (authData->sf.publicKey.hasSignature) { + int sigSz = wc_MlDsaKey_SigSize(&keySig->ks.mldsa.key); + + if (sigSz >= 0) { + *payloadSz += (LENGTH_SZ * 3) + (word32)sigSz + + authData->sf.publicKey.publicKeyTypeSz; + keySig->sigSz = sigSz; + } + else { + wc_MlDsaKey_Free(&keySig->ks.mldsa.key); + keySig->keyId = ID_NONE; + ret = WS_CRYPTO_FAILED; + } + } + } + + WLOG(WS_LOG_DEBUG, + "Leaving PrepareUserAuthRequestMlDsa(), ret = %d", ret); + return ret; +} + +#ifdef WOLFSSH_CERTS +/* Load cert private key to keySig and get signature payload size. */ +static int PrepareUserAuthRequestMlDsaCert(WOLFSSH* ssh, word32* payloadSz, + const WS_UserAuthData* authData, WS_KeySignature* keySig) +{ + int ret = WS_SUCCESS; + + WLOG(WS_LOG_DEBUG, "Entering PrepareUserAuthRequestMlDsaCert()"); + if (ssh == NULL || payloadSz == NULL || authData == NULL || keySig == NULL) + ret = WS_BAD_ARGUMENT; + + if (ret == WS_SUCCESS) { + int mlLevel = KeyIdToMlDsaLevel(keySig->keyId); + if (mlLevel < 0) { + ret = WS_INVALID_ALGO_ID; + } + else { + byte level = (byte)mlLevel; + int wcRet; + int mlDsaInit = 0; + + wcRet = wc_MlDsaKey_Init(&keySig->ks.mldsa.key, + keySig->heap, INVALID_DEVID); + if (wcRet == 0) { + mlDsaInit = 1; + wcRet = wc_MlDsaKey_SetParams(&keySig->ks.mldsa.key, level); + } + if (wcRet != 0) { + if (mlDsaInit) { + wc_MlDsaKey_Free(&keySig->ks.mldsa.key); + keySig->keyId = ID_NONE; + } + ret = WS_CRYPTO_FAILED; + } + } + } + + if (ret == WS_SUCCESS) { + /* Reaching here guarantees the key is initialized: the preceding + * Init+SetParams block sets ret to WS_SUCCESS only on success. */ + word32 idx = 0; + int wcRet = wc_MlDsaKey_PrivateKeyDecode(&keySig->ks.mldsa.key, + authData->sf.publicKey.privateKey, + authData->sf.publicKey.privateKeySz, &idx); + if (wcRet != 0) { + wc_MlDsaKey_Free(&keySig->ks.mldsa.key); + keySig->keyId = ID_NONE; + ret = WS_CRYPTO_FAILED; + } + } + + if (ret == WS_SUCCESS) { + *payloadSz += (LENGTH_SZ + authData->sf.publicKey.publicKeyTypeSz) + + (UINT32_SZ * 2); /* certificate and ocsp counts */ + + if (authData->sf.publicKey.hasSignature) { + int sigSz = wc_MlDsaKey_SigSize(&keySig->ks.mldsa.key); + + if (sigSz >= 0) { + *payloadSz += (LENGTH_SZ * 3) + (word32)sigSz + + authData->sf.publicKey.publicKeyTypeSz; + keySig->sigSz = sigSz; + } + else { + wc_MlDsaKey_Free(&keySig->ks.mldsa.key); + keySig->keyId = ID_NONE; + ret = WS_CRYPTO_FAILED; + } + } + } + + WLOG(WS_LOG_DEBUG, + "Leaving PrepareUserAuthRequestMlDsaCert(), ret = %d", ret); + return ret; +} +#endif /* WOLFSSH_CERTS */ + +/* Sign the user auth request with the ML-DSA key from keySig. */ +static int BuildUserAuthRequestMlDsa(WOLFSSH* ssh, + byte* output, word32* idx, + const WS_UserAuthData* authData, + const byte* sigStart, word32 sigStartIdx, + WS_KeySignature* keySig) +{ + word32 begin; + int ret = WS_SUCCESS; + byte* sig; + word32 sigSz; + byte* checkData = NULL; + word32 checkDataSz = 0; + + WLOG(WS_LOG_DEBUG, "Entering BuildUserAuthRequestMlDsa()"); + if (ssh == NULL || output == NULL || idx == NULL || authData == NULL || + sigStart == NULL || keySig == NULL) { + ret = WS_BAD_ARGUMENT; + return ret; + } + + sigSz = (word32)keySig->sigSz; + + sig = (byte*)WMALLOC(sigSz, keySig->heap, DYNTYPE_BUFFER); + if (sig == NULL) + ret = WS_MEMORY_E; + + begin = *idx; + + if (ret == WS_SUCCESS) { + checkDataSz = LENGTH_SZ + ssh->sessionIdSz + (begin - sigStartIdx); + checkData = (byte*)WMALLOC(checkDataSz, keySig->heap, DYNTYPE_TEMP); + if (checkData == NULL) + ret = WS_MEMORY_E; + } + + if (ret == WS_SUCCESS) { + word32 i = 0; + + c32toa(ssh->sessionIdSz, checkData + i); + i += LENGTH_SZ; + WMEMCPY(checkData + i, ssh->sessionId, ssh->sessionIdSz); + i += ssh->sessionIdSz; + WMEMCPY(checkData + i, sigStart, begin - sigStartIdx); + } + + if (ret == WS_SUCCESS) { + WLOG(WS_LOG_INFO, "Signing with ML-DSA."); + ret = wc_MlDsaKey_SignCtx(&keySig->ks.mldsa.key, NULL, 0, + sig, &sigSz, checkData, checkDataSz, + ssh->rng); + if (ret != 0) { + WLOG(WS_LOG_DEBUG, "BUAR: Bad ML-DSA Sign"); + ret = WS_MLDSA_E; + } + } + + if (ret == WS_SUCCESS) { + const char* name = IdToName(keySig->keyId); + word32 nameSz = (word32)WSTRLEN(name); + + c32toa(LENGTH_SZ * 2 + nameSz + sigSz, output + begin); + begin += LENGTH_SZ; + + c32toa(nameSz, output + begin); + begin += LENGTH_SZ; + + WMEMCPY(output + begin, name, nameSz); + begin += nameSz; + + c32toa(sigSz, output + begin); + begin += LENGTH_SZ; + + WMEMCPY(output + begin, sig, sigSz); + begin += sigSz; + } + + if (ret == WS_SUCCESS) + *idx = begin; + + if (checkData != NULL) { + ForceZero(checkData, checkDataSz); + WFREE(checkData, keySig->heap, DYNTYPE_TEMP); + } + + if (sig != NULL) { + WFREE(sig, keySig->heap, DYNTYPE_BUFFER); + } + + WLOG(WS_LOG_DEBUG, "Leaving BuildUserAuthRequestMlDsa(), ret = %d", ret); + return ret; +} +#endif /* !WOLFSSH_NO_MLDSA */ + + #if !defined(WOLFSSH_NO_RSA) || !defined(WOLFSSH_NO_ECDSA) \ - || !defined(WOLFSSH_NO_ED25519) + || !defined(WOLFSSH_NO_ED25519) || !defined(WOLFSSH_NO_MLDSA) static int PrepareUserAuthRequestPublicKey(WOLFSSH* ssh, word32* payloadSz, WS_UserAuthData* authData, WS_KeySignature* keySig) { @@ -16046,6 +17245,22 @@ static int PrepareUserAuthRequestPublicKey(WOLFSSH* ssh, word32* payloadSz, payloadSz, authData, keySig); break; #endif + #ifndef WOLFSSH_NO_MLDSA + case ID_MLDSA44: + case ID_MLDSA65: + case ID_MLDSA87: + ret = PrepareUserAuthRequestMlDsa(ssh, + payloadSz, authData, keySig); + break; + #ifdef WOLFSSH_CERTS + case ID_X509V3_MLDSA44: + case ID_X509V3_MLDSA65: + case ID_X509V3_MLDSA87: + ret = PrepareUserAuthRequestMlDsaCert(ssh, + payloadSz, authData, keySig); + break; + #endif + #endif default: ret = WS_INVALID_ALGO_ID; } @@ -16169,6 +17384,44 @@ static int BuildUserAuthRequestPublicKey(WOLFSSH* ssh, authData, sigStart, sigStartIdx, keySig); break; #endif + #ifndef WOLFSSH_NO_MLDSA + case ID_MLDSA44: + case ID_MLDSA65: + case ID_MLDSA87: + c32toa(pk->publicKeyTypeSz, output + begin); + begin += LENGTH_SZ; + WMEMCPY(output + begin, + pk->publicKeyType, pk->publicKeyTypeSz); + begin += pk->publicKeyTypeSz; + c32toa(pk->publicKeySz, output + begin); + begin += LENGTH_SZ; + WMEMCPY(output + begin, pk->publicKey, pk->publicKeySz); + begin += pk->publicKeySz; + ret = BuildUserAuthRequestMlDsa(ssh, output, &begin, + authData, sigStart, sigStartIdx, keySig); + break; + #ifdef WOLFSSH_CERTS + case ID_X509V3_MLDSA44: + case ID_X509V3_MLDSA65: + case ID_X509V3_MLDSA87: + /* public key type name */ + c32toa(pk->publicKeyTypeSz, output + begin); + begin += LENGTH_SZ; + WMEMCPY(output + begin, + pk->publicKeyType, pk->publicKeyTypeSz); + begin += pk->publicKeyTypeSz; + + /* build RFC6187 public key to send */ + ret = BuildRFC6187Info(ssh, keySig->keyId, + pk->publicKey, pk->publicKeySz, NULL, 0, + output, &ssh->outputBuffer.bufferSz, &begin); + if (ret == WS_SUCCESS) { + ret = BuildUserAuthRequestMlDsa(ssh, output, &begin, + authData, sigStart, sigStartIdx, keySig); + } + break; + #endif + #endif default: ret = WS_INVALID_ALGO_ID; } @@ -16187,7 +17440,7 @@ static int BuildUserAuthRequestPublicKey(WOLFSSH* ssh, } -#endif /* !WOLFSSH_NO_RSA || !WOLFSSH_NO_ECDSA || !WOLFSSH_NO_ED25519 */ +#endif /* !WOLFSSH_NO_RSA/ECDSA/ED25519/MLDSA */ #ifdef WOLFSSH_KEYBOARD_INTERACTIVE int SendUserAuthKeyboardResponse(WOLFSSH* ssh) @@ -18641,4 +19894,61 @@ int wolfSSH_TestDoUserAuthRequestEd25519(WOLFSSH* ssh, #endif /* !WOLFSSH_NO_ED25519 */ +#ifndef WOLFSSH_NO_MLDSA + +WOLFSSH_API int wolfSSH_TestDoUserAuthRequestMlDsa(WOLFSSH* ssh, + WS_UserAuthData* authData, word32 pubKeyBlobSz) +{ + byte keyId; + int mlLevel; + byte isCert = 0; + + if (authData == NULL) + return WS_BAD_ARGUMENT; + + keyId = NameToId((const char*)authData->sf.publicKey.publicKeyType, + authData->sf.publicKey.publicKeyTypeSz); + + mlLevel = KeyIdToMlDsaLevel(keyId); + if (mlLevel < 0) + return WS_INVALID_ALGO_ID; + +#ifdef WOLFSSH_CERTS + isCert = (keyId == ID_X509V3_MLDSA44 || + keyId == ID_X509V3_MLDSA65 || + keyId == ID_X509V3_MLDSA87) ? 1 : 0; +#endif + + return DoUserAuthRequestMlDsa(ssh, &authData->sf.publicKey, authData, + (byte)mlLevel, isCert, pubKeyBlobSz); +} + +WOLFSSH_API int wolfSSH_TestPrepareUserAuthRequestMlDsa(WOLFSSH* ssh, + word32* payloadSz, const WS_UserAuthData* authData, + WS_KeySignature* keySig) +{ + return PrepareUserAuthRequestMlDsa(ssh, payloadSz, authData, keySig); +} + +#ifdef WOLFSSH_CERTS +WOLFSSH_API int wolfSSH_TestPrepareUserAuthRequestMlDsaCert(WOLFSSH* ssh, + word32* payloadSz, const WS_UserAuthData* authData, + WS_KeySignature* keySig) +{ + return PrepareUserAuthRequestMlDsaCert(ssh, payloadSz, authData, keySig); +} +#endif /* WOLFSSH_CERTS */ + +WOLFSSH_API int wolfSSH_TestBuildUserAuthRequestMlDsa(WOLFSSH* ssh, + byte* output, word32* idx, + const WS_UserAuthData* authData, + const byte* sigStart, word32 sigStartIdx, + WS_KeySignature* keySig) +{ + return BuildUserAuthRequestMlDsa(ssh, output, idx, authData, + sigStart, sigStartIdx, keySig); +} + +#endif /* !WOLFSSH_NO_MLDSA */ + #endif /* WOLFSSH_TEST_INTERNAL */ diff --git a/src/keygen.c b/src/keygen.c index 4baee3ace..98d338ebf 100644 --- a/src/keygen.c +++ b/src/keygen.c @@ -253,6 +253,82 @@ int wolfSSH_MakeEd25519Key(byte* out, word32 outSz, word32 size) #endif } +int wolfSSH_MakeMlDsaKey(byte* out, word32 outSz, word32 level) +{ +#if !defined(WOLFSSH_NO_MLDSA) + int ret = WS_SUCCESS; + WC_RNG rng = {0}; + byte wc_level; + + WLOG(WS_LOG_DEBUG, "Entering wolfSSH_MakeMlDsaKey()"); + + if (level == WOLFSSH_MLDSAKEY_44) wc_level = WC_ML_DSA_44; + else if (level == WOLFSSH_MLDSAKEY_65) wc_level = WC_ML_DSA_65; + else if (level == WOLFSSH_MLDSAKEY_87) wc_level = WC_ML_DSA_87; + else { + WLOG(WS_LOG_DEBUG, "Invalid ML-DSA key level requested"); + return WS_BAD_ARGUMENT; + } + + if (wc_InitRng(&rng) != 0) { + WLOG(WS_LOG_DEBUG, "Couldn't create RNG"); + ret = WS_CRYPTO_FAILED; + } + + if (ret == WS_SUCCESS) { + MlDsaKey key; + int keyInit = 0; + + if (wc_MlDsaKey_Init(&key, NULL, INVALID_DEVID) != 0) + ret = WS_CRYPTO_FAILED; + else { + keyInit = 1; + if (wc_MlDsaKey_SetParams(&key, wc_level) != 0) + ret = WS_CRYPTO_FAILED; + } + + if (ret == WS_SUCCESS) { + ret = wc_MlDsaKey_MakeKey(&key, &rng); + if (ret != 0) { + WLOG(WS_LOG_DEBUG, "ML-DSA key generation failed"); + ret = WS_CRYPTO_FAILED; + } + else + ret = WS_SUCCESS; + } + + if (ret == WS_SUCCESS) { + int keySz; + + keySz = wc_MlDsaKey_KeyToDer(&key, out, outSz); + if (keySz < 0) { + WLOG(WS_LOG_DEBUG, "ML-DSA key to DER failed"); + ret = WS_CRYPTO_FAILED; + } + else + ret = keySz; + } + + if (keyInit) { + wc_MlDsaKey_Free(&key); + } + + if (wc_FreeRng(&rng) != 0) { + WLOG(WS_LOG_DEBUG, "Couldn't free RNG"); + if (ret >= 0) + ret = WS_CRYPTO_FAILED; + } + } + + WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_MakeMlDsaKey(), ret = %d", ret); + return ret; +#else + WOLFSSH_UNUSED(out); + WOLFSSH_UNUSED(outSz); + WOLFSSH_UNUSED(level); + return WS_NOT_COMPILED; +#endif +} #else /* WOLFSSL_KEY_GEN */ #error "wolfSSH keygen requires that keygen is enabled in wolfSSL, use --enable-keygen or #define WOLFSSL_KEY_GEN." diff --git a/src/ssh.c b/src/ssh.c index 45761d808..b70e164d4 100644 --- a/src/ssh.c +++ b/src/ssh.c @@ -1712,6 +1712,9 @@ char* wolfSSH_GetUsername(WOLFSSH* ssh) #include #include #include +#ifndef WOLFSSH_NO_MLDSA + #include +#endif union wolfSSH_key { #ifndef WOLFSSH_NO_RSA @@ -1833,7 +1836,72 @@ static int DoAsn1Key(const byte* in, word32 inSz, byte** out, } if (ret > 0 && !isPrivate) { +#ifndef WOLFSSH_NO_MLDSA + if ( +#ifndef WOLFSSH_NO_MLDSA44 + ret == ID_MLDSA44 || +#endif +#ifndef WOLFSSH_NO_MLDSA65 + ret == ID_MLDSA65 || +#endif +#ifndef WOLFSSH_NO_MLDSA87 + ret == ID_MLDSA87 || +#endif + 0) { + byte* rawPub = NULL; + word32 rawPubSz = +#ifndef WOLFSSH_NO_MLDSA87 + WC_MLDSA_87_PUB_KEY_SIZE; +#elif !defined(WOLFSSH_NO_MLDSA65) + WC_MLDSA_65_PUB_KEY_SIZE; +#else + WC_MLDSA_44_PUB_KEY_SIZE; +#endif + const char* name = IdToName(ret); + word32 nameLen = (word32)WSTRLEN(name); + word32 localIdx = 0; + + *outType = (const byte*)name; + *outTypeSz = nameLen; + + rawPub = (byte*)WMALLOC(rawPubSz, heap, DYNTYPE_PUBKEY); + if (rawPub == NULL) { + ret = WS_MEMORY_E; + } + else { + int wcRet = wc_MlDsaKey_ExportPubRaw(&key->ks.mldsa.key, + rawPub, &rawPubSz); + if (wcRet != 0) { + ret = WS_CRYPTO_FAILED; + } + else { + *outSz = LENGTH_SZ + nameLen + LENGTH_SZ + rawPubSz; + newKey = (byte*)WMALLOC(*outSz, heap, DYNTYPE_PUBKEY); + if (newKey == NULL) { + ret = WS_MEMORY_E; + } + else { + c32toa(nameLen, &newKey[localIdx]); + localIdx += LENGTH_SZ; + WMEMCPY(&newKey[localIdx], name, nameLen); + localIdx += nameLen; + c32toa(rawPubSz, &newKey[localIdx]); + localIdx += LENGTH_SZ; + WMEMCPY(&newKey[localIdx], rawPub, rawPubSz); + *out = newKey; + ret = WS_SUCCESS; + } + } + WFREE(rawPub, heap, DYNTYPE_PUBKEY); + } + } + /* The else below intentionally straddles a preprocessor boundary: + * when WOLFSSH_NO_MLDSA is defined the entire if/else is removed and + * the RSA block becomes a standalone scope, which is harmless. */ + else +#endif /* WOLFSSH_NO_MLDSA */ #ifndef WOLFSSH_NO_RSA + { long e; byte n[RSA_MAX_SIZE]; /* TODO: Handle small stack */ word32 nSz = (word32)sizeof(n), eSz = (word32)sizeof(e); @@ -1882,9 +1950,12 @@ static int DoAsn1Key(const byte* in, word32 inSz, byte** out, *out = newKey; } + } #else + { WLOG(WS_LOG_DEBUG, "DoAsn1Key failed; WOLFSSH_NO_RSA disabled RSA"); ret = WS_UNIMPLEMENTED_E; + } #endif } else if (ret > 0 && isPrivate) { @@ -1892,23 +1963,22 @@ static int DoAsn1Key(const byte* in, word32 inSz, byte** out, newKey = (byte*)WMALLOC(inSz, heap, DYNTYPE_PRIVKEY); if (newKey == NULL) { ret = WS_MEMORY_E; - return ret; } } + else if (*outSz < inSz) { + WLOG(WS_LOG_DEBUG, "DER private key output size too small"); + ret = WS_BUFFER_E; + } else { - if (*outSz < inSz) { - WLOG(WS_LOG_DEBUG, "DER private key output size too small"); - ret = WS_BUFFER_E; - return ret; - } newKey = *out; } - - *out = newKey; - *outSz = inSz; - WMEMCPY(newKey, in, inSz); - *outType = (const byte*)IdToName(ret); - *outTypeSz = (word32)WSTRLEN((const char*)*outType); + if (ret > 0) { + *out = newKey; + *outSz = inSz; + WMEMCPY(newKey, in, inSz); + *outType = (const byte*)IdToName(ret); + *outTypeSz = (word32)WSTRLEN((const char*)*outType); + } } wolfSSH_KEY_clean(key); @@ -2169,7 +2239,11 @@ int wolfSSH_ReadKey_file(const char* name, || WSTRNSTR((const char*)in, "ecdsa-sha2-nistp", inSz) == (const char*)in || WSTRNSTR((const char*)in, - "ssh-ed25519", inSz) == (const char*)in) { + "ssh-ed25519", inSz) == (const char*)in + || WSTRNSTR((const char*)in, + "ssh-mldsa-", inSz) == (const char*)in + || WSTRNSTR((const char*)in, + "x509v3-ssh-mldsa-", inSz) == (const char*)in) { *isPrivate = 0; format = WOLFSSH_FORMAT_SSH; in[inSz] = 0; diff --git a/tests/kex.c b/tests/kex.c index 97a813a99..b08560cf7 100644 --- a/tests/kex.c +++ b/tests/kex.c @@ -380,6 +380,81 @@ static int wolfSSH_KexTest_Ed25519HostKey(void) } #endif /* WOLFSSH_NO_ED25519 */ + +#ifndef WOLFSSH_NO_MLDSA +static int wolfSSH_KexTest_MlDsaHostKey(const char* keyName) +{ + tcp_ready ready; + THREAD_TYPE serverThread; + func_args serverArgs; + func_args clientArgs; + char sA[NUMARGS][ARGLEN]; + char *serverArgv[NUMARGS] = + { sA[0], sA[1], sA[2], sA[3], sA[4], sA[5], sA[6], sA[7], sA[8], + sA[9], sA[10], sA[11] }; + char cA[NUMARGS][ARGLEN]; + char *clientArgv[NUMARGS] = + { cA[0], cA[1], cA[2], cA[3], cA[4], cA[5], cA[6], cA[7], cA[8], + cA[9], cA[10], cA[11] }; + int serverArgc = 0; + int clientArgc = 0; + + InitTcpReady(&ready); + + ADD_ARG(serverArgv, serverArgc, "echoserver"); + ADD_ARG(serverArgv, serverArgc, "-1"); + ADD_ARG(serverArgv, serverArgc, "-f"); + #if !defined(USE_WINDOWS_API) && !defined(WOLFSSH_ZEPHYR) + ADD_ARG(serverArgv, serverArgc, "-p"); + ADD_ARG(serverArgv, serverArgc, "-0"); + #endif + ADD_ARG(serverArgv, serverArgc, "-k"); + ADD_ARG(serverArgv, serverArgc, keyName); + + serverArgs.argc = serverArgc; + serverArgs.argv = serverArgv; + serverArgs.return_code = EXIT_SUCCESS; + serverArgs.signal = &ready; + serverArgs.user_auth = NULL; + ThreadStart(echoserver_test, &serverArgs, &serverThread); + WaitTcpReady(&ready); + + ADD_ARG(clientArgv, clientArgc, "client"); + ADD_ARG(clientArgv, clientArgc, "-u"); + ADD_ARG(clientArgv, clientArgc, "jill"); + #if !defined(USE_WINDOWS_API) && !defined(WOLFSSH_ZEPHYR) + ADD_ARG(clientArgv, clientArgc, "-p"); + ADD_ARG_INT(clientArgv, clientArgc, ready.port); + #endif + + clientArgs.argc = clientArgc; + clientArgs.argv = clientArgv; + clientArgs.return_code = EXIT_SUCCESS; + clientArgs.signal = &ready; + clientArgs.user_auth = tsClientUserAuth; + + client_test(&clientArgs); + +#ifdef WOLFSSH_ZEPHYR + k_sleep(Z_TIMEOUT_TICKS(100)); +#endif + ThreadJoin(serverThread); + + if (clientArgs.return_code == WS_SOCKET_ERROR_E) { + clientArgs.return_code = WS_SUCCESS; + } + if (serverArgs.return_code == WS_SOCKET_ERROR_E) { + serverArgs.return_code = WS_SUCCESS; + } + AssertIntEQ(WS_SUCCESS, clientArgs.return_code); + AssertIntEQ(WS_SUCCESS, serverArgs.return_code); + + FreeTcpReady(&ready); + + return EXIT_SUCCESS; +} +#endif /* WOLFSSH_NO_MLDSA */ + #endif /* KEXTEST_AVAILABLE */ int wolfSSH_KexTest(int argc, char** argv) @@ -431,6 +506,15 @@ int wolfSSH_KexTest(int argc, char** argv) #ifndef WOLFSSH_NO_ED25519 AssertIntEQ(wolfSSH_KexTest_Ed25519HostKey(), EXIT_SUCCESS); #endif +#ifndef WOLFSSH_NO_MLDSA44 + AssertIntEQ(wolfSSH_KexTest_MlDsaHostKey("ssh-mldsa-44"), EXIT_SUCCESS); +#endif +#ifndef WOLFSSH_NO_MLDSA65 + AssertIntEQ(wolfSSH_KexTest_MlDsaHostKey("ssh-mldsa-65"), EXIT_SUCCESS); +#endif +#ifndef WOLFSSH_NO_MLDSA87 + AssertIntEQ(wolfSSH_KexTest_MlDsaHostKey("ssh-mldsa-87"), EXIT_SUCCESS); +#endif AssertIntEQ(wolfSSH_Cleanup(), WS_SUCCESS); diff --git a/tests/unit.c b/tests/unit.c index d7e4d1675..28dfe7ec2 100644 --- a/tests/unit.c +++ b/tests/unit.c @@ -795,6 +795,71 @@ static int test_Ed25519KeyGen(void) } #endif +#ifndef WOLFSSH_NO_MLDSA +static int test_MlDsaKeyGen(void) +{ + static const struct { + word32 level; + word32 derSz; + const char* name; + } params[] = { + #ifndef WOLFSSH_NO_MLDSA44 + { WOLFSSH_MLDSAKEY_44, WC_MLDSA_44_BOTH_KEY_DER_SIZE, "44" }, + #endif + #ifndef WOLFSSH_NO_MLDSA65 + { WOLFSSH_MLDSAKEY_65, WC_MLDSA_65_BOTH_KEY_DER_SIZE, "65" }, + #endif + #ifndef WOLFSSH_NO_MLDSA87 + { WOLFSSH_MLDSAKEY_87, WC_MLDSA_87_BOTH_KEY_DER_SIZE, "87" }, + #endif + }; + word32 i; + int result = 0; + + for (i = 0; i < (word32)(sizeof(params) / sizeof(params[0])); i++) { + byte* der; + int sz; + + der = (byte*)WMALLOC(params[i].derSz, NULL, DYNTYPE_BUFFER); + if (der == NULL) { + printf("MlDsaKeyGen: alloc failed for level %s\n", params[i].name); + result = -106; + break; + } + + sz = wolfSSH_MakeMlDsaKey(der, params[i].derSz, params[i].level); + if (sz < 0) { + printf("MlDsaKeyGen: MakeMlDsaKey level %s failed (%d)\n", + params[i].name, sz); + WFREE(der, NULL, DYNTYPE_BUFFER); + result = -106; + break; + } + + sz = wolfSSH_MakeMlDsaKey(der, params[i].derSz - 1, params[i].level); + if (sz != WS_CRYPTO_FAILED) { + printf("MlDsaKeyGen: undersized buffer wrong result %d, level %s\n", + sz, params[i].name); + WFREE(der, NULL, DYNTYPE_BUFFER); + result = -107; + break; + } + + WFREE(der, NULL, DYNTYPE_BUFFER); + } + + if (result == 0) { + int sz = wolfSSH_MakeMlDsaKey(NULL, 0, 9999); + if (sz != WS_BAD_ARGUMENT) { + printf("MlDsaKeyGen: invalid level wrong result %d\n", sz); + result = -108; + } + } + + return result; +} +#endif + #endif @@ -2315,8 +2380,8 @@ static int test_SendUserAuthFailure_emptyMethods(void) capMsgId = CaptureMsgId(s_authSvcCapture, s_authSvcCaptureSz); if (capMsgId != MSGID_USERAUTH_FAILURE) { - printf("SendUserAuthFailure[%s]: msgId=%d expected USERAUTH_FAILURE\n", - cases[i].label, capMsgId); + printf("SendUserAuthFailure[%s]: msgId=%d expected" + " USERAUTH_FAILURE\n", cases[i].label, capMsgId); result = -620 - i; break; } @@ -2559,6 +2624,341 @@ static const byte unitTestEd25519PrivKey[] = { }; #endif /* !WOLFSSH_NO_ED25519 */ +#if !defined(WOLFSSH_NO_MLDSA) +/* keys/server-key-mldsa44.der - MlDsa44 OneAsymmetricKey */ +static const byte unitTestMlDsaPrivKey[] = { + 0x30, 0x82, 0x0a, 0x3e, 0x02, 0x01, 0x00, 0x30, + 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, + 0x03, 0x04, 0x03, 0x11, 0x04, 0x82, 0x0a, 0x2a, + 0x30, 0x82, 0x0a, 0x26, 0x04, 0x20, 0x07, 0x99, + 0x36, 0x30, 0xd1, 0xef, 0x77, 0x3e, 0x75, 0x79, + 0xbc, 0x3f, 0xb5, 0x78, 0xfa, 0x10, 0x26, 0x77, + 0x79, 0x27, 0x19, 0x34, 0xf7, 0x68, 0x83, 0xce, + 0x08, 0xb6, 0xbb, 0xe9, 0x06, 0x18, 0x04, 0x82, + 0x0a, 0x00, 0x9d, 0x7b, 0x66, 0x85, 0x9a, 0xb3, + 0xcb, 0xdf, 0x19, 0xc5, 0xaa, 0xe2, 0xac, 0x2a, + 0x79, 0xaf, 0xf1, 0xfd, 0xe2, 0xb8, 0x8d, 0x91, + 0xda, 0xf5, 0x8e, 0x86, 0xb4, 0x91, 0x3c, 0x15, + 0x2a, 0x12, 0xc6, 0x98, 0x49, 0x63, 0xed, 0x50, + 0xcb, 0x79, 0x0d, 0x58, 0x43, 0xf2, 0x00, 0x8e, + 0x35, 0x5f, 0x25, 0x2f, 0x3c, 0xcb, 0xab, 0xd9, + 0x04, 0x85, 0x20, 0x1d, 0x5e, 0x55, 0x88, 0x94, + 0x64, 0x37, 0x4f, 0xd3, 0x64, 0x89, 0xbe, 0xe2, + 0xcb, 0xdc, 0x96, 0xcc, 0x62, 0x99, 0x56, 0xde, + 0x26, 0x8c, 0xde, 0x30, 0x18, 0x66, 0xf9, 0xd9, + 0x1b, 0xf1, 0xcf, 0x32, 0xc5, 0x78, 0x48, 0x02, + 0x04, 0xb2, 0x3b, 0x16, 0x3d, 0xe9, 0xa8, 0x6c, + 0x81, 0x06, 0x9f, 0xf4, 0x69, 0x77, 0x7e, 0x86, + 0x34, 0xa5, 0xdd, 0xb1, 0x49, 0x20, 0xe0, 0x2f, + 0x17, 0x2b, 0xdc, 0x62, 0xf9, 0x93, 0x5e, 0x17, + 0x51, 0x38, 0x4a, 0x82, 0x08, 0x48, 0x06, 0x24, + 0x08, 0x48, 0x60, 0x92, 0xb4, 0x51, 0x13, 0x05, + 0x28, 0x1a, 0xb8, 0x8d, 0x89, 0xb4, 0x28, 0x90, + 0xa8, 0x64, 0x0c, 0x81, 0x44, 0xe3, 0xc6, 0x89, + 0x84, 0x16, 0x0e, 0x44, 0x02, 0x40, 0x0b, 0x97, + 0x2d, 0xd9, 0x48, 0x92, 0x01, 0x15, 0x64, 0x42, + 0x38, 0x68, 0x1a, 0x27, 0x8e, 0x13, 0x13, 0x52, + 0x00, 0x31, 0x02, 0xc4, 0x02, 0x26, 0x14, 0x42, + 0x4d, 0x10, 0x28, 0x90, 0x20, 0x31, 0x32, 0x01, + 0xc1, 0x60, 0x0b, 0x01, 0x6c, 0x80, 0x00, 0x6c, + 0x63, 0x26, 0x50, 0x02, 0x80, 0x45, 0x4a, 0xa6, + 0x40, 0x1c, 0xb2, 0x25, 0x08, 0x21, 0x62, 0x24, + 0x49, 0x4e, 0x21, 0x87, 0x48, 0x1b, 0x48, 0x02, + 0x1c, 0x86, 0x64, 0x04, 0xa2, 0x90, 0xda, 0xb0, + 0x89, 0x14, 0x19, 0x4e, 0x49, 0x26, 0x05, 0x18, + 0xc9, 0x00, 0x9a, 0xa4, 0x85, 0xdc, 0xa4, 0x48, + 0x41, 0x22, 0x81, 0x02, 0x35, 0x70, 0x63, 0x26, + 0x8a, 0xd4, 0x10, 0x61, 0x20, 0x44, 0x88, 0xa3, + 0xa2, 0x60, 0x02, 0xa2, 0x81, 0x19, 0xb4, 0x70, + 0x9c, 0x16, 0x84, 0x81, 0x02, 0x41, 0x22, 0xb5, + 0x91, 0xd2, 0x08, 0x51, 0x12, 0x34, 0x41, 0xd3, + 0xb8, 0x11, 0x08, 0x21, 0x32, 0x42, 0x44, 0x65, + 0x00, 0x48, 0x69, 0x92, 0x46, 0x12, 0x22, 0x94, + 0x61, 0xc2, 0xb6, 0x70, 0xe2, 0x98, 0x8d, 0xd3, + 0x26, 0x09, 0x84, 0x22, 0x0a, 0x09, 0xb9, 0x0d, + 0x4c, 0xb0, 0x64, 0x03, 0x24, 0x2e, 0x01, 0x96, + 0x84, 0x99, 0x04, 0x11, 0x9b, 0xc8, 0x11, 0xa3, + 0x12, 0x90, 0x0c, 0x16, 0x0a, 0x8c, 0x20, 0x2c, + 0x94, 0x08, 0x46, 0x04, 0xb6, 0x0d, 0x60, 0xb0, + 0x88, 0xdc, 0x04, 0x22, 0x9a, 0x46, 0x42, 0x21, + 0x42, 0x4d, 0x49, 0x46, 0x29, 0x24, 0x27, 0x62, + 0x13, 0x25, 0x05, 0x0a, 0x84, 0x05, 0x4a, 0x10, + 0x46, 0xa4, 0x06, 0x80, 0x1c, 0x34, 0x82, 0x1a, + 0xa5, 0x4c, 0x19, 0x96, 0x89, 0x24, 0x26, 0x61, + 0x10, 0x46, 0x8c, 0xc1, 0x18, 0x48, 0x1c, 0x19, + 0x6d, 0x04, 0x98, 0x8d, 0x8b, 0x46, 0x02, 0x02, + 0x90, 0x0d, 0x5c, 0x12, 0x65, 0x4a, 0x14, 0x72, + 0x02, 0xc1, 0x65, 0x1b, 0x21, 0x4d, 0x5c, 0x38, + 0x6d, 0x03, 0x41, 0x8c, 0xa4, 0x26, 0x0e, 0x89, + 0xa2, 0x80, 0x4a, 0x40, 0x26, 0x1c, 0x99, 0x69, + 0x81, 0x12, 0x30, 0xc3, 0x40, 0x21, 0x22, 0x46, + 0x61, 0x22, 0x43, 0x72, 0x02, 0x47, 0x26, 0x10, + 0xa1, 0x09, 0x81, 0x22, 0x06, 0xc3, 0x22, 0x2e, + 0x64, 0x32, 0x12, 0x01, 0xb2, 0x01, 0x59, 0x96, + 0x45, 0x04, 0x35, 0x08, 0xc3, 0xb6, 0x8d, 0x14, + 0x30, 0x6c, 0x0c, 0x95, 0x0c, 0xa3, 0x48, 0x80, + 0x63, 0x40, 0x50, 0x00, 0x20, 0x26, 0xdc, 0x20, + 0x24, 0x01, 0x46, 0x71, 0xe1, 0x40, 0x51, 0xc2, + 0x12, 0x0d, 0xc4, 0x10, 0x40, 0x0c, 0x43, 0x24, + 0x02, 0x33, 0x26, 0x02, 0xc8, 0x04, 0x1a, 0x23, + 0x08, 0xe0, 0x16, 0x8a, 0x9a, 0xb0, 0x2d, 0x13, + 0x40, 0x08, 0x43, 0x22, 0x32, 0x44, 0x38, 0x6d, + 0xd1, 0x44, 0x90, 0xd4, 0x86, 0x09, 0x48, 0x82, + 0x00, 0xe2, 0x98, 0x68, 0x12, 0xb1, 0x91, 0x09, + 0x90, 0x6d, 0xc9, 0x30, 0x85, 0xc0, 0x84, 0x80, + 0x9c, 0xc6, 0x4c, 0xe1, 0x10, 0x91, 0xd8, 0x96, + 0x30, 0xda, 0xb6, 0x68, 0xc8, 0xc6, 0x65, 0xe3, + 0x06, 0x50, 0x5c, 0x14, 0x11, 0x02, 0x44, 0x49, + 0x9b, 0xa2, 0x68, 0x5a, 0x32, 0x90, 0x52, 0x80, + 0x45, 0x00, 0x26, 0x30, 0x02, 0x46, 0x4c, 0xd4, + 0xc8, 0x49, 0x9c, 0xb8, 0x90, 0xc4, 0x20, 0x8a, + 0x0c, 0x15, 0x0d, 0x09, 0x37, 0x8e, 0x12, 0x46, + 0x45, 0x08, 0x23, 0x25, 0x21, 0xa1, 0x11, 0x80, + 0x90, 0x65, 0x93, 0x46, 0x72, 0xe1, 0x28, 0x6d, + 0x44, 0x96, 0x51, 0x81, 0x12, 0x80, 0x04, 0x46, + 0x4a, 0x8b, 0x10, 0x6d, 0x13, 0x46, 0x51, 0x53, + 0x36, 0x4a, 0xe1, 0xa6, 0x45, 0xe0, 0x22, 0x01, + 0x48, 0xa6, 0x00, 0x12, 0x91, 0x70, 0x23, 0xb3, + 0x40, 0x02, 0xa8, 0x65, 0xdb, 0x42, 0x06, 0xe0, + 0x06, 0x6a, 0xe3, 0x96, 0x61, 0x48, 0x94, 0x65, + 0x43, 0x22, 0x69, 0xdc, 0x22, 0x85, 0x9b, 0x84, + 0x89, 0x59, 0x18, 0x64, 0x43, 0xb0, 0x20, 0x8b, + 0xa8, 0x29, 0x58, 0x30, 0x24, 0x0a, 0x17, 0x8e, + 0x11, 0x41, 0x2d, 0x24, 0xb8, 0x25, 0x89, 0x14, + 0x20, 0x14, 0x41, 0x91, 0x23, 0xa2, 0x24, 0x21, + 0x17, 0x21, 0x11, 0xb1, 0x04, 0x03, 0x84, 0x4c, + 0x0b, 0x23, 0x62, 0x50, 0x86, 0x65, 0x54, 0x22, + 0x42, 0x42, 0x82, 0x80, 0x08, 0xa5, 0x51, 0xd9, + 0x06, 0x31, 0x23, 0x00, 0x91, 0x10, 0xa9, 0x4d, + 0xe2, 0x04, 0x66, 0x44, 0x36, 0x30, 0x8b, 0xb2, + 0x49, 0x98, 0xc6, 0x70, 0x19, 0xa4, 0x8c, 0x5a, + 0xa6, 0x0d, 0x63, 0x00, 0x2e, 0x9b, 0x94, 0x70, + 0x8a, 0x20, 0x2d, 0x62, 0x26, 0x2c, 0x42, 0x26, + 0x6c, 0x12, 0x13, 0x2e, 0xdb, 0x18, 0x08, 0x11, + 0x21, 0x2e, 0x8a, 0xb0, 0x71, 0x01, 0xc1, 0x2c, + 0xe3, 0x44, 0x62, 0xe3, 0x94, 0x28, 0x0b, 0x38, + 0x70, 0x40, 0x14, 0x65, 0x62, 0xc0, 0x41, 0xa1, + 0x06, 0x21, 0x82, 0xa2, 0x44, 0x94, 0xb0, 0x2c, + 0x21, 0x10, 0x8d, 0x14, 0x96, 0x71, 0x12, 0x14, + 0x05, 0x11, 0x27, 0x04, 0x12, 0x97, 0x65, 0xc4, + 0x02, 0x6e, 0x58, 0xc4, 0x6c, 0xc0, 0x10, 0x70, + 0x84, 0xc0, 0x45, 0x99, 0x12, 0x52, 0x80, 0xa8, + 0x88, 0xd3, 0xb8, 0x28, 0x01, 0x17, 0x70, 0x03, + 0xc3, 0x10, 0x88, 0x18, 0x0a, 0x53, 0x92, 0x40, + 0x53, 0xc6, 0x20, 0x93, 0xc0, 0x81, 0xa4, 0x90, + 0x90, 0x93, 0x92, 0x05, 0x22, 0x88, 0x08, 0xc8, + 0x00, 0x64, 0xf0, 0x19, 0xcf, 0xdf, 0x37, 0x19, + 0x32, 0xc9, 0xaf, 0x0a, 0x3c, 0x4a, 0x8f, 0x9c, + 0xb3, 0xb4, 0x4a, 0x29, 0x5c, 0x6d, 0xd2, 0x81, + 0x10, 0x3f, 0x9f, 0x4d, 0x23, 0x18, 0x65, 0xee, + 0x03, 0xa7, 0xeb, 0x14, 0x99, 0xc0, 0xab, 0x6c, + 0x2e, 0xad, 0x31, 0xa0, 0x15, 0x7f, 0xfd, 0x12, + 0xc3, 0x0b, 0x86, 0x8d, 0x1d, 0x0f, 0x19, 0x8e, + 0x2c, 0xdd, 0xc1, 0xce, 0xf2, 0x75, 0xc2, 0x3f, + 0xff, 0xc3, 0xbc, 0x7d, 0x5c, 0x40, 0x50, 0x81, + 0xd5, 0x92, 0xc9, 0xdc, 0x89, 0x56, 0x00, 0x04, + 0x64, 0x66, 0x27, 0xa9, 0xc0, 0x43, 0xcd, 0x5d, + 0xd6, 0xe6, 0xc7, 0x84, 0xa8, 0xf0, 0x02, 0xda, + 0xa3, 0xf2, 0xd7, 0x27, 0xac, 0x52, 0x30, 0xb3, + 0x95, 0x53, 0x34, 0x31, 0x1f, 0x06, 0xf2, 0x74, + 0xba, 0x58, 0x52, 0xcf, 0xb9, 0x0b, 0xd1, 0x39, + 0x3e, 0x60, 0xfe, 0xd9, 0x55, 0x72, 0xfb, 0xd9, + 0x5c, 0x2d, 0x9e, 0x5f, 0x5d, 0x95, 0xe3, 0xf8, + 0x25, 0x6d, 0x14, 0x70, 0x24, 0xf8, 0x15, 0x04, + 0x24, 0x14, 0x15, 0xab, 0xa5, 0x33, 0xc9, 0xe0, + 0xfd, 0x9c, 0xb3, 0x3d, 0x57, 0xef, 0xf4, 0xe2, + 0x87, 0x21, 0x9b, 0xd4, 0x27, 0x3e, 0x6e, 0x7b, + 0x23, 0x9e, 0x56, 0x7c, 0x67, 0xd2, 0x39, 0xea, + 0x52, 0xb5, 0xbd, 0x6d, 0xda, 0x00, 0xc7, 0x1e, + 0x0a, 0xee, 0x6d, 0x16, 0xfb, 0x9a, 0x34, 0xae, + 0x8b, 0x85, 0x7b, 0x69, 0x9a, 0x98, 0xd6, 0x15, + 0x26, 0x19, 0x4f, 0x09, 0xbd, 0xe0, 0x06, 0x79, + 0x93, 0x2c, 0x9e, 0xaa, 0x87, 0x3c, 0xc6, 0xab, + 0xca, 0x07, 0x98, 0xa9, 0xb4, 0x63, 0x90, 0x78, + 0x13, 0x28, 0xdc, 0x62, 0xf3, 0x04, 0x04, 0xd5, + 0x55, 0x8a, 0x91, 0xfb, 0x8b, 0xdc, 0x1d, 0x6a, + 0x53, 0x16, 0xde, 0x19, 0x88, 0xe7, 0x96, 0xfc, + 0xb1, 0xb5, 0x11, 0xe7, 0x91, 0x4e, 0x62, 0xc6, + 0xa4, 0xb8, 0xab, 0x08, 0x7f, 0x75, 0x06, 0x45, + 0x0b, 0x54, 0x63, 0x78, 0x7d, 0x0a, 0x84, 0x64, + 0x96, 0xa3, 0x9d, 0x44, 0x73, 0xf6, 0x16, 0x74, + 0x46, 0x35, 0x10, 0xa2, 0x9c, 0xbe, 0x5b, 0xc0, + 0xe1, 0x5e, 0xc4, 0xa8, 0x24, 0xab, 0xe1, 0xc2, + 0x59, 0x52, 0x16, 0xd8, 0xc9, 0xb6, 0x3e, 0x58, + 0xad, 0xfb, 0xc8, 0x36, 0x65, 0x7e, 0xf1, 0x8e, + 0x4f, 0x91, 0xc8, 0xe2, 0xf3, 0xa7, 0xd3, 0x28, + 0xab, 0x62, 0x21, 0x96, 0x96, 0x31, 0xef, 0xa1, + 0xaf, 0xe9, 0x2e, 0x36, 0xfe, 0x09, 0xeb, 0xf1, + 0x8d, 0xfa, 0xfb, 0x58, 0x39, 0xa3, 0xce, 0x45, + 0xe4, 0x1f, 0xdd, 0x8c, 0x24, 0xa9, 0xd7, 0x33, + 0x80, 0xf5, 0xbb, 0x05, 0x11, 0x52, 0xcb, 0xbc, + 0xb3, 0x09, 0x14, 0x2e, 0x0a, 0xdd, 0x44, 0xe4, + 0x2f, 0x85, 0x84, 0x4e, 0x09, 0xda, 0x0b, 0x20, + 0x78, 0x61, 0x2a, 0xb9, 0x67, 0x9c, 0x84, 0xe0, + 0xeb, 0xbb, 0x95, 0xd0, 0x31, 0x5d, 0x83, 0x91, + 0x15, 0xbf, 0x27, 0xf5, 0x1e, 0x25, 0xc9, 0xc5, + 0x48, 0xa8, 0xaa, 0x8c, 0xfc, 0xea, 0x60, 0x7a, + 0xcd, 0x97, 0x92, 0xab, 0x07, 0xc7, 0x9e, 0x0b, + 0x54, 0xbc, 0x41, 0xdc, 0x7f, 0xf7, 0x89, 0x72, + 0x12, 0xee, 0x85, 0x18, 0x86, 0x1b, 0xe0, 0x44, + 0xe1, 0x4f, 0x7b, 0x75, 0x3d, 0x27, 0xf7, 0x82, + 0xee, 0x38, 0xf7, 0x61, 0xe3, 0xc9, 0xa5, 0xdb, + 0x59, 0xff, 0x20, 0x0d, 0x7c, 0xb8, 0xd2, 0x2c, + 0xec, 0x88, 0x5d, 0xc4, 0x03, 0x08, 0x67, 0xb4, + 0x72, 0x3b, 0x5c, 0xc6, 0x16, 0xab, 0x1a, 0x6b, + 0x72, 0x99, 0x87, 0x80, 0xa7, 0x35, 0x5a, 0xb9, + 0x91, 0x4e, 0x5c, 0x7a, 0xc6, 0x94, 0x18, 0xd2, + 0xe5, 0x97, 0x7c, 0xd5, 0x91, 0x5d, 0x57, 0x56, + 0xe9, 0xff, 0x5a, 0x64, 0xf9, 0xc8, 0xff, 0x2a, + 0x5a, 0xba, 0xce, 0x0d, 0xcf, 0x67, 0x09, 0xb1, + 0x2d, 0x63, 0xc5, 0x72, 0x78, 0xc7, 0x4f, 0xc1, + 0xc0, 0x23, 0x42, 0xaf, 0xf2, 0xb8, 0x2f, 0x79, + 0xb7, 0xf7, 0x5d, 0xa5, 0xba, 0xd5, 0x0f, 0xa8, + 0x9b, 0xf2, 0xaf, 0x5d, 0x72, 0x92, 0x86, 0xce, + 0x10, 0x52, 0xd3, 0xdd, 0x15, 0x15, 0x65, 0xa8, + 0x38, 0xc2, 0x98, 0x27, 0x47, 0x4e, 0xb1, 0xde, + 0x05, 0x8b, 0xd9, 0x36, 0xd7, 0x0f, 0xf5, 0x33, + 0x6e, 0x4c, 0x9c, 0x49, 0x7d, 0x8e, 0x07, 0x79, + 0x77, 0x14, 0x8a, 0xea, 0x3b, 0x86, 0xc4, 0xaf, + 0xf9, 0x4c, 0x8f, 0x43, 0x26, 0xbf, 0xa4, 0x68, + 0xf4, 0xb3, 0xe7, 0xd2, 0x03, 0xc4, 0x85, 0x1c, + 0xd5, 0x0a, 0x18, 0x55, 0x51, 0xfe, 0xb1, 0x5b, + 0x8e, 0x79, 0xed, 0x07, 0x87, 0x7d, 0xba, 0xd4, + 0x09, 0x98, 0x93, 0xcb, 0xa9, 0x4f, 0x31, 0xce, + 0xe2, 0xab, 0x3a, 0xf2, 0x6d, 0x3a, 0xeb, 0x4f, + 0x2c, 0x1a, 0x6b, 0xf2, 0xff, 0x81, 0xfa, 0xf4, + 0x34, 0xbe, 0xb5, 0x4e, 0x1a, 0xea, 0xf2, 0x10, + 0x7b, 0x3e, 0x96, 0xcf, 0x67, 0x37, 0xd8, 0xae, + 0xf0, 0x3d, 0x03, 0xa8, 0xe6, 0x93, 0x1d, 0x59, + 0xbc, 0x1a, 0x06, 0xb4, 0x1c, 0x0d, 0x68, 0xf2, + 0xbe, 0x27, 0x58, 0x1a, 0x66, 0x92, 0xca, 0x37, + 0x63, 0x67, 0x2a, 0x59, 0x62, 0xbd, 0x40, 0xd5, + 0xe9, 0xd9, 0x4a, 0x49, 0x69, 0x8c, 0x4c, 0xf4, + 0x65, 0x85, 0x34, 0xcc, 0x37, 0xd0, 0x5e, 0x3e, + 0x65, 0x8a, 0x73, 0x6b, 0x32, 0xd2, 0xfa, 0x6c, + 0x54, 0x94, 0xb7, 0x20, 0x75, 0x6e, 0x4a, 0xc9, + 0xf8, 0x72, 0xc8, 0xdc, 0xda, 0x09, 0xca, 0xe3, + 0x94, 0x9b, 0xf8, 0xeb, 0xe8, 0x32, 0xbe, 0xbb, + 0x41, 0x68, 0xb6, 0x01, 0x8d, 0xe9, 0x9f, 0xd3, + 0x7f, 0xfd, 0x91, 0xe2, 0x2b, 0xc0, 0x4e, 0xf9, + 0x42, 0x5b, 0xa2, 0xec, 0xc8, 0x35, 0x0f, 0x36, + 0xd9, 0xd1, 0x88, 0x65, 0xa2, 0x2a, 0xea, 0x50, + 0x99, 0x19, 0x50, 0x31, 0x24, 0x10, 0x7b, 0x56, + 0x67, 0xa4, 0xa5, 0xca, 0xe3, 0xa5, 0xc9, 0x77, + 0xb4, 0xd1, 0x8a, 0xdc, 0xa4, 0xff, 0xca, 0xee, + 0xd8, 0x58, 0x3e, 0x6d, 0xa5, 0xd4, 0xb3, 0x39, + 0x15, 0xa2, 0xcb, 0x02, 0x9c, 0xfe, 0x93, 0x66, + 0x18, 0xd8, 0xd2, 0xce, 0xb2, 0x8d, 0x4d, 0x28, + 0x62, 0xc4, 0x7b, 0x39, 0xe2, 0x2a, 0x02, 0x6e, + 0x38, 0x59, 0xcc, 0x35, 0xda, 0x99, 0xb2, 0xc1, + 0x93, 0x24, 0xb6, 0x63, 0xa8, 0xfe, 0x37, 0x91, + 0x32, 0x78, 0x11, 0xf9, 0x95, 0x72, 0x2d, 0xd1, + 0x51, 0x40, 0x13, 0x90, 0xfa, 0xa7, 0x3d, 0x5d, + 0xfa, 0xce, 0xc1, 0x3d, 0xd4, 0xab, 0xb7, 0x4b, + 0x8c, 0xd1, 0xd9, 0x45, 0xc6, 0x7e, 0x0c, 0xc6, + 0xbc, 0xdd, 0x11, 0xfa, 0x52, 0x83, 0x59, 0x2d, + 0xa7, 0xa8, 0xae, 0x3f, 0xb8, 0x58, 0xa2, 0x84, + 0x14, 0x05, 0x77, 0xf9, 0xb9, 0xfe, 0x05, 0xd1, + 0x4a, 0xf9, 0xe8, 0x7d, 0x1e, 0xb8, 0xb4, 0x39, + 0xfc, 0x80, 0x1d, 0xb2, 0x38, 0x8d, 0xc4, 0x63, + 0xc0, 0xe9, 0x15, 0x5f, 0xfe, 0xf6, 0x81, 0xb2, + 0x3d, 0xe0, 0x13, 0xec, 0xd0, 0x75, 0x1d, 0x03, + 0xb8, 0x6b, 0xdc, 0x13, 0xb6, 0x09, 0xb1, 0x82, + 0xe4, 0x02, 0x12, 0x18, 0xcb, 0x1e, 0x2d, 0xce, + 0xee, 0xf2, 0xc4, 0xa4, 0x9a, 0x1b, 0xa6, 0x51, + 0xc3, 0xfd, 0x73, 0xc1, 0xc5, 0x85, 0xe7, 0xbb, + 0x44, 0xe7, 0x61, 0x9e, 0x00, 0x03, 0x0a, 0x18, + 0xe4, 0x62, 0x86, 0x45, 0xd8, 0xfa, 0x5b, 0x71, + 0x46, 0x86, 0x0e, 0x3a, 0x89, 0x6d, 0xb2, 0xd7, + 0x0e, 0xd7, 0x65, 0x3e, 0xcc, 0x16, 0xe5, 0x9c, + 0x2d, 0xf2, 0x0e, 0x44, 0x64, 0x14, 0xfd, 0xa9, + 0x2f, 0xb6, 0xe8, 0x78, 0xa2, 0x54, 0xa3, 0x45, + 0x5e, 0xb0, 0x14, 0x02, 0xf7, 0xa1, 0xe0, 0x16, + 0x58, 0xd9, 0xc3, 0x58, 0x5a, 0xe6, 0x72, 0xa7, + 0x0a, 0x9f, 0x33, 0xf8, 0xd5, 0x18, 0x54, 0x1e, + 0x80, 0x54, 0x1e, 0x51, 0x69, 0x53, 0x60, 0x57, + 0xf0, 0xf9, 0xc6, 0x97, 0x4b, 0x5b, 0x98, 0xe6, + 0x1a, 0xc1, 0xb4, 0x61, 0xed, 0x3d, 0xc7, 0xe8, + 0x14, 0xd6, 0x92, 0x49, 0x7c, 0x46, 0x1e, 0x3a, + 0x20, 0xb6, 0x20, 0xb3, 0x25, 0xe0, 0xf8, 0x39, + 0xea, 0xc9, 0x7d, 0xcb, 0x38, 0x98, 0x03, 0x95, + 0x9a, 0x99, 0x69, 0xae, 0xd4, 0x0c, 0x1e, 0x50, + 0x34, 0x6d, 0x85, 0x6a, 0x33, 0x2f, 0x8c, 0x03, + 0x6a, 0xa4, 0x1a, 0xfd, 0xac, 0x8a, 0x34, 0x08, + 0xe9, 0x88, 0xb6, 0xa0, 0xb9, 0x96, 0xa7, 0x41, + 0x37, 0x7f, 0xe7, 0xc2, 0xd1, 0xaf, 0xe5, 0x60, + 0x68, 0x06, 0xfe, 0x70, 0x81, 0x80, 0xb4, 0xfe, + 0xf7, 0x6d, 0x88, 0xec, 0xc6, 0x90, 0x38, 0x04, + 0x34, 0x24, 0x1b, 0xb8, 0x57, 0x86, 0x40, 0xd2, + 0x19, 0xec, 0xa1, 0x36, 0x11, 0xff, 0x22, 0x34, + 0x8c, 0x31, 0x3f, 0x63, 0xa1, 0x4f, 0xce, 0x67, + 0x73, 0x5c, 0x78, 0x5d, 0x85, 0xc8, 0xd8, 0x40, + 0x5a, 0x40, 0xdf, 0x88, 0x3d, 0xf3, 0x6d, 0xf4, + 0x9b, 0xb6, 0x29, 0xbf, 0x07, 0xdd, 0xd9, 0x51, + 0x27, 0xa6, 0x97, 0xb2, 0x6f, 0x61, 0xef, 0x1c, + 0xa9, 0x01, 0x52, 0x2b, 0xd0, 0x12, 0xe4, 0x40, + 0xa6, 0xea, 0x25, 0x80, 0xba, 0x80, 0x61, 0x87, + 0xa5, 0x8d, 0x7e, 0x71, 0x09, 0x68, 0xfb, 0xc6, + 0x08, 0x2f, 0x98, 0x2c, 0xe7, 0xab, 0x30, 0xb4, + 0xde, 0x39, 0xb6, 0x71, 0xff, 0x32, 0x90, 0x61, + 0x65, 0xf9, 0xc4, 0x16, 0x7e, 0xda, 0xc4, 0x05, + 0x77, 0x0b, 0xf1, 0xf9, 0xe0, 0xc0, 0x7d, 0x14, + 0x57, 0x6f, 0x48, 0xba, 0xea, 0xe0, 0xc5, 0x93, + 0x60, 0x14, 0xe3, 0xf8, 0x6a, 0x67, 0xb2, 0xdc, + 0x56, 0xe8, 0x37, 0x7f, 0x59, 0x63, 0x5c, 0x77, + 0xd2, 0xe3, 0xa7, 0x73, 0x53, 0x9c, 0x8d, 0xf4, + 0xac, 0xe9, 0x07, 0x8a, 0x1c, 0xbe, 0xa7, 0x4d, + 0x1f, 0x60, 0x36, 0x9f, 0x33, 0x9d, 0xf4, 0x2d, + 0x4f, 0x61, 0xdd, 0x33, 0x40, 0x1f, 0x6e, 0x10, + 0x9f, 0xf7, 0x1f, 0xf0, 0x95, 0x89, 0x62, 0x8c, + 0x04, 0xf7, 0x64, 0xe0, 0x66, 0xd0, 0x4b, 0x4a, + 0x79, 0x96, 0x70, 0x56, 0xdb, 0x07, 0xda, 0xaf, + 0x00, 0xd1, 0x54, 0xec, 0x07, 0xac, 0x09, 0x25, + 0x91, 0x46, 0xd1, 0x4f, 0xbf, 0x50, 0xe9, 0xe6, + 0x54, 0x73, 0x91, 0xc7, 0xfb, 0x67, 0x33, 0xeb, + 0x01, 0x1b, 0xdc, 0x45, 0x5a, 0xdc, 0xa3, 0x96, + 0x35, 0x6c, 0x71, 0x9b, 0xa6, 0xe7, 0x2b, 0x65, + 0x95, 0x2d, 0xae, 0x81, 0x0a, 0x28, 0x31, 0xf0, + 0x2a, 0x9e, 0x01, 0xe2, 0x83, 0x2f, 0xe0, 0xa4, + 0x65, 0xca, 0x92, 0x4a, 0x0f, 0x32, 0x45, 0xb5, + 0xe6, 0x19, 0x24, 0x44, 0x2b, 0x2b, 0xea, 0x64, + 0x46, 0xb2, 0x49, 0xd0, 0x2f, 0xe2, 0x64, 0x0d, + 0x1f, 0xee, 0xe4, 0x29, 0x04, 0x99, 0x80, 0x8b, + 0x7c, 0x7a, 0x3a, 0x4c, 0xd4, 0x18, 0xd4, 0xf7, + 0x3b, 0x0c, 0x44, 0x39, 0x3d, 0x0f, 0x10, 0xd4, + 0x1f, 0x47, 0x7f, 0xb1, 0xdf, 0xd2, 0xc1, 0xd7, + 0x1d, 0x1f, 0xf7, 0x29, 0x36, 0x54, 0x4b, 0x8e, + 0x55, 0x9b, 0xcb, 0x08, 0xf5, 0x31, 0x0f, 0xd4, + 0x0c, 0x5e, 0x18, 0x59, 0x7e, 0xea, 0xef, 0x5a, + 0xd2, 0x0d, 0xc6, 0x94, 0x5d, 0x83, 0xbd, 0x55, + 0xa9, 0x2f, 0xfe, 0x85, 0x82, 0xd9, 0xa9, 0x91, + 0x40, 0xf6, 0xcc, 0xf9, 0x88, 0xba, 0x72, 0x09, + 0x36, 0x9f, 0xa1, 0xc5, 0x7c, 0xea, 0x93, 0xd4, + 0xae, 0x48, 0xaa, 0x2e, 0x91, 0x93, 0x2f, 0x1b, + 0x66, 0x3e, 0x87, 0x0e, 0xdd, 0xa0, 0x1e, 0x19, + 0x8d, 0x25, 0xdf, 0xbf, 0x39, 0x82, 0xdf, 0x7a, + 0x7c, 0x7c, 0x95, 0xb2, 0xbd, 0x27, 0x2b, 0x3d, + 0x76, 0xef, 0x05, 0x38, 0xfc, 0x8a, 0x38, 0x46, + 0x6d, 0xd5, 0xaa, 0x39, 0x6b, 0xa9, 0xca, 0xf5, + 0x9f, 0xfd, 0x81, 0xc2, 0x4f, 0xa5, 0x8c, 0x12, + 0x90, 0xa3, 0x03, 0xe6, 0xfd, 0x79, 0x6a, 0x48, + 0x69, 0x6c, 0xc3, 0x78, 0x30, 0x9d, 0x79, 0xa6, + 0x81, 0xa7, 0xf4, 0xe9, 0xcf, 0x4e, 0x9d, 0x58, + 0x01, 0x10, 0xed, 0x2c, 0x27, 0x2e, 0x5c, 0xaa, + 0xc8, 0xd0, 0x57, 0xef, 0x05, 0xa5, 0xe7, 0x9f, + 0x8d, 0x07, 0xa7, 0xd7, 0x48, 0x05, 0x7b, 0x70, + 0x17, 0xac, 0xe3, 0xd4, 0x4a, 0x09, 0x92, 0xc4, + 0x8e, 0x84, 0x03, 0x74, 0x5a, 0x61, 0x61, 0x0e, + 0x1a, 0x0c, 0x1f, 0x8c, 0x4e, 0xd9, 0x6c, 0x96, + 0x42, 0xac, 0x93, 0x0b, 0xd4, 0x14, 0x6c, 0xd8, + 0x27, 0x0e, 0x9b, 0xb9, 0x77, 0x0d, 0xd5, 0xdd, + 0x82, 0x08, 0xf5, 0x89, 0xdb, 0x44, 0x86, 0x8d, + 0xb2, 0x2c, 0xa8, 0x06, 0x5f, 0xbc, 0x4c, 0x73, + 0x27, 0xfe, 0x32, 0x26, 0x3c, 0x33, 0x83, 0xda, + 0x18, 0xc9 +}; +#endif /* !WOLFSSH_NO_MLDSA */ + #ifndef WOLFSSH_NO_ECDSA /* P-256 DER with the OID last byte changed 0x07 -> 0x01 (secp192r1). * Forces wc_EccPrivateKeyDecode to fail or to return an unsupported curve id, @@ -2917,6 +3317,1077 @@ static int test_DoUserAuthRequestEd25519(void) #endif /* Ed25519 verify test guards */ +#ifndef WOLFSSH_NO_MLDSA +/* Write 32-bit big-endian length into out. */ +static void MlDsaTest_PutLen(byte* out, word32 v) +{ + out[0] = (byte)(v >> 24); + out[1] = (byte)(v >> 16); + out[2] = (byte)(v >> 8); + out[3] = (byte)(v); +} + +static int test_DoUserAuthRequestMlDsa_Params(const char* keyTypeName, + byte level) +{ + static const char username[] = "wolfssh"; + static const char serviceName[] = "ssh-connection"; + static const char authName[] = "publickey"; + const word32 keyTypeNameSz = (word32)(WSTRLEN(keyTypeName)); + const word32 usernameSz = (word32)(sizeof(username) - 1); + const word32 serviceNameSz = (word32)(sizeof(serviceName) - 1); + const word32 authNameSz = (word32)(sizeof(authName) - 1); + + WOLFSSH_CTX* ctx = NULL; + WOLFSSH* ssh = NULL; + MlDsaKey signingKey; + int signingKeyInit = 0; + WC_RNG rng; + int rngInit = 0; + WS_UserAuthData authData; + + byte* pubKeyBlob = NULL; + byte* sigBlob = NULL; + byte* badSigBlob = NULL; + byte* dataToSign = NULL; + byte* checkData = NULL; + byte* sig = NULL; + byte* pubRaw = NULL; + + word32 pubKeyBlobSz = 0; + word32 sigBlobSz = 0; + word32 dataToSignSz = 0; + word32 checkDataSz = 0; + int sigSzInt; + word32 sigSz = 0; + int pubRawSzInt; + word32 pubRawSz = 0; + word32 off; + int result = 0; + int ret; + + ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); + if (ctx == NULL) return -700; + ssh = wolfSSH_new(ctx); + if (ssh == NULL) { result = -701; goto done; } + + /* Stub a session id so the verify hash has something to absorb. */ + ssh->sessionIdSz = 16; + WMEMSET(ssh->sessionId, 0xA5, ssh->sessionIdSz); + + if (wc_InitRng(&rng) != 0) { + result = -702; + goto done; + } + rngInit = 1; + + if (wc_MlDsaKey_Init(&signingKey, NULL, INVALID_DEVID) != 0) { + result = -703; goto done; + } + signingKeyInit = 1; + if (wc_MlDsaKey_SetParams(&signingKey, level) != 0) { + result = -704; goto done; + } + if (wc_MlDsaKey_MakeKey(&signingKey, &rng) != 0) { + result = -705; goto done; + } + + sigSzInt = wc_MlDsaKey_SigSize(&signingKey); + if (sigSzInt < 0) { result = -706; goto done; } + sigSz = (word32)sigSzInt; + sig = (byte*)WMALLOC(sigSz, NULL, 0); + if (sig == NULL) { result = -718; goto done; } + + /* Get raw public key to build pubKeyBlob */ + pubRawSzInt = wc_MlDsaKey_PubSize(&signingKey); + if (pubRawSzInt < 0) { result = -707; goto done; } + pubRawSz = (word32)pubRawSzInt; + pubRaw = (byte*)WMALLOC(pubRawSz, NULL, 0); + if (pubRaw == NULL) { result = -717; goto done; } + if (wc_MlDsaKey_ExportPubRaw(&signingKey, pubRaw, &pubRawSz) != 0) { + result = -708; goto done; + } + + pubKeyBlob = (byte*)WMALLOC(UINT32_SZ * 2 + keyTypeNameSz + pubRawSz, + NULL, 0); + if (pubKeyBlob == NULL) { result = -709; goto done; } + + /* Build the SSH public key blob: string keyTypeName || string pubkey */ + off = 0; + MlDsaTest_PutLen(pubKeyBlob + off, keyTypeNameSz); off += UINT32_SZ; + WMEMCPY(pubKeyBlob + off, keyTypeName, keyTypeNameSz); + off += keyTypeNameSz; + MlDsaTest_PutLen(pubKeyBlob + off, pubRawSz); off += UINT32_SZ; + WMEMCPY(pubKeyBlob + off, pubRaw, pubRawSz); off += pubRawSz; + pubKeyBlobSz = off; + + /* Build dataToSign: user || svc || auth || hasSig || algo || blob. */ + dataToSignSz = UINT32_SZ * 5 + usernameSz + serviceNameSz + authNameSz + + 1 + keyTypeNameSz + pubKeyBlobSz; + dataToSign = (byte*)WMALLOC(dataToSignSz, NULL, 0); + if (dataToSign == NULL) { result = -710; goto done; } + + off = 0; + MlDsaTest_PutLen(dataToSign + off, usernameSz); off += UINT32_SZ; + WMEMCPY(dataToSign + off, username, usernameSz); off += usernameSz; + MlDsaTest_PutLen(dataToSign + off, serviceNameSz); off += UINT32_SZ; + WMEMCPY(dataToSign + off, serviceName, serviceNameSz); off += serviceNameSz; + MlDsaTest_PutLen(dataToSign + off, authNameSz); off += UINT32_SZ; + WMEMCPY(dataToSign + off, authName, authNameSz); off += authNameSz; + dataToSign[off++] = 1; /* hasSignature */ + MlDsaTest_PutLen(dataToSign + off, keyTypeNameSz); off += UINT32_SZ; + WMEMCPY(dataToSign + off, keyTypeName, keyTypeNameSz); off += keyTypeNameSz; + MlDsaTest_PutLen(dataToSign + off, pubKeyBlobSz); off += UINT32_SZ; + WMEMCPY(dataToSign + off, pubKeyBlob, pubKeyBlobSz); off += pubKeyBlobSz; + + /* Build checkData: session ID || msg ID || dataToSign. */ + checkDataSz = UINT32_SZ + ssh->sessionIdSz + MSG_ID_SZ + dataToSignSz; + checkData = (byte*)WMALLOC(checkDataSz, NULL, 0); + if (checkData == NULL) { result = -711; goto done; } + + off = 0; + MlDsaTest_PutLen(checkData + off, ssh->sessionIdSz); off += UINT32_SZ; + WMEMCPY(checkData + off, ssh->sessionId, ssh->sessionIdSz); + off += ssh->sessionIdSz; + checkData[off++] = MSGID_USERAUTH_REQUEST; + WMEMCPY(checkData + off, dataToSign, dataToSignSz); + + if (wc_MlDsaKey_SignCtx(&signingKey, NULL, 0, sig, &sigSz, checkData, + checkDataSz, &rng) != 0) { + result = -712; goto done; + } + + /* Build the SSH signature blob: string keyTypeName || string sig */ + sigBlobSz = UINT32_SZ * 2 + keyTypeNameSz + sigSz; + sigBlob = (byte*)WMALLOC(sigBlobSz, NULL, 0); + if (sigBlob == NULL) { result = -713; goto done; } + + off = 0; + MlDsaTest_PutLen(sigBlob + off, keyTypeNameSz); off += UINT32_SZ; + WMEMCPY(sigBlob + off, keyTypeName, keyTypeNameSz); off += keyTypeNameSz; + MlDsaTest_PutLen(sigBlob + off, sigSz); off += UINT32_SZ; + WMEMCPY(sigBlob + off, sig, sigSz); off += sigSz; + sigBlobSz = off; + + /* Populate authData */ + WMEMSET(&authData, 0, sizeof(authData)); + authData.type = WOLFSSH_USERAUTH_PUBLICKEY; + authData.username = (const byte*)username; + authData.usernameSz = usernameSz; + authData.serviceName = (const byte*)serviceName; + authData.serviceNameSz = serviceNameSz; + authData.authName = (const byte*)authName; + authData.authNameSz = authNameSz; + authData.sf.publicKey.dataToSign = dataToSign; + authData.sf.publicKey.publicKeyType = (const byte*)keyTypeName; + authData.sf.publicKey.publicKeyTypeSz = keyTypeNameSz; + authData.sf.publicKey.publicKey = pubKeyBlob; + authData.sf.publicKey.publicKeySz = pubKeyBlobSz; + authData.sf.publicKey.hasSignature = 1; + authData.sf.publicKey.signature = sigBlob; + authData.sf.publicKey.signatureSz = sigBlobSz; + + /* Positive case: untouched signature must verify. */ + ret = wolfSSH_TestDoUserAuthRequestMlDsa(ssh, &authData, pubKeyBlobSz); + if (ret != WS_SUCCESS) { + printf("DoUserAuthRequestMlDsa positive (%s): ret=%d (expected %d)\n", + keyTypeName, ret, WS_SUCCESS); + result = -714; goto done; + } + + /* Negative case: flip a byte inside the raw signature */ + badSigBlob = (byte*)WMALLOC(sigBlobSz, NULL, 0); + if (badSigBlob == NULL) { result = -715; goto done; } + WMEMCPY(badSigBlob, sigBlob, sigBlobSz); + badSigBlob[UINT32_SZ + keyTypeNameSz + UINT32_SZ + 10] ^= 0xFF; + authData.sf.publicKey.signature = badSigBlob; + + ret = wolfSSH_TestDoUserAuthRequestMlDsa(ssh, &authData, pubKeyBlobSz); + if (ret != WS_MLDSA_E && ret != WS_CRYPTO_FAILED) { + printf("DoUserAuthRequestMlDsa tampered (%s): ret=%d (expected %d)\n", + keyTypeName, ret, WS_MLDSA_E); + result = -716; goto done; + } + +done: + if (signingKeyInit) + wc_MlDsaKey_Free(&signingKey); + if (rngInit) + wc_FreeRng(&rng); + if (pubKeyBlob != NULL) WFREE(pubKeyBlob, NULL, 0); + if (sigBlob != NULL) WFREE(sigBlob, NULL, 0); + if (badSigBlob != NULL) WFREE(badSigBlob, NULL, 0); + if (dataToSign != NULL) WFREE(dataToSign, NULL, 0); + if (checkData != NULL) WFREE(checkData, NULL, 0); + if (sig != NULL) WFREE(sig, NULL, 0); + if (pubRaw != NULL) WFREE(pubRaw, NULL, 0); + if (ssh != NULL) wolfSSH_free(ssh); + if (ctx != NULL) wolfSSH_CTX_free(ctx); + return result; +} + +/* unknown publicKeyType must be rejected at the boundary */ +static int test_DoUserAuthRequestMlDsa_BadAlgo(void) +{ + static const char badAlgo[] = "not-an-mldsa-key"; + WOLFSSH_CTX* ctx = NULL; + WOLFSSH* ssh = NULL; + WS_UserAuthData authData; + int result = 0; + int ret; + + ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); + if (ctx == NULL) return -800; + ssh = wolfSSH_new(ctx); + if (ssh == NULL) { result = -801; goto done; } + + WMEMSET(&authData, 0, sizeof(authData)); + authData.type = WOLFSSH_USERAUTH_PUBLICKEY; + authData.sf.publicKey.publicKeyType = + (const byte*)badAlgo; + authData.sf.publicKey.publicKeyTypeSz = + (word32)(sizeof(badAlgo) - 1); + + /* pubKeyBlobSz=0: the bad key type is rejected before dataToSign is sized, + * so the value is irrelevant for this code path. */ + ret = wolfSSH_TestDoUserAuthRequestMlDsa(ssh, &authData, 0); + if (ret != WS_INVALID_ALGO_ID) { + printf("DoUserAuthRequestMlDsa bad-algo: ret=%d expected %d\n", + ret, WS_INVALID_ALGO_ID); + result = -802; + } + +done: + if (ssh != NULL) wolfSSH_free(ssh); + if (ctx != NULL) wolfSSH_CTX_free(ctx); + return result; +} + +#ifdef WOLFSSH_CERTS + /* Confirm the cert branch is entered, ParseCertChainVerify intentionally + * rejects this. */ +static int test_DoUserAuthRequestMlDsa_CertPath(const char* keyTypeName) +{ + static const char username[] = "wolfssh"; + static const char serviceName[] = "ssh-connection"; + static const char authName[] = "publickey"; + const word32 keyTypeNameSz = (word32)WSTRLEN(keyTypeName); + /* NOTE: pubKeyBlob is an RFC 6187 wire blob, not leaf-cert DER. The real + * server path calls ParseLeafCert() first to extract DER. This test + * exercises ASN.1-invalid rejection rather than cryptographic rejection — + * valid for a negative path test, but does not cover the DER-valid case. */ + static const byte junkCert[] = { 0x30, 0x05, 0x00, 0x00, 0x00, 0x00 }; + const word32 junkCertSz = (word32)sizeof(junkCert); + WOLFSSH_CTX* ctx = NULL; + WOLFSSH* ssh = NULL; + WS_UserAuthData authData; + byte* pubKeyBlob = NULL; + byte* sigBlob = NULL; + word32 pubKeyBlobSz, off; + int result = 0; + int ret; + + /* RFC 6187 cert chain blob: + * string keyTypeName + * uint32 certCount=1 + * string junkCert + * uint32 ocspCount=0 */ + pubKeyBlobSz = UINT32_SZ + keyTypeNameSz + UINT32_SZ + + UINT32_SZ + junkCertSz + UINT32_SZ; + pubKeyBlob = (byte*)WMALLOC(pubKeyBlobSz, NULL, 0); + if (pubKeyBlob == NULL) { result = -820; goto done; } + off = 0; + MlDsaTest_PutLen(pubKeyBlob + off, keyTypeNameSz); off += UINT32_SZ; + WMEMCPY(pubKeyBlob + off, keyTypeName, keyTypeNameSz); off += keyTypeNameSz; + MlDsaTest_PutLen(pubKeyBlob + off, 1); off += UINT32_SZ; + MlDsaTest_PutLen(pubKeyBlob + off, junkCertSz); off += UINT32_SZ; + WMEMCPY(pubKeyBlob + off, junkCert, junkCertSz); off += junkCertSz; + MlDsaTest_PutLen(pubKeyBlob + off, 0); off += UINT32_SZ; + + /* Minimal sig blob: string keyTypeName || string(1 zero byte) */ + sigBlob = (byte*)WMALLOC(UINT32_SZ * 2 + keyTypeNameSz + 1, NULL, 0); + if (sigBlob == NULL) { result = -821; goto done; } + off = 0; + MlDsaTest_PutLen(sigBlob + off, keyTypeNameSz); off += UINT32_SZ; + WMEMCPY(sigBlob + off, keyTypeName, keyTypeNameSz); off += keyTypeNameSz; + MlDsaTest_PutLen(sigBlob + off, 1); off += UINT32_SZ; + sigBlob[off] = 0x00; + + ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); + if (ctx == NULL) { result = -822; goto done; } + ssh = wolfSSH_new(ctx); + if (ssh == NULL) { result = -823; goto done; } + ssh->sessionIdSz = 16; + WMEMSET(ssh->sessionId, 0xA5, ssh->sessionIdSz); + + WMEMSET(&authData, 0, sizeof(authData)); + authData.type = WOLFSSH_USERAUTH_PUBLICKEY; + authData.username = (const byte*)username; + authData.usernameSz = (word32)(sizeof(username) - 1); + authData.serviceName = (const byte*)serviceName; + authData.serviceNameSz = (word32)(sizeof(serviceName) - 1); + authData.authName = (const byte*)authName; + authData.authNameSz = (word32)(sizeof(authName) - 1); + authData.sf.publicKey.publicKeyType = (const byte*)keyTypeName; + authData.sf.publicKey.publicKeyTypeSz = keyTypeNameSz; + authData.sf.publicKey.publicKey = pubKeyBlob; + authData.sf.publicKey.publicKeySz = pubKeyBlobSz; + authData.sf.publicKey.hasSignature = 1; + authData.sf.publicKey.signature = sigBlob; + authData.sf.publicKey.signatureSz = UINT32_SZ * 2 + keyTypeNameSz + 1; + + ret = wolfSSH_TestDoUserAuthRequestMlDsa(ssh, &authData, pubKeyBlobSz); + if (ret == WS_INVALID_ALGO_ID) { + /* Routing failed: x509v3-mldsa-* was not recognised and hit the + * unknown-algo guard instead of the cert parse path. */ + printf("DoUserAuthRequestMlDsa cert-path (%s): routing failed\n", + keyTypeName); + result = -824; + } + else if (ret == WS_SUCCESS) { + /* Wrongful acceptance: junk cert should have been rejected */ + printf("DoUserAuthRequestMlDsa cert-path (%s): " + "wrongfully accepted junk cert\n", + keyTypeName); + result = -825; + } + /* Any other error is expected: junk cert correctly rejected. */ + +done: + if (pubKeyBlob != NULL) WFREE(pubKeyBlob, NULL, 0); + if (sigBlob != NULL) WFREE(sigBlob, NULL, 0); + if (ssh != NULL) wolfSSH_free(ssh); + if (ctx != NULL) wolfSSH_CTX_free(ctx); + return result; +} + +#if defined(WOLFSSH_CERTS) && defined(WOLFSSL_CERT_GEN) +/* Positive cert-path test: generate a real ML-DSA self-signed cert, build a + * valid auth request, and verify that DoUserAuthRequestMlDsa accepts it. */ +static int test_DoUserAuthRequestMlDsa_CertPath_Valid( + const char* keyTypeName, byte level, int certSigType) +{ + static const char username[] = "wolfssh"; + static const char serviceName[] = "ssh-connection"; + static const char authName[] = "publickey"; + const word32 keyTypeNameSz = (word32)WSTRLEN(keyTypeName); + const word32 usernameSz = (word32)(sizeof(username) - 1); + const word32 serviceNameSz = (word32)(sizeof(serviceName) - 1); + const word32 authNameSz = (word32)(sizeof(authName) - 1); + + WOLFSSH_CTX* ctx = NULL; + WOLFSSH* ssh = NULL; + MlDsaKey signingKey; + int signingKeyInit = 0; + WC_RNG rng; + int rngInit = 0; + Cert myCert; + WS_UserAuthData authData; + + byte* certDER = NULL; + byte* rfcBlob = NULL; /* RFC6187 cert chain blob */ + byte* sigBlob = NULL; + byte* dataToSign = NULL; + byte* checkData = NULL; + byte* sig = NULL; + + word32 certDERSz = 0; + word32 rfcBlobSz = 0; + word32 sigBlobSz = 0; + word32 dataToSignSz = 0; + word32 checkDataSz = 0; + int sigSzInt; + word32 sigSz = 0; + int mldsaKeyType; + word32 off; + int result = 0; + int ret; + + mldsaKeyType = (level == WC_ML_DSA_44) ? ML_DSA_44_TYPE : + (level == WC_ML_DSA_65) ? ML_DSA_65_TYPE : ML_DSA_87_TYPE; + + ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); + if (ctx == NULL) return -850; + ssh = wolfSSH_new(ctx); + if (ssh == NULL) { result = -851; goto done; } + ssh->sessionIdSz = 16; + WMEMSET(ssh->sessionId, 0xA5, ssh->sessionIdSz); + + if (wc_InitRng(&rng) != 0) { result = -852; goto done; } + rngInit = 1; + + if (wc_MlDsaKey_Init(&signingKey, NULL, INVALID_DEVID) != 0) { + result = -853; goto done; + } + signingKeyInit = 1; + if (wc_MlDsaKey_SetParams(&signingKey, level) != 0) { + result = -854; goto done; + } + if (wc_MlDsaKey_MakeKey(&signingKey, &rng) != 0) { + result = -855; goto done; + } + + /* Generate a self-signed X.509 cert containing the ML-DSA public key. + * 16384 bytes is sufficient for the largest ML-DSA variant (ML-DSA-87). */ + certDER = (byte*)WMALLOC(16384, NULL, 0); + if (certDER == NULL) { result = -856; goto done; } + + wc_InitCert(&myCert); + WSTRNCPY(myCert.subject.commonName, "wolfSSH-mldsa-test", + CTC_NAME_SIZE - 1); + myCert.subject.commonNameEnc = CTC_UTF8; + WSTRNCPY(myCert.subject.country, "US", CTC_NAME_SIZE - 1); + myCert.daysValid = 365; + myCert.selfSigned = 1; + myCert.sigType = certSigType; + + ret = wc_MakeCert_ex(&myCert, certDER, 16384, mldsaKeyType, + &signingKey, &rng); + if (ret <= 0) { result = -857; goto done; } + ret = wc_SignCert_ex(ret, certSigType, certDER, 16384, mldsaKeyType, + &signingKey, &rng); + if (ret <= 0) { result = -858; goto done; } + certDERSz = (word32)ret; + + /* Build RFC6187 cert chain blob: + * string keyTypeName | uint32 certCount=1 | string certDER | + * uint32 ocspCount=0 */ + rfcBlobSz = UINT32_SZ + keyTypeNameSz + UINT32_SZ + + UINT32_SZ + certDERSz + UINT32_SZ; + rfcBlob = (byte*)WMALLOC(rfcBlobSz, NULL, 0); + if (rfcBlob == NULL) { result = -859; goto done; } + off = 0; + MlDsaTest_PutLen(rfcBlob + off, keyTypeNameSz); off += UINT32_SZ; + WMEMCPY(rfcBlob + off, keyTypeName, keyTypeNameSz); off += keyTypeNameSz; + MlDsaTest_PutLen(rfcBlob + off, 1); off += UINT32_SZ; + MlDsaTest_PutLen(rfcBlob + off, certDERSz); off += UINT32_SZ; + WMEMCPY(rfcBlob + off, certDER, certDERSz); off += certDERSz; + MlDsaTest_PutLen(rfcBlob + off, 0); off += UINT32_SZ; + + /* Build dataToSign; pubkey blob length uses RFC6187 blob size. */ + dataToSignSz = UINT32_SZ * 5 + usernameSz + serviceNameSz + authNameSz + + 1 + keyTypeNameSz + rfcBlobSz; + dataToSign = (byte*)WMALLOC(dataToSignSz, NULL, 0); + if (dataToSign == NULL) { result = -860; goto done; } + off = 0; + MlDsaTest_PutLen(dataToSign + off, usernameSz); off += UINT32_SZ; + WMEMCPY(dataToSign + off, username, usernameSz); off += usernameSz; + MlDsaTest_PutLen(dataToSign + off, serviceNameSz); off += UINT32_SZ; + WMEMCPY(dataToSign + off, serviceName, serviceNameSz); off += serviceNameSz; + MlDsaTest_PutLen(dataToSign + off, authNameSz); off += UINT32_SZ; + WMEMCPY(dataToSign + off, authName, authNameSz); off += authNameSz; + dataToSign[off++] = 1; /* hasSig */ + MlDsaTest_PutLen(dataToSign + off, keyTypeNameSz); off += UINT32_SZ; + WMEMCPY(dataToSign + off, keyTypeName, keyTypeNameSz); off += keyTypeNameSz; + MlDsaTest_PutLen(dataToSign + off, rfcBlobSz); off += UINT32_SZ; + WMEMCPY(dataToSign + off, rfcBlob, rfcBlobSz); off += rfcBlobSz; + + /* Build checkData and sign it. */ + sigSzInt = wc_MlDsaKey_SigSize(&signingKey); + if (sigSzInt < 0) { result = -861; goto done; } + sigSz = (word32)sigSzInt; + sig = (byte*)WMALLOC(sigSz, NULL, 0); + if (sig == NULL) { result = -871; goto done; } + + checkDataSz = UINT32_SZ + ssh->sessionIdSz + MSG_ID_SZ + dataToSignSz; + checkData = (byte*)WMALLOC(checkDataSz, NULL, 0); + if (checkData == NULL) { result = -862; goto done; } + off = 0; + MlDsaTest_PutLen(checkData + off, ssh->sessionIdSz); off += UINT32_SZ; + WMEMCPY(checkData + off, ssh->sessionId, ssh->sessionIdSz); + off += ssh->sessionIdSz; + checkData[off++] = MSGID_USERAUTH_REQUEST; + WMEMCPY(checkData + off, dataToSign, dataToSignSz); + + if (wc_MlDsaKey_SignCtx(&signingKey, NULL, 0, sig, &sigSz, checkData, + checkDataSz, &rng) != 0) { + result = -863; goto done; + } + + /* Build signature blob: string keyTypeName || string sig */ + sigBlobSz = UINT32_SZ * 2 + keyTypeNameSz + sigSz; + sigBlob = (byte*)WMALLOC(sigBlobSz, NULL, 0); + if (sigBlob == NULL) { result = -864; goto done; } + off = 0; + MlDsaTest_PutLen(sigBlob + off, keyTypeNameSz); off += UINT32_SZ; + WMEMCPY(sigBlob + off, keyTypeName, keyTypeNameSz); off += keyTypeNameSz; + MlDsaTest_PutLen(sigBlob + off, sigSz); off += UINT32_SZ; + WMEMCPY(sigBlob + off, sig, sigSz); off += sigSz; + sigBlobSz = off; + + /* DoUserAuthRequestMlDsa with isCert=1 expects pk->publicKey to be the + * leaf cert DER. Pass rfcBlobSz explicitly so dataToSign is sized from + * the wire blob length, not the cert DER length. */ + WMEMSET(&authData, 0, sizeof(authData)); + authData.type = WOLFSSH_USERAUTH_PUBLICKEY; + authData.username = (const byte*)username; + authData.usernameSz = usernameSz; + authData.serviceName = (const byte*)serviceName; + authData.serviceNameSz = serviceNameSz; + authData.authName = (const byte*)authName; + authData.authNameSz = authNameSz; + authData.sf.publicKey.dataToSign = dataToSign; + authData.sf.publicKey.publicKeyType = (const byte*)keyTypeName; + authData.sf.publicKey.publicKeyTypeSz = keyTypeNameSz; + authData.sf.publicKey.publicKey = certDER; + authData.sf.publicKey.publicKeySz = certDERSz; + authData.sf.publicKey.hasSignature = 1; + authData.sf.publicKey.isCert = 1; + authData.sf.publicKey.signature = sigBlob; + authData.sf.publicKey.signatureSz = sigBlobSz; + + ret = wolfSSH_TestDoUserAuthRequestMlDsa(ssh, &authData, rfcBlobSz); + if (ret != WS_SUCCESS) { + printf("DoUserAuthRequestMlDsa cert-path valid (%s): " + "ret=%d expected %d\n", + keyTypeName, ret, WS_SUCCESS); + result = -865; + } + +done: + if (signingKeyInit) wc_MlDsaKey_Free(&signingKey); + if (rngInit) wc_FreeRng(&rng); + if (certDER != NULL) WFREE(certDER, NULL, 0); + if (rfcBlob != NULL) WFREE(rfcBlob, NULL, 0); + if (sigBlob != NULL) WFREE(sigBlob, NULL, 0); + if (dataToSign != NULL) WFREE(dataToSign, NULL, 0); + if (checkData != NULL) WFREE(checkData, NULL, 0); + if (sig != NULL) WFREE(sig, NULL, 0); + if (ssh != NULL) wolfSSH_free(ssh); + if (ctx != NULL) wolfSSH_CTX_free(ctx); + return result; +} +/* Cross-level mismatch: cert has ML-DSA key at a different level than + * keyTypeName claims. PublicKeyDecode fails, should return WS_CRYPTO_FAILED. + * Requires both MLDSA44 (for the claimed type) and MLDSA65 (for the actual + * key embedded in the cert). */ +#if !defined(WOLFSSH_NO_MLDSA44) && !defined(WOLFSSH_NO_MLDSA65) +static int test_DoUserAuthRequestMlDsa_CertPath_WrongLevel(void) +{ + /* Claim ML-DSA-44 type but embed an ML-DSA-65 key in the cert. */ + static const char keyTypeName[] = "x509v3-ssh-mldsa-44"; + static const char username[] = "wolfssh"; + static const char serviceName[] = "ssh-connection"; + static const char authName[] = "publickey"; + const word32 keyTypeNameSz = (word32)WSTRLEN(keyTypeName); + const word32 usernameSz = (word32)(sizeof(username) - 1); + const word32 serviceNameSz = (word32)(sizeof(serviceName) - 1); + const word32 authNameSz = (word32)(sizeof(authName) - 1); + + WOLFSSH_CTX* ctx = NULL; + WOLFSSH* ssh = NULL; + MlDsaKey signingKey; + int signingKeyInit = 0; + WC_RNG rng; + int rngInit = 0; + Cert myCert; + WS_UserAuthData authData; + + byte* certDER = NULL; + byte* sigBlob = NULL; + byte* dataToSign = NULL; + word32 certDERSz = 0; + word32 dataToSignSz = 0; + word32 off; + int result = 0; + int ret; + + ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_SERVER, NULL); + if (ctx == NULL) return -880; + ssh = wolfSSH_new(ctx); + if (ssh == NULL) { result = -881; goto done; } + ssh->sessionIdSz = 16; + WMEMSET(ssh->sessionId, 0xA5, ssh->sessionIdSz); + + if (wc_InitRng(&rng) != 0) { result = -882; goto done; } + rngInit = 1; + + if (wc_MlDsaKey_Init(&signingKey, NULL, INVALID_DEVID) != 0) { + result = -883; goto done; + } + signingKeyInit = 1; + if (wc_MlDsaKey_SetParams(&signingKey, WC_ML_DSA_65) != 0) { + result = -884; goto done; + } + if (wc_MlDsaKey_MakeKey(&signingKey, &rng) != 0) { + result = -885; goto done; + } + + certDER = (byte*)WMALLOC(16384, NULL, 0); + if (certDER == NULL) { result = -886; goto done; } + + wc_InitCert(&myCert); + WSTRNCPY(myCert.subject.commonName, "wolfSSH-mldsa-test", + CTC_NAME_SIZE - 1); + myCert.subject.commonNameEnc = CTC_UTF8; + WSTRNCPY(myCert.subject.country, "US", CTC_NAME_SIZE - 1); + myCert.daysValid = 365; + myCert.selfSigned = 1; + myCert.sigType = CTC_ML_DSA_65; + + ret = wc_MakeCert_ex(&myCert, certDER, 16384, ML_DSA_65_TYPE, + &signingKey, &rng); + if (ret <= 0) { result = -887; goto done; } + ret = wc_SignCert_ex(ret, CTC_ML_DSA_65, certDER, 16384, + ML_DSA_65_TYPE, &signingKey, &rng); + if (ret <= 0) { result = -888; goto done; } + certDERSz = (word32)ret; + + /* Dummy sig blob: keyTypeName || one zero byte. */ + sigBlob = (byte*)WMALLOC(UINT32_SZ * 2 + keyTypeNameSz + 1, NULL, 0); + if (sigBlob == NULL) { result = -889; goto done; } + off = 0; + MlDsaTest_PutLen(sigBlob + off, keyTypeNameSz); off += UINT32_SZ; + WMEMCPY(sigBlob + off, keyTypeName, keyTypeNameSz); off += keyTypeNameSz; + MlDsaTest_PutLen(sigBlob + off, 1); off += UINT32_SZ; + sigBlob[off] = 0x00; + + /* Sized to match the checkData formula in DoUserAuthRequestMlDsa with + * pubKeyBlobSz=0. Populated with zeros - not a valid payload, but + * non-NULL so that if key-level enforcement ever relaxes, execution fails + * at sig verify rather than crashing on a NULL deref. */ + dataToSignSz = UINT32_SZ * 5 + usernameSz + serviceNameSz + authNameSz + + BOOLEAN_SZ + keyTypeNameSz; + dataToSign = (byte*)WMALLOC(dataToSignSz, NULL, 0); + if (dataToSign == NULL) { result = -890; goto done; } + WMEMSET(dataToSign, 0, dataToSignSz); + + WMEMSET(&authData, 0, sizeof(authData)); + authData.type = WOLFSSH_USERAUTH_PUBLICKEY; + authData.username = (const byte*)username; + authData.usernameSz = usernameSz; + authData.serviceName = (const byte*)serviceName; + authData.serviceNameSz = serviceNameSz; + authData.authName = (const byte*)authName; + authData.authNameSz = authNameSz; + authData.sf.publicKey.publicKeyType = (const byte*)keyTypeName; + authData.sf.publicKey.publicKeyTypeSz = keyTypeNameSz; + authData.sf.publicKey.publicKey = certDER; + authData.sf.publicKey.publicKeySz = certDERSz; + authData.sf.publicKey.hasSignature = 1; + authData.sf.publicKey.isCert = 1; + authData.sf.publicKey.dataToSign = dataToSign; + authData.sf.publicKey.signature = sigBlob; + authData.sf.publicKey.signatureSz = UINT32_SZ * 2 + keyTypeNameSz + 1; + + ret = wolfSSH_TestDoUserAuthRequestMlDsa(ssh, &authData, 0); + if (ret == WS_SUCCESS) { + printf("DoUserAuthRequestMlDsa cert-path wrong-level: " + "wrongfully accepted\n"); + result = -891; + } + else if (ret != WS_CRYPTO_FAILED) { + printf("DoUserAuthRequestMlDsa cert-path wrong-level: " + "ret=%d expected %d\n", + ret, WS_CRYPTO_FAILED); + result = -892; + } + +done: + if (signingKeyInit) wc_MlDsaKey_Free(&signingKey); + if (rngInit) wc_FreeRng(&rng); + if (certDER != NULL) WFREE(certDER, NULL, 0); + if (sigBlob != NULL) WFREE(sigBlob, NULL, 0); + if (dataToSign != NULL) WFREE(dataToSign, NULL, 0); + if (ssh != NULL) wolfSSH_free(ssh); + if (ctx != NULL) wolfSSH_CTX_free(ctx); + return result; +} +#endif /* WOLFSSH_NO_MLDSA44 && WOLFSSH_NO_MLDSA65 */ +#endif /* WOLFSSH_CERTS && WOLFSSL_CERT_GEN */ +#endif /* WOLFSSH_CERTS */ + +#ifdef WOLFSSH_KEYGEN +static int test_PrepareUserAuthRequestMlDsa_Params(word32 keygenLevel, + byte keyId, int derBufSz) +{ + WOLFSSH_CTX* ctx = NULL; + WOLFSSH* ssh = NULL; + WS_UserAuthData authData; + WS_KeySignature keySig; + byte* derKey = NULL; + word32 payloadSz; + int derKeySz; + int result = 0; + int ret; + + ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); + if (ctx == NULL) return -900; + ssh = wolfSSH_new(ctx); + if (ssh == NULL) { result = -901; goto done; } + + derKey = (byte*)WMALLOC(derBufSz, NULL, 0); + if (derKey == NULL) { result = -902; goto done; } + + derKeySz = wolfSSH_MakeMlDsaKey(derKey, (word32)derBufSz, keygenLevel); + if (derKeySz < 0) { result = -903; goto done; } + + /* DER success path */ + WMEMSET(&authData, 0, sizeof(authData)); + WMEMSET(&keySig, 0, sizeof(keySig)); + payloadSz = 0; + authData.sf.publicKey.privateKey = derKey; + authData.sf.publicKey.privateKeySz = (word32)derKeySz; + authData.sf.publicKey.hasSignature = 0; + keySig.keyId = keyId; + keySig.heap = NULL; + ret = wolfSSH_TestPrepareUserAuthRequestMlDsa(ssh, &payloadSz, + &authData, &keySig); + if (ret != WS_SUCCESS) { result = -904; goto done; } + wc_MlDsaKey_Free(&keySig.ks.mldsa.key); + + /* Fallback path: garbage fails PrivateKeyDecode, then fails OpenSSH + * magic check; function must return an error (not leak the Init'd key). */ + { + static const byte badKey[] = { 0xFF, 0xFE, 0x00, 0x01 }; + WMEMSET(&authData, 0, sizeof(authData)); + WMEMSET(&keySig, 0, sizeof(keySig)); + payloadSz = 0; + authData.sf.publicKey.privateKey = badKey; + authData.sf.publicKey.privateKeySz = sizeof(badKey); + authData.sf.publicKey.hasSignature = 0; + keySig.keyId = keyId; + keySig.heap = NULL; + ret = wolfSSH_TestPrepareUserAuthRequestMlDsa(ssh, &payloadSz, + &authData, &keySig); + if (ret == WS_SUCCESS) { + wc_MlDsaKey_Free(&keySig.ks.mldsa.key); + result = -905; + goto done; + } + } + + /* hasSignature=1 path: exercises payload-size accumulation and sigSz. */ + { + WMEMSET(&authData, 0, sizeof(authData)); + WMEMSET(&keySig, 0, sizeof(keySig)); + payloadSz = 0; + authData.sf.publicKey.privateKey = derKey; + authData.sf.publicKey.privateKeySz = (word32)derKeySz; + authData.sf.publicKey.hasSignature = 1; + keySig.keyId = keyId; + keySig.heap = NULL; + ret = wolfSSH_TestPrepareUserAuthRequestMlDsa(ssh, &payloadSz, + &authData, &keySig); + /* On failure the function frees the key internally; only free on + * success paths where the key was left initialized for the caller. */ + if (ret != WS_SUCCESS) { result = -906; goto done; } + if (keySig.sigSz == 0) { + wc_MlDsaKey_Free(&keySig.ks.mldsa.key); + result = -907; goto done; + } + if (payloadSz == 0) { + wc_MlDsaKey_Free(&keySig.ks.mldsa.key); + result = -908; goto done; + } + wc_MlDsaKey_Free(&keySig.ks.mldsa.key); + } + +done: + WFREE(derKey, NULL, 0); + wolfSSH_free(ssh); + wolfSSH_CTX_free(ctx); + return result; +} + +#endif /* WOLFSSH_KEYGEN */ + +static int test_DoUserAuthRequestMlDsa(void) +{ + int ret; +#ifndef WOLFSSH_NO_MLDSA44 + ret = test_DoUserAuthRequestMlDsa_Params("ssh-mldsa-44", WC_ML_DSA_44); + if (ret != 0) return ret; +#endif +#ifndef WOLFSSH_NO_MLDSA65 + ret = test_DoUserAuthRequestMlDsa_Params("ssh-mldsa-65", WC_ML_DSA_65); + if (ret != 0) return ret; +#endif +#ifndef WOLFSSH_NO_MLDSA87 + ret = test_DoUserAuthRequestMlDsa_Params("ssh-mldsa-87", WC_ML_DSA_87); + if (ret != 0) return ret; +#endif + ret = test_DoUserAuthRequestMlDsa_BadAlgo(); + if (ret != 0) return ret; +#ifdef WOLFSSH_CERTS +#ifndef WOLFSSH_NO_MLDSA44 + ret = test_DoUserAuthRequestMlDsa_CertPath("x509v3-ssh-mldsa-44"); + if (ret != 0) return ret; +#endif +#ifndef WOLFSSH_NO_MLDSA65 + ret = test_DoUserAuthRequestMlDsa_CertPath("x509v3-ssh-mldsa-65"); + if (ret != 0) return ret; +#endif +#ifndef WOLFSSH_NO_MLDSA87 + ret = test_DoUserAuthRequestMlDsa_CertPath("x509v3-ssh-mldsa-87"); + if (ret != 0) return ret; +#endif +#ifdef WOLFSSL_CERT_GEN +#ifndef WOLFSSH_NO_MLDSA44 + ret = test_DoUserAuthRequestMlDsa_CertPath_Valid( + "x509v3-ssh-mldsa-44", WC_ML_DSA_44, CTC_ML_DSA_44); + if (ret != 0) return ret; +#endif +#ifndef WOLFSSH_NO_MLDSA65 + ret = test_DoUserAuthRequestMlDsa_CertPath_Valid( + "x509v3-ssh-mldsa-65", WC_ML_DSA_65, CTC_ML_DSA_65); + if (ret != 0) return ret; +#endif +#ifndef WOLFSSH_NO_MLDSA87 + ret = test_DoUserAuthRequestMlDsa_CertPath_Valid( + "x509v3-ssh-mldsa-87", WC_ML_DSA_87, CTC_ML_DSA_87); + if (ret != 0) return ret; +#endif +#if !defined(WOLFSSH_NO_MLDSA44) && !defined(WOLFSSH_NO_MLDSA65) + ret = test_DoUserAuthRequestMlDsa_CertPath_WrongLevel(); + if (ret != 0) return ret; +#endif +#endif /* WOLFSSL_CERT_GEN */ +#endif /* WOLFSSH_CERTS */ + return 0; +} + +#ifdef WOLFSSH_KEYGEN +static int test_PrepareUserAuthRequestMlDsa(void) +{ + int ret = 0; +#ifndef WOLFSSH_NO_MLDSA44 + ret = test_PrepareUserAuthRequestMlDsa_Params(WOLFSSH_MLDSAKEY_44, + ID_MLDSA44, WC_MLDSA_44_BOTH_KEY_DER_SIZE); + if (ret != 0) return ret; +#endif +#ifndef WOLFSSH_NO_MLDSA65 + ret = test_PrepareUserAuthRequestMlDsa_Params(WOLFSSH_MLDSAKEY_65, + ID_MLDSA65, WC_MLDSA_65_BOTH_KEY_DER_SIZE); + if (ret != 0) return ret; +#endif +#ifndef WOLFSSH_NO_MLDSA87 + ret = test_PrepareUserAuthRequestMlDsa_Params(WOLFSSH_MLDSAKEY_87, + ID_MLDSA87, WC_MLDSA_87_BOTH_KEY_DER_SIZE); + if (ret != 0) return ret; +#endif + (void)ret; + return 0; +} + +#ifdef WOLFSSH_CERTS +static int test_PrepareUserAuthRequestMlDsaCert_Params(word32 keygenLevel, + byte keyId, int derBufSz) +{ + WOLFSSH_CTX* ctx = NULL; + WOLFSSH* ssh = NULL; + WS_UserAuthData authData; + WS_KeySignature keySig; + byte* derKey = NULL; + word32 payloadSz; + int derKeySz; + int result = 0; + int ret; + + ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); + if (ctx == NULL) return -920; + ssh = wolfSSH_new(ctx); + if (ssh == NULL) { result = -921; goto done; } + + derKey = (byte*)WMALLOC(derBufSz, NULL, 0); + if (derKey == NULL) { result = -922; goto done; } + + derKeySz = wolfSSH_MakeMlDsaKey(derKey, (word32)derBufSz, keygenLevel); + if (derKeySz < 0) { result = -923; goto done; } + + /* Success path: good key, hasSignature=0 */ + WMEMSET(&authData, 0, sizeof(authData)); + WMEMSET(&keySig, 0, sizeof(keySig)); + payloadSz = 0; + authData.sf.publicKey.privateKey = derKey; + authData.sf.publicKey.privateKeySz = (word32)derKeySz; + authData.sf.publicKey.hasSignature = 0; + keySig.keyId = keyId; + keySig.heap = NULL; + ret = wolfSSH_TestPrepareUserAuthRequestMlDsaCert(ssh, &payloadSz, + &authData, &keySig); + if (ret != WS_SUCCESS) { result = -924; goto done; } + wc_MlDsaKey_Free(&keySig.ks.mldsa.key); + + /* Bad key: exercises the PrivateKeyDecode failure free path */ + { + static const byte badKey[] = { 0xFF, 0xFE, 0x00, 0x01 }; + WMEMSET(&authData, 0, sizeof(authData)); + WMEMSET(&keySig, 0, sizeof(keySig)); + payloadSz = 0; + authData.sf.publicKey.privateKey = badKey; + authData.sf.publicKey.privateKeySz = sizeof(badKey); + authData.sf.publicKey.hasSignature = 0; + keySig.keyId = keyId; + keySig.heap = NULL; + ret = wolfSSH_TestPrepareUserAuthRequestMlDsaCert(ssh, &payloadSz, + &authData, &keySig); + if (ret == WS_SUCCESS) { result = -925; goto done; } + } + + /* hasSignature=1 path: exercises sigSz accumulation */ + { + WMEMSET(&authData, 0, sizeof(authData)); + WMEMSET(&keySig, 0, sizeof(keySig)); + payloadSz = 0; + authData.sf.publicKey.privateKey = derKey; + authData.sf.publicKey.privateKeySz = (word32)derKeySz; + authData.sf.publicKey.hasSignature = 1; + keySig.keyId = keyId; + keySig.heap = NULL; + ret = wolfSSH_TestPrepareUserAuthRequestMlDsaCert(ssh, &payloadSz, + &authData, &keySig); + if (ret != WS_SUCCESS) { + wc_MlDsaKey_Free(&keySig.ks.mldsa.key); + result = -926; goto done; + } + if (keySig.sigSz == 0) { + wc_MlDsaKey_Free(&keySig.ks.mldsa.key); + result = -927; goto done; + } + if (payloadSz == 0) { + wc_MlDsaKey_Free(&keySig.ks.mldsa.key); + result = -928; goto done; + } + wc_MlDsaKey_Free(&keySig.ks.mldsa.key); + } + +done: + WFREE(derKey, NULL, 0); + wolfSSH_free(ssh); + wolfSSH_CTX_free(ctx); + return result; +} + +static int test_PrepareUserAuthRequestMlDsaCert(void) +{ + int ret = 0; +#ifndef WOLFSSH_NO_MLDSA44 + ret = test_PrepareUserAuthRequestMlDsaCert_Params(WOLFSSH_MLDSAKEY_44, + ID_X509V3_MLDSA44, WC_MLDSA_44_BOTH_KEY_DER_SIZE); + if (ret != 0) return ret; +#endif +#ifndef WOLFSSH_NO_MLDSA65 + ret = test_PrepareUserAuthRequestMlDsaCert_Params(WOLFSSH_MLDSAKEY_65, + ID_X509V3_MLDSA65, WC_MLDSA_65_BOTH_KEY_DER_SIZE); + if (ret != 0) return ret; +#endif +#ifndef WOLFSSH_NO_MLDSA87 + ret = test_PrepareUserAuthRequestMlDsaCert_Params(WOLFSSH_MLDSAKEY_87, + ID_X509V3_MLDSA87, WC_MLDSA_87_BOTH_KEY_DER_SIZE); + if (ret != 0) return ret; +#endif + (void)ret; + return 0; +} +#endif /* WOLFSSH_CERTS */ + +#endif /* WOLFSSH_KEYGEN */ + +static int test_BuildUserAuthRequestMlDsa(void) +{ +#ifndef WOLFSSH_NO_MLDSA44 + WOLFSSH_CTX* ctx = NULL; + WOLFSSH* ssh = NULL; + WS_KeySignature keySig; + WS_UserAuthData authData; + byte output[MLDSA_MAX_SIG_SIZE + 256]; + word32 idx = 0; + word32 idx0 = 0; + word32 idxBefore = 0; + int sigSz; + int result = 0; + int ret; + + ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL); + if (ctx == NULL) { result = -700; goto done; } + ssh = wolfSSH_new(ctx); + if (ssh == NULL) { result = -701; goto done; } + + ssh->sessionIdSz = 16; + WMEMSET(ssh->sessionId, 0xA5, ssh->sessionIdSz); + + WMEMSET(&keySig, 0, sizeof(keySig)); + keySig.keyId = ID_MLDSA44; + keySig.heap = NULL; + ret = wc_MlDsaKey_Init(&keySig.ks.mldsa.key, NULL, INVALID_DEVID); + if (ret != 0) { result = -702; goto done; } + { + word32 kidx = 0; + ret = wc_MlDsaKey_PrivateKeyDecode(&keySig.ks.mldsa.key, + unitTestMlDsaPrivKey, + (word32)sizeof(unitTestMlDsaPrivKey), &kidx); + } + if (ret != 0) { + wc_MlDsaKey_Free(&keySig.ks.mldsa.key); + result = -703; + goto done; + } + sigSz = wc_MlDsaKey_SigSize(&keySig.ks.mldsa.key); + if (sigSz < 0) { + wc_MlDsaKey_Free(&keySig.ks.mldsa.key); + result = -704; + goto done; + } + keySig.sigSz = (word32)sigSz; + + WMEMSET(&authData, 0, sizeof(authData)); + authData.sf.publicKey.hasSignature = 1; + authData.sf.publicKey.publicKeyType = (const byte*)"ssh-mldsa-44"; + authData.sf.publicKey.publicKeyTypeSz = 12; + + /* Write minimal auth-request header so (begin - sigStartIdx) > 0. */ + { + static const char testUser[] = "user"; + static const char testSvc[] = "ssh-connection"; + static const char testMethod[] = "publickey"; + output[idx++] = MSGID_USERAUTH_REQUEST; + MlDsaTest_PutLen(output + idx, (word32)WSTRLEN(testUser)); + idx += UINT32_SZ; + WMEMCPY(output + idx, testUser, WSTRLEN(testUser)); + idx += (word32)WSTRLEN(testUser); + MlDsaTest_PutLen(output + idx, (word32)WSTRLEN(testSvc)); + idx += UINT32_SZ; + WMEMCPY(output + idx, testSvc, WSTRLEN(testSvc)); + idx += (word32)WSTRLEN(testSvc); + MlDsaTest_PutLen(output + idx, (word32)WSTRLEN(testMethod)); + idx += UINT32_SZ; + WMEMCPY(output + idx, testMethod, WSTRLEN(testMethod)); + idx += (word32)WSTRLEN(testMethod); + } + + idxBefore = idx; + ret = wolfSSH_TestBuildUserAuthRequestMlDsa(ssh, output, &idx, + &authData, output, idx0, &keySig); + + wc_MlDsaKey_Free(&keySig.ks.mldsa.key); + + if (ret != WS_SUCCESS) { + printf("BuildUserAuthRequestMlDsa failed: %d\n", ret); + result = -705; + } + else { + /* 3 length fields + algo name + sig bytes */ + word32 nameSz = (word32)WSTRLEN("ssh-mldsa-44"); + word32 expAdv = 3 * UINT32_SZ + nameSz + (word32)sigSz; + if (idx - idxBefore != expAdv) { + printf("BuildUserAuthRequestMlDsa idx advance wrong:" + " got %d expected %d\n", + (int)(idx - idxBefore), (int)expAdv); + result = -706; + } + } + +done: + if (ssh) wolfSSH_free(ssh); + if (ctx) wolfSSH_CTX_free(ctx); + return result; +#else + return 0; +#endif +} +#endif + /* IdentifyAsn1Key unit test * * Exercises every new wc_Free* error-path added in IdentifyAsn1Key: @@ -2981,6 +4452,65 @@ static int test_IdentifyAsn1Key(void) } #endif +#if !defined(WOLFSSH_NO_MLDSA) && !defined(WOLFSSH_NO_MLDSA44) + ret = IdentifyAsn1Key(unitTestMlDsaPrivKey, + (word32)sizeof(unitTestMlDsaPrivKey), + 1, NULL, NULL); + if (ret != ID_MLDSA44) { + printf("IdentifyAsn1Key: MlDsa priv failed, ret=%d\n", ret); + result = -606; + goto done; + } + + /* Raw public key probe path: extract the public key from the private key + * test vector, then pass the raw bytes (no SPKI wrapper) to + * IdentifyAsn1Key. This exercises the level-probing fallback added for + * certificate-extracted public keys. */ + { + MlDsaKey mlKey; + byte* mlPub = NULL; + word32 mlPubSz = 0; + word32 mlIdx = 0; + + if (wc_MlDsaKey_Init(&mlKey, NULL, INVALID_DEVID) != 0) { + result = -607; goto done; + } + if (wc_MlDsaKey_PrivateKeyDecode(&mlKey, unitTestMlDsaPrivKey, + sizeof(unitTestMlDsaPrivKey), + &mlIdx) != 0) { + wc_MlDsaKey_Free(&mlKey); + result = -608; goto done; + } + { + int mlPubSzInt = wc_MlDsaKey_PubSize(&mlKey); + if (mlPubSzInt < 0) { + wc_MlDsaKey_Free(&mlKey); + result = -609; goto done; + } + mlPubSz = (word32)mlPubSzInt; + } + mlPub = (byte*)WMALLOC(mlPubSz, NULL, 0); + if (mlPub == NULL) { + wc_MlDsaKey_Free(&mlKey); + result = -619; goto done; + } + if (wc_MlDsaKey_ExportPubRaw(&mlKey, mlPub, &mlPubSz) != 0) { + WFREE(mlPub, NULL, 0); + wc_MlDsaKey_Free(&mlKey); + result = -610; goto done; + } + wc_MlDsaKey_Free(&mlKey); + + ret = IdentifyAsn1Key(mlPub, mlPubSz, 0, NULL, NULL); + WFREE(mlPub, NULL, 0); + if (ret != ID_MLDSA44) { + printf("IdentifyAsn1Key: MlDsa raw pub probe failed, ret=%d\n", + ret); + result = -611; goto done; + } + } +#endif + /* Unsupported ECC curve: triggers wc_ecc_free in the default: branch * (wolfSSL has P-192) or the else branch (wolfSSL lacks P-192). * Either way the key must be freed and WS_UNIMPLEMENTED_E returned. */ @@ -3319,8 +4849,8 @@ static int test_CertMan_NoPromoteNonCaIntermediate(void) * without it the intermediate (extKeyUsage==0) would be * wrongly demoted to non-CA. * "keyCertSign" the RFC 5280 conforming CA case (needs cert-ext support). - * "digitalSignature" KeyUsage present but lacking keyCertSign: an intermediate - * CA that must be demoted (the keyCertSign-rejection branch). + * "digitalSignature" has KeyUsage but lacks keyCertSign: intermediate + * CA must be demoted (keyCertSign-rejection branch). * * expectPromote==1 asserts the intermediate was promoted (verify returns * anything but WS_CERT_NO_SIGNER_E); ==0 asserts it was not (verify returns @@ -3329,7 +4859,8 @@ static int test_CertMan_NoPromoteNonCaIntermediate(void) * synthetic leaf is rejected later with WS_CERT_PROFILE_E, which is * orthogonal -- hence the "anything but WS_CERT_NO_SIGNER_E" success criterion * mirroring the negative test's sanity check. */ -static int certmanCheckIntermediate(const char* interKeyUsage, int expectPromote) +static int certmanCheckIntermediate(const char* interKeyUsage, + int expectPromote) { int result = 0; int ret; @@ -3353,7 +4884,8 @@ static int certmanCheckIntermediate(const char* interKeyUsage, int expectPromote printf("CertMan: can't load root cert\n"); result = -920; goto done; } - if (certmanLoadFile("./keys/ca-key-ecc.der", &rootKeyBuf, &rootKeySz) != 0) { + if (certmanLoadFile("./keys/ca-key-ecc.der", + &rootKeyBuf, &rootKeySz) != 0) { printf("CertMan: can't load root key\n"); result = -921; goto done; } @@ -4264,7 +5796,8 @@ static int test_ScpRecvCallback_SymlinkGuard(void) result = -858; goto cleanup; } - ret = snprintf(leakedPath, sizeof(leakedPath), "%s/leaked.txt", outsidePath); + ret = snprintf(leakedPath, sizeof(leakedPath), + "%s/leaked.txt", outsidePath); if (!scpTestSnprintfOk(ret, sizeof(leakedPath))) { result = -859; goto cleanup; @@ -5012,8 +6545,8 @@ static int test_SftpRecvSizeBoundAccept(void) return rc; if (ret != WS_FATAL_ERROR) return -955; - /* WS_WANT_READ confirms the bound admitted the body and the loop is waiting - * on it; WS_BUFFER_E here would mean a legitimate max WRITE was rejected. */ + /* WS_WANT_READ: body accepted, loop waiting; WS_BUFFER_E would mean a + * legitimate max WRITE was rejected. */ if (err != WS_WANT_READ) return -956; @@ -5276,6 +6809,28 @@ int wolfSSH_UnitTest(int argc, char** argv) printf("ParseEd25519PubKey: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; +#endif +#ifndef WOLFSSH_NO_MLDSA + unitResult = test_DoUserAuthRequestMlDsa(); + printf("DoUserAuthRequestMlDsa: %s (result=%d)\n", + (unitResult == 0 ? "SUCCESS" : "FAILED"), unitResult); + testResult = testResult || (unitResult != 0); +#ifdef WOLFSSH_KEYGEN + unitResult = test_PrepareUserAuthRequestMlDsa(); + printf("PrepareUserAuthRequestMlDsa: %s (result=%d)\n", + (unitResult == 0 ? "SUCCESS" : "FAILED"), unitResult); + testResult = testResult || (unitResult != 0); +#ifdef WOLFSSH_CERTS + unitResult = test_PrepareUserAuthRequestMlDsaCert(); + printf("PrepareUserAuthRequestMlDsaCert: %s (result=%d)\n", + (unitResult == 0 ? "SUCCESS" : "FAILED"), unitResult); + testResult = testResult || (unitResult != 0); +#endif /* WOLFSSH_CERTS */ +#endif /* WOLFSSH_KEYGEN */ + unitResult = test_BuildUserAuthRequestMlDsa(); + printf("BuildUserAuthRequestMlDsa: %s (result=%d)\n", + (unitResult == 0 ? "SUCCESS" : "FAILED"), unitResult); + testResult = testResult || (unitResult != 0); #endif unitResult = test_ChannelPutData(); printf("ChannelPutData: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); @@ -5343,7 +6898,8 @@ int wolfSSH_UnitTest(int argc, char** argv) #if defined(WOLFSSH_TEST_INTERNAL) && defined(WOLFSSH_SCP) && \ !defined(WOLFSSH_SCP_USER_CALLBACKS) unitResult = test_ScpExtractFileName(); - printf("ScpExtractFileName: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); + printf("ScpExtractFileName: %s\n", + (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; #endif @@ -5399,6 +6955,11 @@ int wolfSSH_UnitTest(int argc, char** argv) printf("Ed25519KeyGen: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); testResult = testResult || unitResult; #endif +#ifndef WOLFSSH_NO_MLDSA + unitResult = test_MlDsaKeyGen(); + printf("MlDsaKeyGen: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); + testResult = testResult || unitResult; +#endif #endif wolfSSH_Cleanup(); @@ -5406,7 +6967,6 @@ int wolfSSH_UnitTest(int argc, char** argv) return (testResult ? 1 : 0); } - #ifndef NO_UNITTEST_MAIN_DRIVER int main(int argc, char** argv) { diff --git a/wolfssh/error.h b/wolfssh/error.h index 307173af6..ff3a4b607 100644 --- a/wolfssh/error.h +++ b/wolfssh/error.h @@ -137,8 +137,9 @@ enum WS_ErrorCodes { WS_AUTH_PENDING = -1096, /* User authentication still pending */ WS_KDF_E = -1097, /* KDF error*/ WS_DISCONNECT = -1098, /* peer sent disconnect */ + WS_MLDSA_E = -1099, /* MLDSA failure */ - WS_LAST_E = WS_DISCONNECT /* Update this to indicate last error */ + WS_LAST_E = WS_MLDSA_E /* Last error indicator */ }; diff --git a/wolfssh/internal.h b/wolfssh/internal.h index 19fa55cd7..9325f606d 100644 --- a/wolfssh/internal.h +++ b/wolfssh/internal.h @@ -43,6 +43,9 @@ #ifndef WOLFSSL_WOLFSSH #error "wolfssh requires wolfSSL built with WOLFSSL_WOLFSSH" #endif +#ifdef HAVE_DILITHIUM + #include +#endif #ifdef WOLFSSH_SCP #include #endif @@ -102,6 +105,17 @@ extern "C" { #define WOLFSSH_NO_DH #endif +#ifndef HAVE_DILITHIUM + #undef WOLFSSH_NO_MLDSA + #define WOLFSSH_NO_MLDSA + #undef WOLFSSH_NO_MLDSA44 + #undef WOLFSSH_NO_MLDSA65 + #undef WOLFSSH_NO_MLDSA87 + #define WOLFSSH_NO_MLDSA44 + #define WOLFSSH_NO_MLDSA65 + #define WOLFSSH_NO_MLDSA87 +#endif + #ifdef NO_SHA #undef WOLFSSH_NO_SHA1 #define WOLFSSH_NO_SHA1 @@ -138,7 +152,6 @@ extern "C" { #error "You need at least one MAC algorithm." #endif - #if defined(WOLFSSH_NO_DH) || defined(WOLFSSH_NO_SHA1) #undef WOLFSSH_NO_DH_GROUP1_SHA1 #define WOLFSSH_NO_DH_GROUP1_SHA1 @@ -159,6 +172,16 @@ extern "C" { #undef WOLFSSH_NO_DH_GEX_SHA256 #define WOLFSSH_NO_DH_GEX_SHA256 #endif + +#ifdef WOLFSSH_NO_MLDSA + #undef WOLFSSH_NO_MLDSA44 + #undef WOLFSSH_NO_MLDSA65 + #undef WOLFSSH_NO_MLDSA87 + #define WOLFSSH_NO_MLDSA44 + #define WOLFSSH_NO_MLDSA65 + #define WOLFSSH_NO_MLDSA87 +#endif + #if defined(WOLFSSH_NO_ECDH) \ || defined(NO_SHA256) || defined(NO_ECC256) #undef WOLFSSH_NO_ECDH_SHA2_NISTP256 @@ -262,7 +285,10 @@ extern "C" { defined(WOLFSSH_NO_ECDSA_SHA2_NISTP256) && \ defined(WOLFSSH_NO_ECDSA_SHA2_NISTP384) && \ defined(WOLFSSH_NO_ECDSA_SHA2_NISTP521) && \ - defined(WOLFSSH_NO_ED25519) + defined(WOLFSSH_NO_ED25519) && \ + defined(WOLFSSH_NO_MLDSA44) && \ + defined(WOLFSSH_NO_MLDSA65) && \ + defined(WOLFSSH_NO_MLDSA87) #error "You need at least one signing algorithm." #endif @@ -373,10 +399,22 @@ enum { ID_ECDSA_SHA2_NISTP384, ID_ECDSA_SHA2_NISTP521, ID_ED25519, +/* ML-DSA enum values shift with configs. + * Internal-only, never serialized. */ +#ifndef WOLFSSH_NO_MLDSA + ID_MLDSA44, + ID_MLDSA65, + ID_MLDSA87, +#endif ID_X509V3_SSH_RSA, ID_X509V3_ECDSA_SHA2_NISTP256, ID_X509V3_ECDSA_SHA2_NISTP384, ID_X509V3_ECDSA_SHA2_NISTP521, +#ifndef WOLFSSH_NO_MLDSA + ID_X509V3_MLDSA44, + ID_X509V3_MLDSA65, + ID_X509V3_MLDSA87, +#endif /* Service IDs */ ID_SERVICE_USERAUTH, @@ -1078,6 +1116,11 @@ typedef struct WS_KeySignature { ed25519_key key; } ed25519; #endif +#ifndef WOLFSSH_NO_MLDSA + struct { + MlDsaKey key; + } mldsa; +#endif /* WOLFSSH_NO_MLDSA */ } ks; } WS_KeySignature; @@ -1463,6 +1506,21 @@ enum WS_MessageIdLimits { WOLFSSH_API int wolfSSH_TestDoUserAuthRequestEd25519(WOLFSSH* ssh, WS_UserAuthData* authData); #endif /* !WOLFSSH_NO_ED25519 */ +#ifndef WOLFSSH_NO_MLDSA + WOLFSSH_API int wolfSSH_TestDoUserAuthRequestMlDsa(WOLFSSH* ssh, + WS_UserAuthData* authData, word32 pubKeyBlobSz); + WOLFSSH_API int wolfSSH_TestPrepareUserAuthRequestMlDsa(WOLFSSH* ssh, + word32* payloadSz, const WS_UserAuthData* authData, + WS_KeySignature* keySig); +#ifdef WOLFSSH_CERTS + WOLFSSH_API int wolfSSH_TestPrepareUserAuthRequestMlDsaCert(WOLFSSH* ssh, + word32* payloadSz, const WS_UserAuthData* authData, + WS_KeySignature* keySig); +#endif /* WOLFSSH_CERTS */ + WOLFSSH_API int wolfSSH_TestBuildUserAuthRequestMlDsa(WOLFSSH* ssh, + byte* output, word32* idx, const WS_UserAuthData* authData, + const byte* sigStart, word32 sigStartIdx, WS_KeySignature* keySig); +#endif /* !WOLFSSH_NO_MLDSA */ #if defined(WOLFSSH_SCP) && !defined(WOLFSSH_SCP_USER_CALLBACKS) WOLFSSH_API int wolfSSH_TestScpExtractFileName(const char* filePath, char* fileName, word32 fileNameSz); diff --git a/wolfssh/keygen.h b/wolfssh/keygen.h index f760d8b19..9116194d1 100644 --- a/wolfssh/keygen.h +++ b/wolfssh/keygen.h @@ -42,12 +42,16 @@ extern "C" { #define WOLFSSH_ECDSAKEY_PRIME384 384 #define WOLFSSH_ECDSAKEY_PRIME521 521 #define WOLFSSH_ED25519KEY 256 +#define WOLFSSH_MLDSAKEY_44 44 +#define WOLFSSH_MLDSAKEY_65 65 +#define WOLFSSH_MLDSAKEY_87 87 WOLFSSH_API int wolfSSH_MakeRsaKey(byte* out, word32 outSz, word32 size, word32 e); WOLFSSH_API int wolfSSH_MakeEcdsaKey(byte* out, word32 outSz, word32 size); WOLFSSH_API int wolfSSH_MakeEd25519Key(byte* out, word32 outSz, word32 size); +WOLFSSH_API int wolfSSH_MakeMlDsaKey(byte* out, word32 outSz, word32 level); #ifdef __cplusplus