3939#if (!defined(WOLFSSH_NO_ED25519 ) && defined(HAVE_ED25519 ) && \
4040 defined(HAVE_ED25519_MAKE_KEY ) && defined(HAVE_ED25519_KEY_EXPORT )) || \
4141 (!defined(WOLFSSH_NO_ECDSA ) && !defined(WOLFSSH_NO_RSA ) && \
42- defined(HAVE_ECC_KEY_EXPORT ))
42+ defined(HAVE_ECC_KEY_EXPORT )) || \
43+ (!defined(WOLFSSH_NO_MLDSA ) && defined(WOLFSSH_CERTS ) && \
44+ defined(WOLFSSL_CERT_GEN ))
4345 #include <wolfssl/wolfcrypt/asn_public.h>
4446#endif
4547#ifdef NO_FILESYSTEM
@@ -596,6 +598,15 @@ static THREAD_RETURN WOLFSSH_THREAD pubkey_server_thread(void* args)
596598 }
597599 }
598600
601+ #ifdef WOLFSSH_CERTS
602+ if (serverArgs -> hostCertBuf != NULL &&
603+ wolfSSH_CTX_UseCert_buffer (ctx , serverArgs -> hostCertBuf ,
604+ serverArgs -> hostCertBufSz , WOLFSSH_FORMAT_ASN1 ) < 0 ) {
605+ serverArgs -> return_code = WS_BAD_FILE_E ;
606+ goto cleanup ;
607+ }
608+ #endif /* WOLFSSH_CERTS */
609+
599610 clientFd = accept (listenFd , (struct sockaddr * )& clientAddr , & clientAddrSz );
600611 if (clientFd == WOLFSSH_SOCKET_INVALID ) {
601612 serverArgs -> return_code = WS_SOCKET_ERROR_E ;
@@ -638,10 +649,15 @@ static int AcceptAnyServerHostKey(const byte* pubKey, word32 pubKeySz,
638649 * WS_FATAL_ERROR for a reject test
639650 * hostKeyBuf - server host key DER; NULL uses the default fixture key
640651 * via load_key() (what every existing caller wants)
641- * hostKeyBufSz - size of hostKeyBuf; ignored when hostKeyBuf is NULL */
642- static int run_pubkey_test_ex (PubkeyServerCtx * sCtx , PubkeyClientCtx * cCtx ,
652+ * hostKeyBufSz - size of hostKeyBuf; ignored when hostKeyBuf is NULL
653+ * hostCertBuf - server host cert DER; NULL = none
654+ * rootCertBuf - client root CA for hostCertBuf
655+ * hostKeyAlgo - client host key algo list; NULL keeps the default */
656+ static int run_pubkey_test_host (PubkeyServerCtx * sCtx , PubkeyClientCtx * cCtx ,
643657 int expect , const byte * hostKeyBuf ,
644- word32 hostKeyBufSz )
658+ word32 hostKeyBufSz , const byte * hostCertBuf ,
659+ word32 hostCertBufSz , const byte * rootCertBuf ,
660+ word32 rootCertBufSz , const char * hostKeyAlgo )
645661{
646662 thread_args serverArgs ;
647663 tcp_ready ready ;
@@ -662,6 +678,8 @@ static int run_pubkey_test_ex(PubkeyServerCtx* sCtx, PubkeyClientCtx* cCtx,
662678 serverArgs .caCertSz = sCtx -> caCertSz ;
663679 serverArgs .hostKeyBuf = hostKeyBuf ;
664680 serverArgs .hostKeyBufSz = hostKeyBufSz ;
681+ serverArgs .hostCertBuf = hostCertBuf ;
682+ serverArgs .hostCertBufSz = hostCertBufSz ;
665683 InitTcpReady (serverArgs .signal );
666684
667685 ThreadStart (pubkey_server_thread , (void * )& serverArgs , & serThread );
@@ -671,6 +689,17 @@ static int run_pubkey_test_ex(PubkeyServerCtx* sCtx, PubkeyClientCtx* cCtx,
671689 AssertNotNull (clientCtx );
672690 wolfSSH_CTX_SetPublicKeyCheck (clientCtx , AcceptAnyServerHostKey );
673691 wolfSSH_SetUserAuth (clientCtx , clientPubkeyUserAuth );
692+ #ifdef WOLFSSH_CERTS
693+ if (rootCertBuf != NULL )
694+ AssertIntEQ (wolfSSH_CTX_AddRootCert_buffer (clientCtx , rootCertBuf ,
695+ rootCertBufSz , WOLFSSH_FORMAT_ASN1 ), WS_SUCCESS );
696+ #else
697+ (void )rootCertBuf ;
698+ (void )rootCertBufSz ;
699+ #endif
700+ if (hostKeyAlgo != NULL )
701+ AssertIntEQ (wolfSSH_CTX_SetAlgoListKey (clientCtx , hostKeyAlgo ),
702+ WS_SUCCESS );
674703
675704 clientSsh = wolfSSH_new (clientCtx );
676705 AssertNotNull (clientSsh );
@@ -708,6 +737,14 @@ static int run_pubkey_test_ex(PubkeyServerCtx* sCtx, PubkeyClientCtx* cCtx,
708737 return WS_SUCCESS ;
709738}
710739
740+ static int run_pubkey_test_ex (PubkeyServerCtx * sCtx , PubkeyClientCtx * cCtx ,
741+ int expect , const byte * hostKeyBuf ,
742+ word32 hostKeyBufSz )
743+ {
744+ return run_pubkey_test_host (sCtx , cCtx , expect , hostKeyBuf , hostKeyBufSz ,
745+ NULL , 0 , NULL , 0 , NULL );
746+ }
747+
711748/* Existing callers all want the default fixture host key, and every one of
712749 * them is an RSA or ECDSA test. */
713750#if !defined(WOLFSSH_NO_RSA ) || !defined(WOLFSSH_NO_ECDSA )
@@ -1470,6 +1507,37 @@ static void test_pubkey_load_mldsa_privonly_hostkey(void)
14701507}
14711508
14721509#if defined(WOLFSSH_HAVE_MLDSA_DERIVE_PUB ) && !defined(WOLFSSH_NO_ECDSA )
1510+ /* Authorize hansel's ECC key on the server and have the client present
1511+ * it. cCtx points into pubBuf and privBuf. */
1512+ static void load_hansel_ecc_client (PubkeyServerCtx * sCtx ,
1513+ PubkeyClientCtx * cCtx , byte * pubBuf , word32 pubBufSz ,
1514+ byte * privBuf , word32 privBufSz )
1515+ {
1516+ const byte * pubKeyType = NULL ;
1517+ word32 pubKeyTypeSz = 0 ;
1518+ const byte * privKeyType = NULL ;
1519+ word32 privKeyTypeSz = 0 ;
1520+
1521+ AssertIntEQ (wolfSSH_ReadKey_buffer ((const byte * )hanselPublicEcc ,
1522+ (word32 )WSTRLEN (hanselPublicEcc ), WOLFSSH_FORMAT_SSH ,
1523+ & pubBuf , & pubBufSz , & pubKeyType , & pubKeyTypeSz , NULL ),
1524+ WS_SUCCESS );
1525+
1526+ AssertIntEQ (wc_Sha256Hash (pubBuf , pubBufSz , sCtx -> hash ), 0 );
1527+
1528+ AssertIntEQ (wolfSSH_ReadKey_buffer (hanselPrivateEcc , hanselPrivateEccSz ,
1529+ WOLFSSH_FORMAT_ASN1 ,
1530+ & privBuf , & privBufSz , & privKeyType , & privKeyTypeSz , NULL ),
1531+ WS_SUCCESS );
1532+
1533+ cCtx -> publicKeyType = pubKeyType ;
1534+ cCtx -> publicKeyTypeSz = pubKeyTypeSz ;
1535+ cCtx -> publicKey = pubBuf ;
1536+ cCtx -> publicKeySz = pubBufSz ;
1537+ cCtx -> privateKey = privBuf ;
1538+ cCtx -> privateKeySz = privBufSz ;
1539+ }
1540+
14731541/* ML-DSA counterpart to test_pubkey_auth_ed25519_privonly_hostkey: a real
14741542 * handshake with a private-only host key, exercising the derived public
14751543 * key all the way through SendKexGetSigningKey. */
@@ -1478,39 +1546,16 @@ static void test_pubkey_auth_mldsa_privonly_hostkey(void)
14781546 PubkeyServerCtx sCtx = {0 };
14791547 PubkeyClientCtx cCtx ;
14801548 byte pubKeyBuf [512 ];
1481- byte * p = pubKeyBuf ;
1482- word32 pubKeySz = sizeof (pubKeyBuf );
1483- const byte * pubKeyType = NULL ;
1484- word32 pubKeyTypeSz = 0 ;
14851549 byte privKeyBuf [1300 ];
1486- byte * privKeyPtr = privKeyBuf ;
1487- word32 privKeySz = sizeof (privKeyBuf );
1488- const byte * privKeyType = NULL ;
1489- word32 privKeyTypeSz = 0 ;
14901550 byte * hostKeyDer ;
14911551 int hostKeyDerSz ;
14921552
14931553 printf ("Testing ML-DSA private-only host key at KEX (derived pubkey)\n" );
14941554
14951555 hostKeyDer = mldsa_privonly_hostkey_der (& hostKeyDerSz );
14961556
1497- AssertIntEQ (wolfSSH_ReadKey_buffer ((const byte * )hanselPublicEcc ,
1498- (word32 )WSTRLEN (hanselPublicEcc ), WOLFSSH_FORMAT_SSH ,
1499- & p , & pubKeySz , & pubKeyType , & pubKeyTypeSz , NULL ), WS_SUCCESS );
1500-
1501- AssertIntEQ (wc_Sha256Hash (pubKeyBuf , pubKeySz , sCtx .hash ), 0 );
1502-
1503- AssertIntEQ (wolfSSH_ReadKey_buffer (hanselPrivateEcc , hanselPrivateEccSz ,
1504- WOLFSSH_FORMAT_ASN1 ,
1505- & privKeyPtr , & privKeySz , & privKeyType , & privKeyTypeSz , NULL ),
1506- WS_SUCCESS );
1507-
1508- cCtx .publicKeyType = pubKeyType ;
1509- cCtx .publicKeyTypeSz = pubKeyTypeSz ;
1510- cCtx .publicKey = pubKeyBuf ;
1511- cCtx .publicKeySz = pubKeySz ;
1512- cCtx .privateKey = privKeyBuf ;
1513- cCtx .privateKeySz = privKeySz ;
1557+ load_hansel_ecc_client (& sCtx , & cCtx , pubKeyBuf , sizeof (pubKeyBuf ),
1558+ privKeyBuf , sizeof (privKeyBuf ));
15141559
15151560 run_pubkey_test_ex (& sCtx , & cCtx , WS_SUCCESS , hostKeyDer ,
15161561 (word32 )hostKeyDerSz );
@@ -1521,6 +1566,104 @@ static void test_pubkey_auth_mldsa_privonly_hostkey(void)
15211566}
15221567#endif /* WOLFSSH_HAVE_MLDSA_DERIVE_PUB && !WOLFSSH_NO_ECDSA */
15231568
1569+ /* A generated cert can't meet the FPKI profile the client enforces. */
1570+ #if defined(WOLFSSH_HAVE_MLDSA_DERIVE_PUB ) && !defined(WOLFSSH_NO_ECDSA ) && \
1571+ defined(WOLFSSH_CERTS ) && defined(WOLFSSL_CERT_GEN ) && \
1572+ defined(WOLFSSH_NO_FPKI )
1573+ /* Make an ML-DSA-44 cert for key, signed by signer (self-signed when
1574+ * issuerDer is NULL). Caller frees. */
1575+ static byte * mldsa44_cert_der (MlDsaKey * key , MlDsaKey * signer ,
1576+ const byte * issuerDer , int issuerDerSz , WC_RNG * rng , int * derSz )
1577+ {
1578+ Cert cert ;
1579+ byte * der ;
1580+ int sz ;
1581+
1582+ der = (byte * )WMALLOC (16384 , NULL , 0 );
1583+ AssertNotNull (der );
1584+ wc_InitCert (& cert );
1585+ WSTRNCPY (cert .subject .commonName , issuerDer == NULL ?
1586+ "wolfSSH-mldsa-ca" : "wolfSSH-mldsa-host" , CTC_NAME_SIZE - 1 );
1587+ WSTRNCPY (cert .subject .country , "US" , CTC_NAME_SIZE - 1 );
1588+ cert .daysValid = 365 ;
1589+ cert .sigType = CTC_ML_DSA_44 ;
1590+ if (issuerDer == NULL ) {
1591+ cert .selfSigned = 1 ;
1592+ cert .isCA = 1 ;
1593+ }
1594+ else {
1595+ AssertIntEQ (wc_SetIssuerBuffer (& cert , issuerDer , issuerDerSz ), 0 );
1596+ }
1597+ sz = wc_MakeCert_ex (& cert , der , 16384 , ML_DSA_44_TYPE , key , rng );
1598+ AssertIntGT (sz , 0 );
1599+ sz = wc_SignCert_ex (sz , CTC_ML_DSA_44 , der , 16384 , ML_DSA_44_TYPE ,
1600+ signer , rng );
1601+ AssertIntGT (sz , 0 );
1602+
1603+ * derSz = sz ;
1604+ return der ;
1605+ }
1606+
1607+ /* x509v3-ssh-mldsa-44 handshake with a private-only host key: the cert
1608+ * path signs without the cached public key and sends the certificate. */
1609+ static void test_pubkey_auth_mldsa_privonly_hostcert (void )
1610+ {
1611+ PubkeyServerCtx sCtx = {0 };
1612+ PubkeyClientCtx cCtx ;
1613+ byte pubKeyBuf [512 ];
1614+ byte privKeyBuf [1300 ];
1615+ MlDsaKey caKey ;
1616+ MlDsaKey hostKey ;
1617+ WC_RNG rng ;
1618+ byte * caDer ;
1619+ int caDerSz ;
1620+ byte * certDer ;
1621+ int certDerSz ;
1622+ byte * hostKeyDer ;
1623+ int hostKeyDerSz ;
1624+
1625+ printf ("Testing ML-DSA private-only host key with x509v3 cert\n" );
1626+
1627+ AssertIntEQ (wc_InitRng (& rng ), 0 );
1628+ WMEMSET (& caKey , 0 , sizeof (caKey ));
1629+ WMEMSET (& hostKey , 0 , sizeof (hostKey ));
1630+ AssertIntEQ (wc_MlDsaKey_Init (& caKey , NULL , INVALID_DEVID ), 0 );
1631+ AssertIntEQ (wc_MlDsaKey_SetParams (& caKey , WC_ML_DSA_44 ), 0 );
1632+ AssertIntEQ (wc_MlDsaKey_MakeKey (& caKey , & rng ), 0 );
1633+ AssertIntEQ (wc_MlDsaKey_Init (& hostKey , NULL , INVALID_DEVID ), 0 );
1634+ AssertIntEQ (wc_MlDsaKey_SetParams (& hostKey , WC_ML_DSA_44 ), 0 );
1635+ AssertIntEQ (wc_MlDsaKey_MakeKey (& hostKey , & rng ), 0 );
1636+
1637+ /* The client refuses a CA as the leaf, so issue the host cert. */
1638+ caDer = mldsa44_cert_der (& caKey , & caKey , NULL , 0 , & rng , & caDerSz );
1639+ certDer = mldsa44_cert_der (& hostKey , & caKey , caDer , caDerSz , & rng ,
1640+ & certDerSz );
1641+ wc_MlDsaKey_Free (& caKey );
1642+ wc_FreeRng (& rng );
1643+
1644+ hostKeyDer = (byte * )WMALLOC (WC_MLDSA_44_PRV_KEY_DER_SIZE , NULL , 0 );
1645+ AssertNotNull (hostKeyDer );
1646+ hostKeyDerSz = wc_MlDsaKey_PrivateKeyToDer (& hostKey , hostKeyDer ,
1647+ WC_MLDSA_44_PRV_KEY_DER_SIZE );
1648+ wc_MlDsaKey_Free (& hostKey );
1649+ AssertIntGT (hostKeyDerSz , 0 );
1650+
1651+ load_hansel_ecc_client (& sCtx , & cCtx , pubKeyBuf , sizeof (pubKeyBuf ),
1652+ privKeyBuf , sizeof (privKeyBuf ));
1653+
1654+ run_pubkey_test_host (& sCtx , & cCtx , WS_SUCCESS , hostKeyDer ,
1655+ (word32 )hostKeyDerSz , certDer , (word32 )certDerSz ,
1656+ caDer , (word32 )caDerSz , "x509v3-ssh-mldsa-44" );
1657+
1658+ WMEMSET (privKeyBuf , 0 , sizeof (privKeyBuf ));
1659+ WMEMSET (hostKeyDer , 0 , hostKeyDerSz );
1660+ WFREE (hostKeyDer , NULL , 0 );
1661+ WFREE (certDer , NULL , 0 );
1662+ WFREE (caDer , NULL , 0 );
1663+ }
1664+ #endif /* WOLFSSH_HAVE_MLDSA_DERIVE_PUB && !WOLFSSH_NO_ECDSA &&
1665+ * WOLFSSH_CERTS && WOLFSSL_CERT_GEN && WOLFSSH_NO_FPKI */
1666+
15241667#if defined(WOLFSSH_HAVE_MLDSA_DERIVE_PUB ) && \
15251668 defined(WOLFSSL_MLDSA_PUBLIC_KEY ) && \
15261669 (!defined(WOLFSSH_NO_ECDSA ) || !defined(WOLFSSH_NO_RSA ))
@@ -2650,6 +2793,11 @@ int wolfSSH_AuthTest(int argc, char** argv)
26502793 #if defined(WOLFSSH_HAVE_MLDSA_DERIVE_PUB ) && !defined(WOLFSSH_NO_ECDSA )
26512794 test_pubkey_auth_mldsa_privonly_hostkey ();
26522795 #endif
2796+ #if defined(WOLFSSH_HAVE_MLDSA_DERIVE_PUB ) && \
2797+ !defined(WOLFSSH_NO_ECDSA ) && defined(WOLFSSH_CERTS ) && \
2798+ defined(WOLFSSL_CERT_GEN ) && defined(WOLFSSH_NO_FPKI )
2799+ test_pubkey_auth_mldsa_privonly_hostcert ();
2800+ #endif
26532801 #if defined(WOLFSSH_HAVE_MLDSA_DERIVE_PUB ) && \
26542802 defined(WOLFSSL_MLDSA_PUBLIC_KEY ) && \
26552803 (!defined(WOLFSSH_NO_ECDSA ) || !defined(WOLFSSH_NO_RSA ))
0 commit comments