3838#endif
3939#if (!defined(WOLFSSH_NO_ED25519 ) && defined(HAVE_ED25519 ) && \
4040 defined(HAVE_ED25519_MAKE_KEY ) && defined(HAVE_ED25519_KEY_EXPORT )) || \
41- (!defined(WOLFSSH_NO_ECDSA ) && defined(HAVE_ECC_KEY_EXPORT ))
41+ (!defined(WOLFSSH_NO_ECDSA ) && defined(HAVE_ECC_KEY_EXPORT )) || \
42+ (!defined(WOLFSSH_NO_MLDSA ) && defined(WOLFSSH_CERTS ) && \
43+ defined(WOLFSSL_CERT_GEN ))
4244 #include <wolfssl/wolfcrypt/asn_public.h>
4345#endif
4446#ifdef NO_FILESYSTEM
@@ -595,6 +597,15 @@ static THREAD_RETURN WOLFSSH_THREAD pubkey_server_thread(void* args)
595597 }
596598 }
597599
600+ #ifdef WOLFSSH_CERTS
601+ if (serverArgs -> hostCertBuf != NULL &&
602+ wolfSSH_CTX_UseCert_buffer (ctx , serverArgs -> hostCertBuf ,
603+ serverArgs -> hostCertBufSz , WOLFSSH_FORMAT_ASN1 ) < 0 ) {
604+ serverArgs -> return_code = WS_BAD_FILE_E ;
605+ goto cleanup ;
606+ }
607+ #endif /* WOLFSSH_CERTS */
608+
598609 clientFd = accept (listenFd , (struct sockaddr * )& clientAddr , & clientAddrSz );
599610 if (clientFd == WOLFSSH_SOCKET_INVALID ) {
600611 serverArgs -> return_code = WS_SOCKET_ERROR_E ;
@@ -637,10 +648,15 @@ static int AcceptAnyServerHostKey(const byte* pubKey, word32 pubKeySz,
637648 * WS_FATAL_ERROR for a reject test
638649 * hostKeyBuf - server host key DER; NULL uses the default fixture key
639650 * via load_key() (what every existing caller wants)
640- * hostKeyBufSz - size of hostKeyBuf; ignored when hostKeyBuf is NULL */
641- static int run_pubkey_test_ex (PubkeyServerCtx * sCtx , PubkeyClientCtx * cCtx ,
651+ * hostKeyBufSz - size of hostKeyBuf; ignored when hostKeyBuf is NULL
652+ * hostCertBuf - server host cert DER; NULL = none
653+ * rootCertBuf - client root CA for hostCertBuf
654+ * hostKeyAlgo - client host key algo list; NULL keeps the default */
655+ static int run_pubkey_test_host (PubkeyServerCtx * sCtx , PubkeyClientCtx * cCtx ,
642656 int expect , const byte * hostKeyBuf ,
643- word32 hostKeyBufSz )
657+ word32 hostKeyBufSz , const byte * hostCertBuf ,
658+ word32 hostCertBufSz , const byte * rootCertBuf ,
659+ word32 rootCertBufSz , const char * hostKeyAlgo )
644660{
645661 thread_args serverArgs ;
646662 tcp_ready ready ;
@@ -661,6 +677,8 @@ static int run_pubkey_test_ex(PubkeyServerCtx* sCtx, PubkeyClientCtx* cCtx,
661677 serverArgs .caCertSz = sCtx -> caCertSz ;
662678 serverArgs .hostKeyBuf = hostKeyBuf ;
663679 serverArgs .hostKeyBufSz = hostKeyBufSz ;
680+ serverArgs .hostCertBuf = hostCertBuf ;
681+ serverArgs .hostCertBufSz = hostCertBufSz ;
664682 InitTcpReady (serverArgs .signal );
665683
666684 ThreadStart (pubkey_server_thread , (void * )& serverArgs , & serThread );
@@ -670,6 +688,17 @@ static int run_pubkey_test_ex(PubkeyServerCtx* sCtx, PubkeyClientCtx* cCtx,
670688 AssertNotNull (clientCtx );
671689 wolfSSH_CTX_SetPublicKeyCheck (clientCtx , AcceptAnyServerHostKey );
672690 wolfSSH_SetUserAuth (clientCtx , clientPubkeyUserAuth );
691+ #ifdef WOLFSSH_CERTS
692+ if (rootCertBuf != NULL )
693+ AssertIntEQ (wolfSSH_CTX_AddRootCert_buffer (clientCtx , rootCertBuf ,
694+ rootCertBufSz , WOLFSSH_FORMAT_ASN1 ), WS_SUCCESS );
695+ #else
696+ (void )rootCertBuf ;
697+ (void )rootCertBufSz ;
698+ #endif
699+ if (hostKeyAlgo != NULL )
700+ AssertIntEQ (wolfSSH_CTX_SetAlgoListKey (clientCtx , hostKeyAlgo ),
701+ WS_SUCCESS );
673702
674703 clientSsh = wolfSSH_new (clientCtx );
675704 AssertNotNull (clientSsh );
@@ -707,6 +736,14 @@ static int run_pubkey_test_ex(PubkeyServerCtx* sCtx, PubkeyClientCtx* cCtx,
707736 return WS_SUCCESS ;
708737}
709738
739+ static int run_pubkey_test_ex (PubkeyServerCtx * sCtx , PubkeyClientCtx * cCtx ,
740+ int expect , const byte * hostKeyBuf ,
741+ word32 hostKeyBufSz )
742+ {
743+ return run_pubkey_test_host (sCtx , cCtx , expect , hostKeyBuf , hostKeyBufSz ,
744+ NULL , 0 , NULL , 0 , NULL );
745+ }
746+
710747/* Existing callers all want the default fixture host key, and every one of
711748 * them is an RSA or ECDSA test. */
712749#if !defined(WOLFSSH_NO_RSA ) || !defined(WOLFSSH_NO_ECDSA )
@@ -1478,6 +1515,37 @@ static void test_pubkey_load_mldsa_privonly_hostkey(void)
14781515}
14791516
14801517#if defined(WOLFSSH_HAVE_MLDSA_DERIVE_PUB ) && !defined(WOLFSSH_NO_ECDSA )
1518+ /* Authorize hansel's ECC key on the server and have the client present
1519+ * it. cCtx points into pubBuf and privBuf. */
1520+ static void load_hansel_ecc_client (PubkeyServerCtx * sCtx ,
1521+ PubkeyClientCtx * cCtx , byte * pubBuf , word32 pubBufSz ,
1522+ byte * privBuf , word32 privBufSz )
1523+ {
1524+ const byte * pubKeyType = NULL ;
1525+ word32 pubKeyTypeSz = 0 ;
1526+ const byte * privKeyType = NULL ;
1527+ word32 privKeyTypeSz = 0 ;
1528+
1529+ AssertIntEQ (wolfSSH_ReadKey_buffer ((const byte * )hanselPublicEcc ,
1530+ (word32 )WSTRLEN (hanselPublicEcc ), WOLFSSH_FORMAT_SSH ,
1531+ & pubBuf , & pubBufSz , & pubKeyType , & pubKeyTypeSz , NULL ),
1532+ WS_SUCCESS );
1533+
1534+ AssertIntEQ (wc_Sha256Hash (pubBuf , pubBufSz , sCtx -> hash ), 0 );
1535+
1536+ AssertIntEQ (wolfSSH_ReadKey_buffer (hanselPrivateEcc , hanselPrivateEccSz ,
1537+ WOLFSSH_FORMAT_ASN1 ,
1538+ & privBuf , & privBufSz , & privKeyType , & privKeyTypeSz , NULL ),
1539+ WS_SUCCESS );
1540+
1541+ cCtx -> publicKeyType = pubKeyType ;
1542+ cCtx -> publicKeyTypeSz = pubKeyTypeSz ;
1543+ cCtx -> publicKey = pubBuf ;
1544+ cCtx -> publicKeySz = pubBufSz ;
1545+ cCtx -> privateKey = privBuf ;
1546+ cCtx -> privateKeySz = privBufSz ;
1547+ }
1548+
14811549/* ML-DSA counterpart to test_pubkey_auth_ed25519_privonly_hostkey: a real
14821550 * handshake with a private-only host key, exercising the derived public
14831551 * key all the way through SendKexGetSigningKey. */
@@ -1486,39 +1554,16 @@ static void test_pubkey_auth_mldsa_privonly_hostkey(void)
14861554 PubkeyServerCtx sCtx = {0 };
14871555 PubkeyClientCtx cCtx ;
14881556 byte pubKeyBuf [512 ];
1489- byte * p = pubKeyBuf ;
1490- word32 pubKeySz = sizeof (pubKeyBuf );
1491- const byte * pubKeyType = NULL ;
1492- word32 pubKeyTypeSz = 0 ;
14931557 byte privKeyBuf [1300 ];
1494- byte * privKeyPtr = privKeyBuf ;
1495- word32 privKeySz = sizeof (privKeyBuf );
1496- const byte * privKeyType = NULL ;
1497- word32 privKeyTypeSz = 0 ;
14981558 byte * hostKeyDer ;
14991559 int hostKeyDerSz ;
15001560
15011561 printf ("Testing ML-DSA private-only host key at KEX (derived pubkey)\n" );
15021562
15031563 hostKeyDer = mldsa_privonly_hostkey_der (& hostKeyDerSz );
15041564
1505- AssertIntEQ (wolfSSH_ReadKey_buffer ((const byte * )hanselPublicEcc ,
1506- (word32 )WSTRLEN (hanselPublicEcc ), WOLFSSH_FORMAT_SSH ,
1507- & p , & pubKeySz , & pubKeyType , & pubKeyTypeSz , NULL ), WS_SUCCESS );
1508-
1509- AssertIntEQ (wc_Sha256Hash (pubKeyBuf , pubKeySz , sCtx .hash ), 0 );
1510-
1511- AssertIntEQ (wolfSSH_ReadKey_buffer (hanselPrivateEcc , hanselPrivateEccSz ,
1512- WOLFSSH_FORMAT_ASN1 ,
1513- & privKeyPtr , & privKeySz , & privKeyType , & privKeyTypeSz , NULL ),
1514- WS_SUCCESS );
1515-
1516- cCtx .publicKeyType = pubKeyType ;
1517- cCtx .publicKeyTypeSz = pubKeyTypeSz ;
1518- cCtx .publicKey = pubKeyBuf ;
1519- cCtx .publicKeySz = pubKeySz ;
1520- cCtx .privateKey = privKeyBuf ;
1521- cCtx .privateKeySz = privKeySz ;
1565+ load_hansel_ecc_client (& sCtx , & cCtx , pubKeyBuf , sizeof (pubKeyBuf ),
1566+ privKeyBuf , sizeof (privKeyBuf ));
15221567
15231568 run_pubkey_test_ex (& sCtx , & cCtx , WS_SUCCESS , hostKeyDer ,
15241569 (word32 )hostKeyDerSz );
@@ -1529,6 +1574,104 @@ static void test_pubkey_auth_mldsa_privonly_hostkey(void)
15291574}
15301575#endif /* WOLFSSH_HAVE_MLDSA_DERIVE_PUB && !WOLFSSH_NO_ECDSA */
15311576
1577+ /* A generated cert can't meet the FPKI profile the client enforces. */
1578+ #if defined(WOLFSSH_HAVE_MLDSA_DERIVE_PUB ) && !defined(WOLFSSH_NO_ECDSA ) && \
1579+ defined(WOLFSSH_CERTS ) && defined(WOLFSSL_CERT_GEN ) && \
1580+ defined(WOLFSSH_NO_FPKI )
1581+ /* Make an ML-DSA-44 cert for key, signed by signer (self-signed when
1582+ * issuerDer is NULL). Caller frees. */
1583+ static byte * mldsa44_cert_der (MlDsaKey * key , MlDsaKey * signer ,
1584+ const byte * issuerDer , int issuerDerSz , WC_RNG * rng , int * derSz )
1585+ {
1586+ Cert cert ;
1587+ byte * der ;
1588+ int sz ;
1589+
1590+ der = (byte * )WMALLOC (16384 , NULL , 0 );
1591+ AssertNotNull (der );
1592+ wc_InitCert (& cert );
1593+ WSTRNCPY (cert .subject .commonName , issuerDer == NULL ?
1594+ "wolfSSH-mldsa-ca" : "wolfSSH-mldsa-host" , CTC_NAME_SIZE - 1 );
1595+ WSTRNCPY (cert .subject .country , "US" , CTC_NAME_SIZE - 1 );
1596+ cert .daysValid = 365 ;
1597+ cert .sigType = CTC_ML_DSA_44 ;
1598+ if (issuerDer == NULL ) {
1599+ cert .selfSigned = 1 ;
1600+ cert .isCA = 1 ;
1601+ }
1602+ else {
1603+ AssertIntEQ (wc_SetIssuerBuffer (& cert , issuerDer , issuerDerSz ), 0 );
1604+ }
1605+ sz = wc_MakeCert_ex (& cert , der , 16384 , ML_DSA_44_TYPE , key , rng );
1606+ AssertIntGT (sz , 0 );
1607+ sz = wc_SignCert_ex (sz , CTC_ML_DSA_44 , der , 16384 , ML_DSA_44_TYPE ,
1608+ signer , rng );
1609+ AssertIntGT (sz , 0 );
1610+
1611+ * derSz = sz ;
1612+ return der ;
1613+ }
1614+
1615+ /* x509v3-ssh-mldsa-44 handshake with a private-only host key: the cert
1616+ * path signs without the cached public key and sends the certificate. */
1617+ static void test_pubkey_auth_mldsa_privonly_hostcert (void )
1618+ {
1619+ PubkeyServerCtx sCtx = {0 };
1620+ PubkeyClientCtx cCtx ;
1621+ byte pubKeyBuf [512 ];
1622+ byte privKeyBuf [1300 ];
1623+ MlDsaKey caKey ;
1624+ MlDsaKey hostKey ;
1625+ WC_RNG rng ;
1626+ byte * caDer ;
1627+ int caDerSz ;
1628+ byte * certDer ;
1629+ int certDerSz ;
1630+ byte * hostKeyDer ;
1631+ int hostKeyDerSz ;
1632+
1633+ printf ("Testing ML-DSA private-only host key with x509v3 cert\n" );
1634+
1635+ AssertIntEQ (wc_InitRng (& rng ), 0 );
1636+ WMEMSET (& caKey , 0 , sizeof (caKey ));
1637+ WMEMSET (& hostKey , 0 , sizeof (hostKey ));
1638+ AssertIntEQ (wc_MlDsaKey_Init (& caKey , NULL , INVALID_DEVID ), 0 );
1639+ AssertIntEQ (wc_MlDsaKey_SetParams (& caKey , WC_ML_DSA_44 ), 0 );
1640+ AssertIntEQ (wc_MlDsaKey_MakeKey (& caKey , & rng ), 0 );
1641+ AssertIntEQ (wc_MlDsaKey_Init (& hostKey , NULL , INVALID_DEVID ), 0 );
1642+ AssertIntEQ (wc_MlDsaKey_SetParams (& hostKey , WC_ML_DSA_44 ), 0 );
1643+ AssertIntEQ (wc_MlDsaKey_MakeKey (& hostKey , & rng ), 0 );
1644+
1645+ /* The client refuses a CA as the leaf, so issue the host cert. */
1646+ caDer = mldsa44_cert_der (& caKey , & caKey , NULL , 0 , & rng , & caDerSz );
1647+ certDer = mldsa44_cert_der (& hostKey , & caKey , caDer , caDerSz , & rng ,
1648+ & certDerSz );
1649+ wc_MlDsaKey_Free (& caKey );
1650+ wc_FreeRng (& rng );
1651+
1652+ hostKeyDer = (byte * )WMALLOC (WC_MLDSA_44_PRV_KEY_DER_SIZE , NULL , 0 );
1653+ AssertNotNull (hostKeyDer );
1654+ hostKeyDerSz = wc_MlDsaKey_PrivateKeyToDer (& hostKey , hostKeyDer ,
1655+ WC_MLDSA_44_PRV_KEY_DER_SIZE );
1656+ wc_MlDsaKey_Free (& hostKey );
1657+ AssertIntGT (hostKeyDerSz , 0 );
1658+
1659+ load_hansel_ecc_client (& sCtx , & cCtx , pubKeyBuf , sizeof (pubKeyBuf ),
1660+ privKeyBuf , sizeof (privKeyBuf ));
1661+
1662+ run_pubkey_test_host (& sCtx , & cCtx , WS_SUCCESS , hostKeyDer ,
1663+ (word32 )hostKeyDerSz , certDer , (word32 )certDerSz ,
1664+ caDer , (word32 )caDerSz , "x509v3-ssh-mldsa-44" );
1665+
1666+ WMEMSET (privKeyBuf , 0 , sizeof (privKeyBuf ));
1667+ WMEMSET (hostKeyDer , 0 , hostKeyDerSz );
1668+ WFREE (hostKeyDer , NULL , 0 );
1669+ WFREE (certDer , NULL , 0 );
1670+ WFREE (caDer , NULL , 0 );
1671+ }
1672+ #endif /* WOLFSSH_HAVE_MLDSA_DERIVE_PUB && !WOLFSSH_NO_ECDSA &&
1673+ * WOLFSSH_CERTS && WOLFSSL_CERT_GEN && WOLFSSH_NO_FPKI */
1674+
15321675#if defined(WOLFSSH_HAVE_MLDSA_DERIVE_PUB ) && \
15331676 defined(WOLFSSL_MLDSA_PUBLIC_KEY ) && \
15341677 (!defined(WOLFSSH_NO_ECDSA ) || !defined(WOLFSSH_NO_RSA ))
@@ -2660,6 +2803,11 @@ int wolfSSH_AuthTest(int argc, char** argv)
26602803 #if defined(WOLFSSH_HAVE_MLDSA_DERIVE_PUB ) && !defined(WOLFSSH_NO_ECDSA )
26612804 test_pubkey_auth_mldsa_privonly_hostkey ();
26622805 #endif
2806+ #if defined(WOLFSSH_HAVE_MLDSA_DERIVE_PUB ) && \
2807+ !defined(WOLFSSH_NO_ECDSA ) && defined(WOLFSSH_CERTS ) && \
2808+ defined(WOLFSSL_CERT_GEN ) && defined(WOLFSSH_NO_FPKI )
2809+ test_pubkey_auth_mldsa_privonly_hostcert ();
2810+ #endif
26632811 #if defined(WOLFSSH_HAVE_MLDSA_DERIVE_PUB ) && \
26642812 defined(WOLFSSL_MLDSA_PUBLIC_KEY ) && \
26652813 (!defined(WOLFSSH_NO_ECDSA ) || !defined(WOLFSSH_NO_RSA ))
0 commit comments