diff --git a/src/internal.c b/src/internal.c index a66226917..781cb0f1a 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 "MLDSA buffer error"; + case WS_AUTH_PENDING: return "userauth is still pending (callback would block)"; @@ -956,14 +971,45 @@ 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 */ 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 */ @@ -1428,7 +1474,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 @@ -1438,9 +1485,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 @@ -1450,7 +1510,7 @@ 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". @@ -1469,6 +1529,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) { @@ -1546,6 +1610,31 @@ 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 + ret = wc_MlDsaKey_PublicKeyDecode(&key->ks.mldsa.key, + in, inSz, &idx); + } + if (ret == 0 && 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; @@ -1794,6 +1883,42 @@ static int GetOpenSshKeyEd25519(ed25519_key* key, } #endif +#ifndef WOLFSSH_NO_MLDSA +/* utility for getting OpenSSH key in ML-DSA */ +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 @@ -1822,6 +1947,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) @@ -1869,6 +2025,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); @@ -1971,6 +2141,20 @@ 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, @@ -2017,7 +2201,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". @@ -2064,7 +2248,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. * @@ -2199,6 +2383,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); @@ -2845,7 +3044,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 @@ -3886,8 +4103,19 @@ static int GetNameList(byte* idList, word32* idListSz, return ret; } +/* ML-DSA variants are listed before ED25519 to prioritize post-quantum + * algorithms during key algorithm negotiation. */ 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 @@ -3903,6 +4131,18 @@ 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_ED25519 + ID_ED25519, +#endif #ifndef WOLFSSH_NO_ECDSA_SHA2_NISTP521 ID_ECDSA_SHA2_NISTP521, #endif @@ -3923,9 +4163,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); @@ -4128,6 +4365,32 @@ enum wc_HashType HashForId(byte id) case ID_ED25519: return WC_HASH_TYPE_SHA512; #endif +#ifndef WOLFSSH_NO_MLDSA44 + case ID_MLDSA44: + return WC_HASH_TYPE_NONE; +#endif +#ifndef WOLFSSH_NO_MLDSA65 + case ID_MLDSA65: + return WC_HASH_TYPE_NONE; +#endif +#ifndef WOLFSSH_NO_MLDSA87 + case ID_MLDSA87: + return WC_HASH_TYPE_NONE; +#endif +#ifdef WOLFSSH_CERTS +#ifndef WOLFSSH_NO_MLDSA44 + case ID_X509V3_MLDSA44: + return WC_HASH_TYPE_NONE; +#endif +#ifndef WOLFSSH_NO_MLDSA65 + case ID_X509V3_MLDSA65: + return WC_HASH_TYPE_NONE; +#endif +#ifndef WOLFSSH_NO_MLDSA87 + case ID_X509V3_MLDSA87: + return WC_HASH_TYPE_NONE; +#endif +#endif /* WOLFSSH_CERTS */ /* SHA2-384 */ #ifndef WOLFSSH_NO_ECDH_SHA2_NISTP384 case ID_ECDH_SHA2_NISTP384: @@ -4983,6 +5246,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; @@ -4997,6 +5261,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; @@ -5166,6 +5435,63 @@ 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; + + switch(keyId) { + case ID_MLDSA44: + level = WC_ML_DSA_44; + break; + case ID_MLDSA65: + level = WC_ML_DSA_65; + break; + case ID_MLDSA87: + level = WC_ML_DSA_87; + break; + default: + return WS_INVALID_ALGO_ID; + } + + 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, @@ -5398,6 +5724,56 @@ static int ParseRSAPubKeyCert(WOLFSSH *ssh, return ret; } + +#ifndef WOLFSSH_NO_MLDSA +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; + + switch(keyId) { + case ID_X509V3_MLDSA44: + level = WC_ML_DSA_44; + break; + case ID_X509V3_MLDSA65: + level = WC_ML_DSA_65; + break; + case ID_X509V3_MLDSA87: + level = WC_ML_DSA_87; + break; + default: + return WS_INVALID_ALGO_ID; + } + + 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 */ @@ -5444,6 +5820,23 @@ 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; } @@ -5470,6 +5863,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; } } @@ -6280,6 +6678,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; @@ -7944,67 +8355,103 @@ 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 +static int DoUserAuthRequestMlDsa(WOLFSSH* ssh, + WS_UserAuthData_PublicKey* pk, WS_UserAuthData* authData, byte level, + byte isCert, word32 pubKeyBlobSz) { const byte* publicKeyType; - byte temp[32]; word32 publicKeyTypeSz = 0; - word32 sz, qSz; + byte* pubRaw = NULL; + 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 + 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; + int wcRet = wc_MlDsaKey_Init(key_ptr, ssh->ctx->heap, INVALID_DEVID); + if (wcRet == 0) { + 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"); + if (isCert) { +#ifdef WOLFSSH_CERTS + /* For X.509, extract public key DER and decode it directly. */ + DecodedCert cert; + word32 pubKeySz = 0; + word32 idx = 0; + + wc_InitDecodedCert(&cert, pk->publicKey, pk->publicKeySz, + ssh->ctx->heap); + ret = wc_ParseCert(&cert, CERT_TYPE, 0, NULL); + if (ret == 0) { + ret = wc_GetPubKeyDerFromCert(&cert, NULL, &pubKeySz); + if (ret == 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) { + pubRawSz = pubKeySz; + ret = wc_MlDsaKey_PublicKeyDecode(key_ptr, pubRaw, + pubRawSz, &idx); + } + } + } + } + wc_FreeDecodedCert(&cert); +#else ret = WS_INVALID_ALGO_ID; +#endif } - } - if (ret == WS_SUCCESS) { - ret = GetSize(&qSz, pk->publicKey, pk->publicKeySz, &i); - } + else { + /* First check that the public key's type matches the one we are expecting. */ + ret = GetSize(&publicKeyTypeSz, pk->publicKey, pk->publicKeySz, &i); - if (ret == WS_SUCCESS) { - ret = wc_ed25519_import_public(pk->publicKey + i, qSz, key_ptr); + 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; + } + } + + /* Import public key */ + if (ret == WS_SUCCESS) { + const byte* pubRawRef = NULL; + ret = GetStringRef(&pubRawSz, &pubRawRef, pk->publicKey, pk->publicKeySz, &i); + if (ret == WS_SUCCESS) { + ret = wc_MlDsaKey_ImportPubRaw(key_ptr, pubRawRef, pubRawSz); + } + } + } } if (ret != 0) { @@ -8012,10 +8459,9 @@ static int DoUserAuthRequestEd25519(WOLFSSH* ssh, ret = WS_CRYPTO_FAILED; } + /* Verify signature */ if (ret == WS_SUCCESS) { i = 0; - /* First check that the signature's public key type matches the one - * we are expecting. */ ret = GetSize(&publicKeyTypeSz, pk->signature, pk->signatureSz, &i); } @@ -8034,19 +8480,167 @@ static int DoUserAuthRequestEd25519(WOLFSSH* ssh, } if (ret == WS_SUCCESS) { - /* Get the size of the signature blob. */ - ret = GetSize(&sz, pk->signature, pk->signatureSz, &i); + ret = GetSize(&sigSz, pk->signature, pk->signatureSz, &i); } if (ret == WS_SUCCESS) { - ret = wc_ed25519_verify_msg_init(pk->signature + i, sz, - key_ptr, (byte)Ed25519, NULL, 0); + 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) { - c32toa(ssh->sessionIdSz, temp); - ret = wc_ed25519_verify_msg_update(temp, UINT32_SZ, key_ptr); - } + 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 (pubRaw != NULL) { + WFREE(pubRaw, ssh->ctx->heap, DYNTYPE_PUBKEY); + } + + if (key_ptr != NULL) { + 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) { + ret = GetSize(&qSz, pk->publicKey, pk->publicKeySz, &i); + } + + if (ret == WS_SUCCESS) { + ret = wc_ed25519_import_public(pk->publicKey + i, qSz, key_ptr); + } + + if (ret != 0) { + WLOG(WS_LOG_DEBUG, "Could not decode public key"); + ret = WS_CRYPTO_FAILED; + } + + if (ret == WS_SUCCESS) { + i = 0; + /* First check that the signature's public key type matches the one + * we are expecting. */ + ret = GetSize(&publicKeyTypeSz, pk->signature, pk->signatureSz, &i); + } + + if (ret == WS_SUCCESS) { + publicKeyType = pk->signature + i; + i += publicKeyTypeSz; + + 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) { + /* Get the size of the signature blob. */ + ret = GetSize(&sz, pk->signature, pk->signatureSz, &i); + } + + if (ret == WS_SUCCESS) { + ret = wc_ed25519_verify_msg_init(pk->signature + i, sz, + key_ptr, (byte)Ed25519, NULL, 0); + } + + if (ret == WS_SUCCESS) { + c32toa(ssh->sessionIdSz, temp); + ret = wc_ed25519_verify_msg_update(temp, UINT32_SZ, key_ptr); + } if (ret == WS_SUCCESS) { ret = wc_ed25519_verify_msg_update(ssh->sessionId, ssh->sessionIdSz, @@ -8094,7 +8688,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) @@ -8201,7 +8796,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; @@ -8314,7 +8919,24 @@ 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) { + byte level = (pkTypeId == ID_MLDSA44) ? WC_ML_DSA_44 : + (pkTypeId == ID_MLDSA65) ? WC_ML_DSA_65 : WC_ML_DSA_87; + ret = DoUserAuthRequestMlDsa(ssh, &authData->sf.publicKey, authData, level, 0, pubKeyBlobSz); + } + else if (pkTypeId == ID_X509V3_MLDSA44 || + pkTypeId == ID_X509V3_MLDSA65 || + pkTypeId == ID_X509V3_MLDSA87) { + byte level = (pkTypeId == ID_X509V3_MLDSA44) ? WC_ML_DSA_44 : + (pkTypeId == ID_X509V3_MLDSA65) ? WC_ML_DSA_65 : WC_ML_DSA_87; + ret = DoUserAuthRequestMlDsa(ssh, &authData->sf.publicKey, authData, level, 1, pubKeyBlobSz); + } +#endif + else { wc_HashAlg hash; byte digest[WC_MAX_DIGEST_SIZE]; word32 digestSz = 0; @@ -8449,7 +9071,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 || !WOLFSSH_NO_ECDSA || !WOLFSSH_NO_ED25519 || !WOLFSSH_NO_MLDSA */ static int DoUserAuthRequest(WOLFSSH* ssh, @@ -8504,7 +9126,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); @@ -11449,7 +12072,7 @@ struct wolfSSH_sigKeyBlockFull { const char *primeName; word32 primeNameSz; } ecc; - +#endif #ifndef WOLFSSH_NO_ED25519 struct { ed25519_key key; @@ -11461,6 +12084,12 @@ struct wolfSSH_sigKeyBlockFull { byte qPad; } ed; #endif +#ifndef WOLFSSH_NO_MLDSA + struct { + MlDsaKey key; + byte q[WC_MLDSA_87_PUB_KEY_SIZE]; + word32 qSz; + } mldsa; #endif } sk; }; @@ -11478,7 +12107,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 @@ -11524,6 +12157,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; } @@ -11894,6 +12543,95 @@ 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"); + + if (sigKeyBlock_ptr->pubKeyId == ID_MLDSA44 + #ifdef WOLFSSH_CERTS + || sigKeyBlock_ptr->pubKeyId == ID_X509V3_MLDSA44 + #endif + ) + level = WC_ML_DSA_44; + else if (sigKeyBlock_ptr->pubKeyId == ID_MLDSA65 + #ifdef WOLFSSH_CERTS + || sigKeyBlock_ptr->pubKeyId == ID_X509V3_MLDSA65 + #endif + ) + level = WC_ML_DSA_65; + else + level = WC_ML_DSA_87; + + 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; } @@ -12037,6 +12775,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 @@ -12888,6 +13641,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) { @@ -12911,6 +13682,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; } @@ -13205,6 +13986,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) { @@ -13220,7 +14014,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; } @@ -13243,7 +14043,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. */ } @@ -13319,11 +14125,33 @@ int SendKexDhReply(WOLFSSH* ssh) } break; +#ifndef WOLFSSH_NO_MLDSA + case ID_MLDSA44: + case ID_MLDSA65: + case ID_MLDSA87: + { +#if !defined(WOLFSSH_NO_MLDSA) + /* Copy the mldsaKeyBlock into the buffer. */ + 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; +#endif + } + 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, @@ -15702,8 +16530,189 @@ static int BuildUserAuthRequestEd25519(WOLFSSH* ssh, #endif /* WOLFSSH_NO_ED25519 */ +#ifndef WOLFSSH_NO_MLDSA +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; + /* GetOpenSshKey → GetOpenSshKeyMlDsa handles Init + SetParams + * internally; no pre-init is needed here. */ + ret = GetOpenSshKey(keySig, + authData->sf.publicKey.privateKey, + authData->sf.publicKey.privateKeySz, &idx); + } + + 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 + ret = sigSz; + } + } + + WLOG(WS_LOG_DEBUG, + "Leaving PrepareUserAuthRequestMlDsa(), ret = %d", ret); + return ret; +} + +#ifdef WOLFSSH_CERTS +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) { + byte level; + if (keySig->keyId == ID_X509V3_MLDSA44) level = WC_ML_DSA_44; + else if (keySig->keyId == ID_X509V3_MLDSA65) level = WC_ML_DSA_65; + else level = WC_ML_DSA_87; + + ret = wc_MlDsaKey_Init(&keySig->ks.mldsa.key, keySig->heap, INVALID_DEVID); + if (ret == 0) + ret = wc_MlDsaKey_SetParams(&keySig->ks.mldsa.key, level); + } + + if (ret == WS_SUCCESS) { + word32 idx = 0; + ret = wc_MlDsaKey_PrivateKeyDecode(&keySig->ks.mldsa.key, + authData->sf.publicKey.privateKey, + authData->sf.publicKey.privateKeySz, &idx); + } + + 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 + ret = sigSz; + } + } + + WLOG(WS_LOG_DEBUG, "Leaving PrepareUserAuthRequestMlDsaCert(), ret = %d", ret); + return ret; +} +#endif /* WOLFSSH_CERTS */ + +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) { @@ -15795,6 +16804,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; } @@ -15918,6 +16943,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; } @@ -15936,7 +16999,7 @@ static int BuildUserAuthRequestPublicKey(WOLFSSH* ssh, } -#endif /* !WOLFSSH_NO_RSA || !WOLFSSH_NO_ECDSA || !WOLFSSH_NO_ED25519 */ +#endif /* !WOLFSSH_NO_RSA || !WOLFSSH_NO_ECDSA || !WOLFSSH_NO_ED25519 || !WOLFSSH_NO_MLDSA */ #ifdef WOLFSSH_KEYBOARD_INTERACTIVE int SendUserAuthKeyboardResponse(WOLFSSH* ssh) @@ -18313,4 +19376,45 @@ int wolfSSH_TestDoUserAuthRequestEd25519(WOLFSSH* ssh, #endif /* !WOLFSSH_NO_ED25519 */ +#ifndef WOLFSSH_NO_MLDSA + +int wolfSSH_TestDoUserAuthRequestMlDsa(WOLFSSH* ssh, + WS_UserAuthData* authData, word32 pubKeyBlobSz) +{ + byte keyId; + byte level = WC_ML_DSA_44; + byte isCert = 0; + + if (authData == NULL) { + return WS_BAD_ARGUMENT; + } + + keyId = NameToId((const char*)authData->sf.publicKey.publicKeyType, + authData->sf.publicKey.publicKeyTypeSz); + + if (keyId == ID_MLDSA44) { + level = WC_ML_DSA_44; + } else if (keyId == ID_MLDSA65) { + level = WC_ML_DSA_65; + } else if (keyId == ID_MLDSA87) { + level = WC_ML_DSA_87; + } else if (keyId == ID_X509V3_MLDSA44) { + level = WC_ML_DSA_44; + isCert = 1; + } else if (keyId == ID_X509V3_MLDSA65) { + level = WC_ML_DSA_65; + isCert = 1; + } else if (keyId == ID_X509V3_MLDSA87) { + level = WC_ML_DSA_87; + isCert = 1; + } else { + return WS_INVALID_ALGO_ID; + } + + return DoUserAuthRequestMlDsa(ssh, &authData->sf.publicKey, authData, level, isCert, + pubKeyBlobSz); +} + +#endif /* !WOLFSSH_NO_MLDSA */ + #endif /* WOLFSSH_TEST_INTERNAL */ diff --git a/src/keygen.c b/src/keygen.c index 4baee3ace..a5aa4a754 100644 --- a/src/keygen.c +++ b/src/keygen.c @@ -253,6 +253,80 @@ int wolfSSH_MakeEd25519Key(byte* out, word32 outSz, word32 size) #endif } +int wolfSSH_MakeMlDsaKey(byte* out, word32 outSz, word32 size) +{ +#if !defined(WOLFSSH_NO_MLDSA) + int ret = WS_SUCCESS; + WC_RNG rng = {0}; + byte level; + + WLOG(WS_LOG_DEBUG, "Entering wolfSSH_MakeMlDsaKey()"); + + if (size == WOLFSSH_MLDSAKEY_44) level = WC_ML_DSA_44; + else if (size == WOLFSSH_MLDSAKEY_65) level = WC_ML_DSA_65; + else if (size == WOLFSSH_MLDSAKEY_87) level = WC_ML_DSA_87; + else { + WLOG(WS_LOG_DEBUG, "Invalid ML-DSA key size 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, 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); + } + + /* A wc_FreeRNG failure is not fed into ret to preserve valid keys */ + if (wc_FreeRng(&rng) != 0) + WLOG(WS_LOG_DEBUG, "Couldn't free RNG"); + } + + WLOG(WS_LOG_DEBUG, "Leaving wolfSSH_MakeMlDsaKey(), ret = %d", ret); + return ret; +#else + WOLFSSH_UNUSED(out); + WOLFSSH_UNUSED(outSz); + WOLFSSH_UNUSED(size); + 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/tests/unit.c b/tests/unit.c index 5d1acdcd9..a8998ea14 100644 --- a/tests/unit.c +++ b/tests/unit.c @@ -703,6 +703,53 @@ 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_PRV_KEY_DER_SIZE, "44" }, + #endif + #ifndef WOLFSSH_NO_MLDSA65 + { WOLFSSH_MLDSAKEY_65, WC_MLDSA_65_PRV_KEY_DER_SIZE, "65" }, + #endif + #ifndef WOLFSSH_NO_MLDSA87 + { WOLFSSH_MLDSAKEY_87, WC_MLDSA_87_PRV_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); + WFREE(der, NULL, DYNTYPE_BUFFER); + + if (sz < 0) { + printf("MlDsaKeyGen: MakeMlDsaKey level %s failed (%d)\n", + params[i].name, sz); + result = -106; + break; + } + } + + return result; +} +#endif + #endif @@ -2270,6 +2317,177 @@ static const byte unitTestEd25519PrivKey[] = { }; #endif /* !WOLFSSH_NO_ED25519 */ +#if !defined(WOLFSSH_NO_MLDSA) +/* keys/server-key-mldsa.der - MlDsa OneAsymmetricKey (RFC 8410) */ +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, @@ -2576,6 +2794,668 @@ static int test_DoUserAuthRequestEd25519(void) #endif /* Ed25519 verify test guards */ +#ifndef WOLFSSH_NO_MLDSA +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; + word32 sigSz; + 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; } + + sigSz = wc_MlDsaKey_SigSize(&signingKey); + sig = (byte*)WMALLOC(sigSz, NULL, 0); + if (sig == NULL) { result = -706; goto done; } + + /* Get raw public key to build pubKeyBlob */ + pubRawSz = wc_MlDsaKey_PubSize(&signingKey); + pubRaw = (byte*)WMALLOC(pubRawSz, NULL, 0); + if (pubRaw == NULL) { result = -707; 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 the dataToSign region: username || service || authmethod || hasSig=1 || pkAlgo || pkBlob. */ + 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: sessionIdSz || sessionId || MSGID_USERAUTH_REQUEST || 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); + + /* 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); + 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; + 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.countryNm, "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; the pubkey blob length field uses the 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. */ + sigSz = wc_MlDsaKey_SigSize(&signingKey); + sig = (byte*)WMALLOC(sigSz, NULL, 0); + if (sig == NULL) { result = -861; 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); + + /* 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 contains an ML-DSA key at a different level than + * what keyTypeName claims. wc_MlDsaKey_PublicKeyDecode must fail, mapping to + * WS_CRYPTO_FAILED, exercising the error branch after wc_GetPubKeyDerFromCert + * succeeds. */ +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)(sizeof(keyTypeName) - 1); + 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.countryNm, "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_CERTS && WOLFSSL_CERT_GEN */ +#endif /* WOLFSSH_CERTS */ + +static int test_DoUserAuthRequestMlDsa(void) +{ + int ret; + ret = test_DoUserAuthRequestMlDsa_Params("ssh-mldsa-44", WC_ML_DSA_44); + if (ret != 0) return ret; + ret = test_DoUserAuthRequestMlDsa_Params("ssh-mldsa-65", WC_ML_DSA_65); + if (ret != 0) return ret; + ret = test_DoUserAuthRequestMlDsa_Params("ssh-mldsa-87", WC_ML_DSA_87); + if (ret != 0) return ret; + ret = test_DoUserAuthRequestMlDsa_BadAlgo(); + if (ret != 0) return ret; +#ifdef WOLFSSH_CERTS + ret = test_DoUserAuthRequestMlDsa_CertPath("x509v3-ssh-mldsa-44"); + if (ret != 0) return ret; + ret = test_DoUserAuthRequestMlDsa_CertPath("x509v3-ssh-mldsa-65"); + if (ret != 0) return ret; + ret = test_DoUserAuthRequestMlDsa_CertPath("x509v3-ssh-mldsa-87"); + if (ret != 0) return ret; +#ifdef WOLFSSL_CERT_GEN + ret = test_DoUserAuthRequestMlDsa_CertPath_Valid( + "x509v3-ssh-mldsa-44", WC_ML_DSA_44, CTC_ML_DSA_44); + if (ret != 0) return ret; + ret = test_DoUserAuthRequestMlDsa_CertPath_Valid( + "x509v3-ssh-mldsa-65", WC_ML_DSA_65, CTC_ML_DSA_65); + if (ret != 0) return ret; + ret = test_DoUserAuthRequestMlDsa_CertPath_Valid( + "x509v3-ssh-mldsa-87", WC_ML_DSA_87, CTC_ML_DSA_87); + if (ret != 0) return ret; + ret = test_DoUserAuthRequestMlDsa_CertPath_WrongLevel(); + if (ret != 0) return ret; +#endif /* WOLFSSL_CERT_GEN */ +#endif /* WOLFSSH_CERTS */ + return 0; +} +#endif + /* IdentifyAsn1Key unit test * * Exercises every new wc_Free* error-path added in IdentifyAsn1Key: @@ -2640,6 +3520,17 @@ static int test_IdentifyAsn1Key(void) } #endif +#if !defined(WOLFSSH_NO_MLDSA) + ret = IdentifyAsn1Key(unitTestMlDsaPrivKey, + (word32)sizeof(unitTestMlDsaPrivKey), + 1, NULL, NULL); + if (ret != ID_MLDSA44 && ret != ID_MLDSA65 && ret != ID_MLDSA87) { + printf("IdentifyAsn1Key: MlDsa priv failed, ret=%d\n", ret); + result = -604; + 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. */ @@ -3527,6 +4418,12 @@ int wolfSSH_UnitTest(int argc, char** argv) printf("DoUserAuthRequestEd25519: %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); #endif unitResult = test_ChannelPutData(); printf("ChannelPutData: %s\n", (unitResult == 0 ? "SUCCESS" : "FAILED")); @@ -3599,6 +4496,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(); @@ -3606,7 +4508,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..18be46471 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 /* Update this to indicate last error */ }; diff --git a/wolfssh/internal.h b/wolfssh/internal.h index 3898076fd..18455b9ac 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, +/* All three ML-DSA plain-key IDs are guarded by the single umbrella macro so + * disabling individual variants only affects dispatch tables, not enum values. */ +#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, @@ -1060,6 +1098,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; @@ -1407,6 +1450,10 @@ 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); +#endif /* !WOLFSSH_NO_MLDSA */ #endif /* WOLFSSH_TEST_INTERNAL */ /* dynamic memory types */ diff --git a/wolfssh/keygen.h b/wolfssh/keygen.h index f760d8b19..c39408404 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 size); #ifdef __cplusplus