https://github.com/apache/tomcat/blob/main/java/org/apache/tomcat/util/openssl/openssl_h.java#L82
Maybe LibreSSL is loaded which doesn't support PQC? Dimitris On Fri, May 15, 2026 at 5:35 PM Mark Thomas <[email protected]> wrote: > On 15/05/2026 15:12, Dimitris Soumis wrote: > > PQC should be enabled by default. > > Agreed. And it appears to be. > > > dsoumis@192:~$ openssl list -signature-algorithms 2>&1 | grep -i mldsa > > { 2.16.840.1.101.3.4.3.17, id-ml-dsa-44, ML-DSA-44, MLDSA44 } @ > default > > { 2.16.840.1.101.3.4.3.18, id-ml-dsa-65, ML-DSA-65, MLDSA65 } @ > default > > { 2.16.840.1.101.3.4.3.19, id-ml-dsa-87, ML-DSA-87, MLDSA87 } @ > default > > That is what I see. > > > We should add an extra check for ML-DSA availability if that's the > > issue instead of just checking the version number. > > I will add this if you agree. > > I don't think that is the issue given I am seeing the same results as you. > > I am seeing slightly different behaviour with OpenSSL master. > > It is looking more like an environmental issue. I need to keep digging > to figure out what is going wrong. > > Until I figure out what is going wrong, I don't think it is worth adding > additional checks to the tests. > > Mark > > > > > > Dimitris > > > > > > On Fri, May 15, 2026 at 4:55 PM Mark Thomas <[email protected]> wrote: > > > >> I'm seeing lots of failures with 3.5.5 > >> > >> The root cause appears to be: > >> > >> 15-May-2026 12:59:58.147 SEVERE [main] > >> org.apache.tomcat.util.net.openssl.panama.OpenSSLContext.logLastError > >> Error loading certificate: [error:0A0000F7:SSL routines::unknown > >> certificate type] > >> > >> and similar variations. > >> > >> Does PQC need to be explicitly enabled in the OpenSSL build? > >> > >> Mark > >> > >> > >> On 15/05/2026 14:41, Dimitris Soumis wrote: > >>> OpenSSL-FFM tests did pass for me though with Openssl 3.5.4. Could you > >>> provide the failure logs if there are any pending or what version you > are > >>> using that triggers those failures? > >>> > >>> On Fri, May 15, 2026 at 4:16 PM Dimitris Soumis <[email protected]> > >> wrote: > >>> > >>>> Apologies for the noise. Indeed, it wasn't tested properly. Thanks for > >>>> fixing it. > >>>> > >>>> Dimitris > >>>> > >>>> On Fri, May 15, 2026 at 3:02 PM Mark Thomas <[email protected]> wrote: > >>>> > >>>>> On 13/05/2026 12:44, [email protected] wrote: > >>>>>> This is an automated email from the ASF dual-hosted git repository. > >>>>>> > >>>>>> dsoumis pushed a commit to branch main > >>>>>> in repository https://gitbox.apache.org/repos/asf/tomcat.git > >>>>>> > >>>>>> > >>>>>> The following commit(s) were added to refs/heads/main by this push: > >>>>>> new 7ff10fab8e Add unit tests for PQC features > >>>>>> 7ff10fab8e is described below > >>>>>> > >>>>>> commit 7ff10fab8ede061fe61524ef96b463fef637429f > >>>>>> Author: Dimitrios Soumis <[email protected]> > >>>>>> AuthorDate: Wed May 13 13:44:42 2026 +0200 > >>>>>> > >>>>>> Add unit tests for PQC features > >>>>> > >>>>> How well tested is this patch? And with which OpenSSL versions? > >>>>> > >>>>> The OpenSSL tests can never run because the version check is looking > at > >>>>> the OpenSSLStatus rather than AprStatus. > >>>>> > >>>>> With the above fixed, the OpenSSL tests still won't run because the > >>>>> OpenSSL version isn't set until after the version check. > >>>>> > >>>>> With that fixed, most of the OpenSSL tests result in errors or > >> failures. > >>>>> The OpenSSL-FFM tests have a similar failure rate. > >>>>> > >>>>> Mark > >>>>> > >>>>> > >>>>>> --- > >>>>>> test/org/apache/tomcat/util/net/TestPQC.java | 331 > >>>>> +++++++++++++++++++++ > >>>>>> .../tomcat/util/net/TesterKeystoreGenerator.java | 65 ++++ > >>>>>> 2 files changed, 396 insertions(+) > >>>>>> > >>>>>> diff --git a/test/org/apache/tomcat/util/net/TestPQC.java > >>>>> b/test/org/apache/tomcat/util/net/TestPQC.java > >>>>>> new file mode 100644 > >>>>>> index 0000000000..db3f53ff60 > >>>>>> --- /dev/null > >>>>>> +++ b/test/org/apache/tomcat/util/net/TestPQC.java > >>>>>> @@ -0,0 +1,331 @@ > >>>>>> +/* > >>>>>> + * Licensed to the Apache Software Foundation (ASF) under one or > >> more > >>>>>> + * contributor license agreements. See the NOTICE file > distributed > >>>>> with > >>>>>> + * this work for additional information regarding copyright > >> ownership. > >>>>>> + * The ASF licenses this file to You under the Apache License, > >>>>> Version 2.0 > >>>>>> + * (the "License"); you may not use this file except in compliance > >>>>> with > >>>>>> + * the License. You may obtain a copy of the License at > >>>>>> + * > >>>>>> + * http://www.apache.org/licenses/LICENSE-2.0 > >>>>>> + * > >>>>>> + * Unless required by applicable law or agreed to in writing, > >> software > >>>>>> + * distributed under the License is distributed on an "AS IS" > BASIS, > >>>>>> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or > >>>>> implied. > >>>>>> + * See the License for the specific language governing permissions > >> and > >>>>>> + * limitations under the License. > >>>>>> + */ > >>>>>> +package org.apache.tomcat.util.net; > >>>>>> + > >>>>>> +import java.io.File; > >>>>>> +import java.util.ArrayList; > >>>>>> +import java.util.Collection; > >>>>>> +import java.util.List; > >>>>>> +import java.util.Map; > >>>>>> +import java.util.concurrent.TimeUnit; > >>>>>> + > >>>>>> +import javax.net.ssl.SSLContext; > >>>>>> +import javax.net.ssl.SSLHandshakeException; > >>>>>> +import javax.net.ssl.TrustManager; > >>>>>> + > >>>>>> +import org.junit.Assert; > >>>>>> +import org.junit.Assume; > >>>>>> +import org.junit.Test; > >>>>>> +import org.junit.runner.RunWith; > >>>>>> +import org.junit.runners.Parameterized; > >>>>>> +import org.junit.runners.Parameterized.Parameter; > >>>>>> + > >>>>>> +import org.apache.catalina.Context; > >>>>>> +import org.apache.catalina.connector.Connector; > >>>>>> +import org.apache.catalina.startup.TesterServlet; > >>>>>> +import org.apache.catalina.startup.Tomcat; > >>>>>> +import org.apache.catalina.startup.TomcatBaseTest; > >>>>>> +import org.apache.tomcat.util.buf.ByteChunk; > >>>>>> +import org.apache.tomcat.util.net.SSLHostConfigCertificate.Type; > >>>>>> +import org.apache.tomcat.util.net.openssl.OpenSSLStatus; > >>>>>> + > >>>>>> +@RunWith(Parameterized.class) > >>>>>> +public class TestPQC extends TomcatBaseTest { > >>>>>> + > >>>>>> + @Parameterized.Parameters(name = "{0}") > >>>>>> + public static Collection<Object[]> parameters() { > >>>>>> + List<Object[]> parameterSets = new ArrayList<>(); > >>>>>> + parameterSets.add(new Object[] { > >>>>>> + "JSSE", Boolean.FALSE, "org.apache.tomcat.util.net > >>>>> .jsse.JSSEImplementation"}); > >>>>>> + parameterSets.add(new Object[] { > >>>>>> + "OpenSSL", Boolean.TRUE, " > org.apache.tomcat.util.net > >>>>> .openssl.OpenSSLImplementation"}); > >>>>>> + parameterSets.add(new Object[] { > >>>>>> + "OpenSSL-FFM", Boolean.TRUE, " > >>>>> org.apache.tomcat.util.net.openssl.panama.OpenSSLImplementation"}); > >>>>>> + return parameterSets; > >>>>>> + } > >>>>>> + > >>>>>> + @Parameter(0) > >>>>>> + public String connectorName; > >>>>>> + > >>>>>> + @Parameter(1) > >>>>>> + public boolean useOpenSSL; > >>>>>> + > >>>>>> + @Parameter(2) > >>>>>> + public String sslImplementationName; > >>>>>> + > >>>>>> + @Override > >>>>>> + public void setUp() throws Exception { > >>>>>> + super.setUp(); > >>>>>> + > >>>>>> + Tomcat tomcat = getTomcatInstance(); > >>>>>> + Connector connector = tomcat.getConnector(); > >>>>>> + > >>>>>> + Assert.assertTrue(connector.setProperty("SSLEnabled", > >> "true")); > >>>>>> + SSLHostConfig sslHostConfig = new SSLHostConfig(); > >>>>>> + sslHostConfig.setProtocols(Constants.SSL_PROTO_TLSv1_3); > >>>>>> + connector.addSslHostConfig(sslHostConfig); > >>>>>> + > >>>>>> + TesterSupport.configureSSLImplementation(tomcat, > >>>>> sslImplementationName, useOpenSSL); > >>>>>> + > >>>>>> + Context ctx = getProgrammaticRootContext(); > >>>>>> + Tomcat.addServlet(ctx, "TesterServlet", new > TesterServlet()); > >>>>>> + ctx.addServletMappingDecoded("/*", "TesterServlet"); > >>>>>> + } > >>>>>> + > >>>>>> + @Test > >>>>>> + public void testHostMLDSA44() throws Exception { > >>>>>> + File[] pqcFiles = configureHostMLDSA("ML-DSA-44"); > >>>>>> + doTestWithOpenSSLClient(pqcFiles[0].getAbsolutePath(), > null, > >>>>> null, null); > >>>>>> + } > >>>>>> + > >>>>>> + > >>>>>> + @Test > >>>>>> + public void testHostMLDSA65() throws Exception { > >>>>>> + File[] pqcFiles = configureHostMLDSA("ML-DSA-65"); > >>>>>> + doTestWithOpenSSLClient(pqcFiles[0].getAbsolutePath(), > null, > >>>>> null, null); > >>>>>> + } > >>>>>> + > >>>>>> + > >>>>>> + @Test > >>>>>> + public void testHostMLDSA87() throws Exception { > >>>>>> + File[] pqcFiles = configureHostMLDSA("ML-DSA-87"); > >>>>>> + doTestWithOpenSSLClient(pqcFiles[0].getAbsolutePath(), > null, > >>>>> null, null); > >>>>>> + } > >>>>>> + > >>>>>> + @Test > >>>>>> + public void testHostRSAandMLDSA() throws Exception { > >>>>>> + configureHostRSA(); > >>>>>> + configureHostMLDSA("ML-DSA-65"); > >>>>>> + doTest(); > >>>>>> + } > >>>>>> + > >>>>>> + @Test > >>>>>> + public void testHostECandMLDSA() throws Exception { > >>>>>> + configureHostEC(); > >>>>>> + configureHostMLDSA("ML-DSA-65"); > >>>>>> + doTest(); > >>>>>> + } > >>>>>> + > >>>>>> + @Test > >>>>>> + public void testHostRSAwithX25519MLKEM768() throws Exception { > >>>>>> + configureHostRSA(); > >>>>>> + configureHostWithGroup("X25519MLKEM768"); > >>>>>> + doTestWithOpenSSLClient(new > >>>>> File(TesterSupport.CA_CERT_PEM).getAbsolutePath(), > >>>>>> + "X25519MLKEM768", null, null); > >>>>>> + } > >>>>>> + > >>>>>> + > >>>>>> + @Test > >>>>>> + public void testHostRSAwithSecP256r1MLKEM768() throws > Exception { > >>>>>> + configureHostRSA(); > >>>>>> + configureHostWithGroup("SecP256r1MLKEM768"); > >>>>>> + doTestWithOpenSSLClient(new > >>>>> File(TesterSupport.CA_CERT_PEM).getAbsolutePath(), > >>>>>> + "SecP256r1MLKEM768", null, null); > >>>>>> + } > >>>>>> + > >>>>>> + @Test > >>>>>> + public void testHostRSAwithSecP384r1MLKEM1024() throws > Exception > >> { > >>>>>> + configureHostRSA(); > >>>>>> + configureHostWithGroup("SecP384r1MLKEM1024"); > >>>>>> + doTestWithOpenSSLClient(new > >>>>> File(TesterSupport.CA_CERT_PEM).getAbsolutePath(), > >>>>>> + "SecP384r1MLKEM1024", null, null); > >>>>>> + } > >>>>>> + > >>>>>> + @Test > >>>>>> + public void testHostMLDSAwithX25519MLKEM768() throws Exception > { > >>>>>> + File[] pqcFiles = configureHostMLDSA("ML-DSA-65"); > >>>>>> + configureHostWithGroup("X25519MLKEM768"); > >>>>>> + doTestWithOpenSSLClient(pqcFiles[0].getAbsolutePath(), > >>>>> "X25519MLKEM768", null, null); > >>>>>> + } > >>>>>> + > >>>>>> + @Test > >>>>>> + public void testHostMLDSAwithSecP256r1MLKEM768() throws > >> Exception { > >>>>>> + File[] pqcFiles = configureHostMLDSA("ML-DSA-65"); > >>>>>> + configureHostWithGroup("SecP256r1MLKEM768"); > >>>>>> + doTestWithOpenSSLClient(pqcFiles[0].getAbsolutePath(), > >>>>> "SecP256r1MLKEM768", null, null); > >>>>>> + } > >>>>>> + > >>>>>> + @Test > >>>>>> + public void testClientMLDSA() throws Exception { > >>>>>> + configureHostRSA(); > >>>>>> + File[] clientFiles = > >>>>> TesterKeystoreGenerator.generatePQCCertificate("testuser", > "ML-DSA-65", > >>>>>> + null, null); > >>>>>> + SSLHostConfig sslHostConfig = > >>>>> getTomcatInstance().getConnector().findSslHostConfigs()[0]; > >>>>>> + sslHostConfig.setCertificateVerification("required"); > >>>>>> + > >>>>> sslHostConfig.setCaCertificateFile(clientFiles[0].getAbsolutePath()); > >>>>>> + doTestWithOpenSSLClient(new > >>>>> File(TesterSupport.CA_CERT_PEM).getAbsolutePath(), null, > >>>>>> + clientFiles[0].getAbsolutePath(), > >>>>> clientFiles[1].getAbsolutePath()); > >>>>>> + } > >>>>>> + > >>>>>> + @Test > >>>>>> + public void testClientMLDSAwithMLDSAServer() throws Exception { > >>>>>> + File[] serverFiles = configureHostMLDSA("ML-DSA-65"); > >>>>>> + File[] clientFiles = > >>>>> TesterKeystoreGenerator.generatePQCCertificate("testuser", > "ML-DSA-65", > >>>>>> + null, null); > >>>>>> + SSLHostConfig sslHostConfig = > >>>>> getTomcatInstance().getConnector().findSslHostConfigs()[0]; > >>>>>> + sslHostConfig.setCertificateVerification("required"); > >>>>>> + > >>>>> sslHostConfig.setCaCertificateFile(clientFiles[0].getAbsolutePath()); > >>>>>> + doTestWithOpenSSLClient(serverFiles[0].getAbsolutePath(), > >> null, > >>>>>> + clientFiles[0].getAbsolutePath(), > >>>>> clientFiles[1].getAbsolutePath()); > >>>>>> + } > >>>>>> + > >>>>>> + @Test(expected = SSLHandshakeException.class) > >>>>>> + public void testHostMLDSAHandshakeFailure() throws Exception { > >>>>>> + assumePQCSupported(); > >>>>>> + configureHostMLDSA("ML-DSA-65"); > >>>>>> + > >>>>>> + SSLContext sc = > >>>>> SSLContext.getInstance(Constants.SSL_PROTO_TLSv1_2); > >>>>>> + sc.init(null, new TrustManager[] { new > >>>>> TesterSupport.TrustAllCerts() }, null); > >>>>>> + TesterSupport.ClientSSLSocketFactory > clientSSLSocketFactory = > >>>>>> + new > >>>>> TesterSupport.ClientSSLSocketFactory(sc.getSocketFactory()); > >>>>>> + clientSSLSocketFactory.setProtocols(new String[] { > >>>>> Constants.SSL_PROTO_TLSv1_2 }); > >>>>>> + > >>>>> > >> > javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory(clientSSLSocketFactory); > >>>>>> + > >>>>>> + Tomcat tomcat = getTomcatInstance(); > >>>>>> + tomcat.start(); > >>>>>> + getUrl("https://localhost:" + getPort() + "/"); > >>>>>> + } > >>>>>> + > >>>>>> + > >>>>>> + private void assumePQCSupported() { > >>>>>> + if (!useOpenSSL) { > >>>>>> + Assume.assumeTrue("JSSE does not yet support PQC", > >> false); > >>>>>> + } > >>>>>> + > >>>>>> + Assume.assumeTrue("PQC requires OpenSSL 3.5+", > >>>>>> + OpenSSLStatus.getMajorVersion() > 3 || > >>>>>> + OpenSSLStatus.getMajorVersion() == 3 && > >>>>> OpenSSLStatus.getMinorVersion() >= 5); > >>>>>> + } > >>>>>> + > >>>>>> + private File[] configureHostMLDSA(String algorithm) throws > >>>>> Exception { > >>>>>> + File[] pqcFiles = > >>>>> TesterKeystoreGenerator.generatePQCCertificate("localhost", > algorithm, > >>>>>> + new String[] { "localhost" }, null); > >>>>>> + > >>>>>> + Tomcat tomcat = getTomcatInstance(); > >>>>>> + Connector connector = tomcat.getConnector(); > >>>>>> + SSLHostConfig sslHostConfig = > >>>>> connector.findSslHostConfigs()[0]; > >>>>>> + > >>>>>> + SSLHostConfigCertificate cert = new > >>>>> SSLHostConfigCertificate(sslHostConfig, Type.MLDSA); > >>>>>> + cert.setCertificateFile(pqcFiles[0].getAbsolutePath()); > >>>>>> + cert.setCertificateKeyFile(pqcFiles[1].getAbsolutePath()); > >>>>>> + sslHostConfig.addCertificate(cert); > >>>>>> + > >>>>>> + return pqcFiles; > >>>>>> + } > >>>>>> + > >>>>>> + private void configureHostRSA() { > >>>>>> + Tomcat tomcat = getTomcatInstance(); > >>>>>> + Connector connector = tomcat.getConnector(); > >>>>>> + SSLHostConfig sslHostConfig = > >>>>> connector.findSslHostConfigs()[0]; > >>>>>> + > >>>>>> + SSLHostConfigCertificate cert = new > >>>>> SSLHostConfigCertificate(sslHostConfig, Type.RSA); > >>>>>> + cert.setCertificateFile(new > >>>>> File(TesterSupport.LOCALHOST_RSA_CERT_PEM).getAbsolutePath()); > >>>>>> + cert.setCertificateKeyFile(new > >>>>> File(TesterSupport.LOCALHOST_RSA_KEY_PEM).getAbsolutePath()); > >>>>>> + cert.setCertificateKeyPassword(TesterSupport.JKS_PASS); > >>>>>> + sslHostConfig.addCertificate(cert); > >>>>>> + } > >>>>>> + > >>>>>> + private void configureHostEC() { > >>>>>> + Tomcat tomcat = getTomcatInstance(); > >>>>>> + Connector connector = tomcat.getConnector(); > >>>>>> + SSLHostConfig sslHostConfig = > >>>>> connector.findSslHostConfigs()[0]; > >>>>>> + > >>>>>> + SSLHostConfigCertificate cert = new > >>>>> SSLHostConfigCertificate(sslHostConfig, Type.EC); > >>>>>> + cert.setCertificateFile(new > >>>>> File(TesterSupport.LOCALHOST_EC_CERT_PEM).getAbsolutePath()); > >>>>>> + cert.setCertificateKeyFile(new > >>>>> File(TesterSupport.LOCALHOST_EC_KEY_PEM).getAbsolutePath()); > >>>>>> + sslHostConfig.addCertificate(cert); > >>>>>> + } > >>>>>> + > >>>>>> + private void configureHostWithGroup(String groupName) { > >>>>>> + Tomcat tomcat = getTomcatInstance(); > >>>>>> + Connector connector = tomcat.getConnector(); > >>>>>> + SSLHostConfig sslHostConfig = > >>>>> connector.findSslHostConfigs()[0]; > >>>>>> + sslHostConfig.setGroups(groupName); > >>>>>> + } > >>>>>> + > >>>>>> + private void doTest() throws Exception { > >>>>>> + assumePQCSupported(); > >>>>>> + SSLContext sc = > >>>>> SSLContext.getInstance(Constants.SSL_PROTO_TLSv1_3); > >>>>>> + sc.init(null, new TrustManager[] { new > >>>>> TesterSupport.TrustAllCerts() }, null); > >>>>>> + > >>>>> > >> > javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); > >>>>>> + Tomcat tomcat = getTomcatInstance(); > >>>>>> + tomcat.start(); > >>>>>> + ByteChunk res = getUrl("https://localhost:" + getPort() + > >>>>> "/"); > >>>>>> + Assert.assertEquals("OK", res.toString()); > >>>>>> + } > >>>>>> + > >>>>>> + private void doTestWithOpenSSLClient(String caFile, String > >> groups, > >>>>>> + String clientCert, String clientKey) throws Exception { > >>>>>> + assumePQCSupported(); > >>>>>> + > >>>>>> + Tomcat tomcat = getTomcatInstance(); > >>>>>> + tomcat.start(); > >>>>>> + > >>>>>> + String openSSLPath = > >>>>> System.getProperty("tomcat.test.openssl.path"); > >>>>>> + String openSSLLibPath = null; > >>>>>> + if (openSSLPath == null || openSSLPath.length() == 0) { > >>>>>> + openSSLPath = "openssl"; > >>>>>> + } else { > >>>>>> + openSSLLibPath = openSSLPath.substring(0, > >>>>> openSSLPath.lastIndexOf('/')); > >>>>>> + openSSLLibPath = openSSLLibPath + "/../:" + > >> openSSLLibPath > >>>>> + "/../lib:" + openSSLLibPath + "/../lib64"; > >>>>>> + } > >>>>>> + > >>>>>> + List<String> cmd = new ArrayList<>(); > >>>>>> + cmd.add(openSSLPath); > >>>>>> + cmd.add("s_client"); > >>>>>> + cmd.add("-connect"); > >>>>>> + cmd.add("localhost:" + getPort()); > >>>>>> + cmd.add("-CAfile"); > >>>>>> + cmd.add(caFile); > >>>>>> + cmd.add("-tls1_3"); > >>>>>> + if (groups != null) { > >>>>>> + cmd.add("-groups"); > >>>>>> + cmd.add(groups); > >>>>>> + } > >>>>>> + if (clientCert != null) { > >>>>>> + cmd.add("-cert"); > >>>>>> + cmd.add(clientCert); > >>>>>> + cmd.add("-key"); > >>>>>> + cmd.add(clientKey); > >>>>>> + } > >>>>>> + > >>>>>> + ProcessBuilder pb = new ProcessBuilder(cmd); > >>>>>> + > >>>>>> + if (openSSLLibPath != null) { > >>>>>> + Map<String,String> env = pb.environment(); > >>>>>> + String libraryPath = env.get("LD_LIBRARY_PATH"); > >>>>>> + if (libraryPath == null) { > >>>>>> + libraryPath = openSSLLibPath; > >>>>>> + } else { > >>>>>> + libraryPath = libraryPath + ":" + openSSLLibPath; > >>>>>> + } > >>>>>> + env.put("LD_LIBRARY_PATH", libraryPath); > >>>>>> + } > >>>>>> + > >>>>>> + pb.redirectErrorStream(true); > >>>>>> + Process p = pb.start(); > >>>>>> + > >>>>>> + p.getOutputStream().write("GET / HTTP/1.0\r\nHost: > >>>>> localhost\r\n\r\n".getBytes()); > >>>>>> + p.getOutputStream().flush(); > >>>>>> + > >>>>>> + String output = new > >> String(p.getInputStream().readAllBytes()); > >>>>>> + > >>>>>> + Assert.assertTrue("Process did not complete in time", > >>>>> p.waitFor(10, TimeUnit.SECONDS)); > >>>>>> + Assert.assertTrue("TLS handshake failed:\n" + output, > >>>>> output.contains("HTTP/1.")); > >>>>>> + Assert.assertTrue("Unexpected response body:\n" + output, > >>>>> output.contains("OK")); > >>>>>> + } > >>>>>> +} > >>>>>> diff --git > >>>>> a/test/org/apache/tomcat/util/net/TesterKeystoreGenerator.java > >>>>> b/test/org/apache/tomcat/util/net/TesterKeystoreGenerator.java > >>>>>> index 9fd4affde6..00f1772fcc 100644 > >>>>>> --- a/test/org/apache/tomcat/util/net/TesterKeystoreGenerator.java > >>>>>> +++ b/test/org/apache/tomcat/util/net/TesterKeystoreGenerator.java > >>>>>> @@ -19,6 +19,7 @@ package org.apache.tomcat.util.net; > >>>>>> > >>>>>> import java.io.File; > >>>>>> import java.io.FileOutputStream; > >>>>>> +import java.io.FileWriter; > >>>>>> import java.math.BigInteger; > >>>>>> import java.security.KeyPair; > >>>>>> import java.security.KeyPairGenerator; > >>>>>> @@ -33,6 +34,8 @@ import org.bouncycastle.asn1.x509.GeneralNames; > >>>>>> import org.bouncycastle.cert.X509v3CertificateBuilder; > >>>>>> import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; > >>>>>> import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; > >>>>>> +import org.bouncycastle.jce.provider.BouncyCastleProvider; > >>>>>> +import org.bouncycastle.openssl.jcajce.JcaPEMWriter; > >>>>>> import org.bouncycastle.operator.ContentSigner; > >>>>>> import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; > >>>>>> > >>>>>> @@ -100,4 +103,66 @@ public final class TesterKeystoreGenerator { > >>>>>> > >>>>>> return keystoreFile; > >>>>>> } > >>>>>> + > >>>>>> + /** > >>>>>> + * Generate temporary PEM files containing a self-signed PQC > >>>>> certificate and private key. > >>>>>> + * > >>>>>> + * @param cn the Common Name for the certificate subject > >>>>>> + * @param algorithm the PQC algorithm name, e.g. {@code > >>>>> "ML-DSA-44"}, {@code "ML-DSA-65"}, > >>>>>> + * or {@code "ML-DSA-87"} > >>>>>> + * @param sanNames DNS Subject Alternative Names to include, > or > >>>>> {@code null} for none > >>>>>> + * @param customizer callback to add extensions to the > >>>>> certificate, or {@code null} for none > >>>>>> + * > >>>>>> + * @return a two-element array: {@code [0]} is the certificate > >> PEM > >>>>> file, {@code [1]} is the > >>>>>> + * private key PEM file > >>>>>> + * > >>>>>> + * @throws Exception if certificate generation fails > >>>>>> + */ > >>>>>> + public static File[] generatePQCCertificate(String cn, String > >>>>> algorithm, String[] sanNames, > >>>>>> + > >>>>> CertificateExtensionsCustomizer customizer) throws Exception { > >>>>>> + BouncyCastleProvider bouncyCastleProvider = new > >>>>> BouncyCastleProvider(); > >>>>>> + > >>>>>> + KeyPairGenerator keyPairGenerator = > >>>>> KeyPairGenerator.getInstance(algorithm, bouncyCastleProvider); > >>>>>> + KeyPair keyPair = keyPairGenerator.generateKeyPair(); > >>>>>> + > >>>>>> + X500Name subject = new X500Name("CN=" + cn); > >>>>>> + BigInteger serial = > >>>>> BigInteger.valueOf(System.currentTimeMillis()); > >>>>>> + long oneDay = 86400000L; > >>>>>> + Date notBefore = new Date(System.currentTimeMillis() - > >> oneDay); > >>>>>> + Date notAfter = new Date(System.currentTimeMillis() + 365L > * > >>>>> oneDay); > >>>>>> + > >>>>>> + X509v3CertificateBuilder certBuilder = new > >>>>> JcaX509v3CertificateBuilder(subject, serial, notBefore, > >>>>>> + notAfter, subject, keyPair.getPublic()); > >>>>>> + > >>>>>> + if (sanNames != null && sanNames.length > 0) { > >>>>>> + GeneralName[] generalNames = new > >>>>> GeneralName[sanNames.length]; > >>>>>> + for (int i = 0; i < sanNames.length; i++) { > >>>>>> + generalNames[i] = new > >> GeneralName(GeneralName.dNSName, > >>>>> sanNames[i]); > >>>>>> + } > >>>>>> + > >> certBuilder.addExtension(Extension.subjectAlternativeName, > >>>>> false, new GeneralNames(generalNames)); > >>>>>> + } > >>>>>> + > >>>>>> + if (customizer != null) { > >>>>>> + customizer.customize(keyPair, certBuilder); > >>>>>> + } > >>>>>> + > >>>>>> + ContentSigner signer = new > >>>>> JcaContentSignerBuilder(algorithm).setProvider(bouncyCastleProvider) > >>>>>> + .build(keyPair.getPrivate()); > >>>>>> + X509Certificate certificate = new > >>>>> JcaX509CertificateConverter().setProvider(bouncyCastleProvider) > >>>>>> + .getCertificate(certBuilder.build(signer)); > >>>>>> + > >>>>>> + File certFile = File.createTempFile("test-pqc-cert-", > >> ".pem"); > >>>>>> + certFile.deleteOnExit(); > >>>>>> + try (JcaPEMWriter writer = new JcaPEMWriter(new > >>>>> FileWriter(certFile))) { > >>>>>> + writer.writeObject(certificate); > >>>>>> + } > >>>>>> + > >>>>>> + File keyFile = File.createTempFile("test-pqc-key-", > ".pem"); > >>>>>> + keyFile.deleteOnExit(); > >>>>>> + try (JcaPEMWriter writer = new JcaPEMWriter(new > >>>>> FileWriter(keyFile))) { > >>>>>> + writer.writeObject(keyPair.getPrivate()); > >>>>>> + } > >>>>>> + > >>>>>> + return new File[] { certFile, keyFile }; > >>>>>> + } > >>>>>> } > >>>>>> > >>>>>> > >>>>>> > --------------------------------------------------------------------- > >>>>>> To unsubscribe, e-mail: [email protected] > >>>>>> For additional commands, e-mail: [email protected] > >>>>>> > >>>>> > >>>>> > >>>>> --------------------------------------------------------------------- > >>>>> To unsubscribe, e-mail: [email protected] > >>>>> For additional commands, e-mail: [email protected] > >>>>> > >>>>> > >>> > >> > >> > >> --------------------------------------------------------------------- > >> To unsubscribe, e-mail: [email protected] > >> For additional commands, e-mail: [email protected] > >> > >> > > > > > --------------------------------------------------------------------- > To unsubscribe, e-mail: [email protected] > For additional commands, e-mail: [email protected] > >
