From c38afda9caaab93c74813cb4d5dd80dcca4730e0 Mon Sep 17 00:00:00 2001 From: Dimitris Soumis Date: Tue, 24 Mar 2026 13:26:29 +0200 Subject: [PATCH 1/6] Fix naming convention of performance tests in BUILDING.txt --- BUILDING.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BUILDING.txt b/BUILDING.txt index 0159b6803af4..c5a285533cf4 100644 --- a/BUILDING.txt +++ b/BUILDING.txt @@ -570,7 +570,7 @@ NOTE: Cobertura is licensed under GPL v2 with parts of it being under Where there is no benefit in running an absolute performance test as part of a standard test run, the test will be excluded by naming it - Tester*Performance.java. + Test*Performance.java. The relative tests are included as part of a standard test run however, where the assumptions made about host capabilities are not true (e.g. on From 1d6791c299d2add3e1dc7d66219ecdd6527b4dba Mon Sep 17 00:00:00 2001 From: Dimitris Soumis Date: Tue, 24 Mar 2026 14:21:59 +0200 Subject: [PATCH 2/6] Add TestSSLAuthenticator --- .../authenticator/TestSSLAuthenticator.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 test/org/apache/catalina/authenticator/TestSSLAuthenticator.java diff --git a/test/org/apache/catalina/authenticator/TestSSLAuthenticator.java b/test/org/apache/catalina/authenticator/TestSSLAuthenticator.java new file mode 100644 index 000000000000..346d06add831 --- /dev/null +++ b/test/org/apache/catalina/authenticator/TestSSLAuthenticator.java @@ -0,0 +1,40 @@ +/* + * 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.catalina.authenticator; + +import org.junit.Assert; +import org.junit.Test; + +import org.apache.catalina.startup.Tomcat; +import org.apache.catalina.startup.TomcatBaseTest; +import org.apache.tomcat.util.net.TesterSupport; + +public class TestSSLAuthenticator extends TomcatBaseTest { + + // https://bz.apache.org/bugzilla/show_bug.cgi?id=65991 + @Test + public void testBindOnInitFalseNoNPE() throws Exception { + Tomcat tomcat = getTomcatInstance(); + + TesterSupport.configureClientCertContext(tomcat); + Assert.assertTrue(tomcat.getConnector().setProperty("bindOnInit", "false")); + + tomcat.start(); + tomcat.stop(); + } +} From 415af163de717f722e80da4554cb102758b31745 Mon Sep 17 00:00:00 2001 From: Dimitris Soumis Date: Tue, 24 Mar 2026 14:53:23 +0200 Subject: [PATCH 3/6] Add TestValidateClientSessionId --- .../TestValidateClientSessionId.java | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 test/org/apache/catalina/connector/TestValidateClientSessionId.java diff --git a/test/org/apache/catalina/connector/TestValidateClientSessionId.java b/test/org/apache/catalina/connector/TestValidateClientSessionId.java new file mode 100644 index 000000000000..dc94fc3fc369 --- /dev/null +++ b/test/org/apache/catalina/connector/TestValidateClientSessionId.java @@ -0,0 +1,89 @@ +/* + * 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.catalina.connector; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Assert; +import org.junit.Test; + +import org.apache.catalina.Context; +import org.apache.catalina.startup.Tomcat; +import org.apache.catalina.startup.TomcatBaseTest; +import org.apache.tomcat.util.buf.ByteChunk; + +public class TestValidateClientSessionId extends TomcatBaseTest { + + @Test + public void testMaliciousSessionIdRejected() throws Exception { + Tomcat tomcat = getTomcatInstance(); + + Context ctx = getProgrammaticRootContext(); + Tomcat.addServlet(ctx, "snoop", new SnoopServlet()); + ctx.addServletMappingDecoded("/", "snoop"); + + tomcat.start(); + + Map> reqHead = new HashMap<>(); + reqHead.put("Cookie", List.of("JSESSIONID=DUMMY_SESSION_ID")); + + ByteChunk res = new ByteChunk(); + getUrl("http://localhost:" + getPort() + "/?createSession=true", res, reqHead, null); + + RequestDescriptor requestDesc = SnoopResult.parse(res.toString()); + + String actualSessionId = requestDesc.getRequestInfo("SESSION-ID"); + Assert.assertNotEquals("DUMMY_SESSION_ID", actualSessionId); + } + + @Test + public void testValidSessionIdAcceptedAcrossContexts() throws Exception { + Tomcat tomcat = getTomcatInstance(); + + Context ctx1 = tomcat.addContext("/app1", null); + ctx1.setSessionCookiePath("/"); + Tomcat.addServlet(ctx1, "snoop", new SnoopServlet()); + ctx1.addServletMappingDecoded("/", "snoop"); + + Context ctx2 = tomcat.addContext("/app2", null); + ctx2.setSessionCookiePath("/"); + Tomcat.addServlet(ctx2, "snoop", new SnoopServlet()); + ctx2.addServletMappingDecoded("/", "snoop"); + + tomcat.start(); + + ByteChunk res = new ByteChunk(); + Map> resHead = new HashMap<>(); + getUrl("http://localhost:" + getPort() + "/app1/?createSession=true", res, null, resHead); + + RequestDescriptor requestDesc = SnoopResult.parse(res.toString()); + String sessionId1 = requestDesc.getRequestInfo("SESSION-ID"); + + Map> reqHead = new HashMap<>(); + reqHead.put("Cookie", List.of("JSESSIONID=" + sessionId1)); + + getUrl("http://localhost:" + getPort() + "/app2/?createSession=true", res, reqHead, null); + + requestDesc = SnoopResult.parse(res.toString()); + String sessionId2 = requestDesc.getRequestInfo("SESSION-ID"); + Assert.assertEquals(sessionId1, sessionId2); + } + +} From b0974117964b11f6fb10c2cf9e05d45eeb746c87 Mon Sep 17 00:00:00 2001 From: Dimitris Soumis Date: Fri, 27 Mar 2026 14:38:54 +0200 Subject: [PATCH 4/6] Add generateKeystore method in TesterSupport which utilizes BouncyCastle to generate a temporary JKS keystore containing a self-signed RSA certificate. --- .../apache/tomcat/util/net/TesterSupport.java | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/test/org/apache/tomcat/util/net/TesterSupport.java b/test/org/apache/tomcat/util/net/TesterSupport.java index 9c3e0d209525..31a1c73240b5 100644 --- a/test/org/apache/tomcat/util/net/TesterSupport.java +++ b/test/org/apache/tomcat/util/net/TesterSupport.java @@ -18,11 +18,15 @@ import java.io.File; import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.math.BigInteger; import java.net.InetAddress; import java.net.Socket; import java.net.UnknownHostException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; @@ -36,6 +40,7 @@ import java.security.cert.TrustAnchor; import java.security.cert.X509CertSelector; import java.security.cert.X509Certificate; +import java.util.Date; import java.util.EnumSet; import java.util.Enumeration; import java.util.HashSet; @@ -79,6 +84,15 @@ import org.apache.tomcat.util.net.jsse.JSSEImplementation; import org.apache.tomcat.util.net.openssl.OpenSSLImplementation; import org.apache.tomcat.util.net.openssl.OpenSSLStatus; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.GeneralName; +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.operator.ContentSigner; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; public final class TesterSupport { @@ -412,6 +426,68 @@ protected static boolean checkLastClientAuthRequestedIssuers() { new X500Principal(lastRequestedIssuers[0].getName())); } + @FunctionalInterface + public interface CertificateExtensionsCustomizer { + void customize(KeyPair keyPair, X509v3CertificateBuilder certBuilder) + throws Exception; + } + + /** + * Generate a temporary JKS keystore containing a self-signed RSA certificate. + * + * @param cn the Common Name for the certificate subject + * @param alias the keystore alias for the key entry + * @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 temporary keystore file with password {@link #JKS_PASS} + * + * @throws Exception if certificate generation or keystore creation fails + */ + public static File generateKeystore(String cn, String alias, String[] sanNames, + CertificateExtensionsCustomizer customizer) throws Exception { + KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); + kpg.initialize(4096); + KeyPair keyPair = kpg.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("SHA256withRSA").build(keyPair.getPrivate()); + X509Certificate certificate = new JcaX509CertificateConverter().getCertificate(certBuilder.build(signer)); + + KeyStore ks = KeyStore.getInstance("JKS"); + ks.load(null, null); + ks.setKeyEntry(alias, keyPair.getPrivate(), JKS_PASS.toCharArray(), new X509Certificate[] { certificate }); + + File keystoreFile = File.createTempFile("test-cert-", ".jks"); + keystoreFile.deleteOnExit(); + try (FileOutputStream fos = new FileOutputStream(keystoreFile)) { + ks.store(fos, JKS_PASS.toCharArray()); + } + + return keystoreFile; + } + + public static final byte DATA = (byte)33; public static class SimpleServlet extends HttpServlet { From 9d59636a3284e551c3e5664b87e9d267bdb4ea40 Mon Sep 17 00:00:00 2001 From: Dimitris Soumis Date: Fri, 27 Mar 2026 14:38:58 +0200 Subject: [PATCH 5/6] Add TestLargeClientHello and add a debug log in SecureNioChannel when BUFFER_UNDERFLOW occurs during handshake --- .../tomcat/util/net/LocalStrings.properties | 1 + .../tomcat/util/net/SecureNioChannel.java | 3 + .../tomcat/util/net/TestLargeClientHello.java | 86 +++++++++++++++++++ 3 files changed, 90 insertions(+) create mode 100644 test/org/apache/tomcat/util/net/TestLargeClientHello.java diff --git a/java/org/apache/tomcat/util/net/LocalStrings.properties b/java/org/apache/tomcat/util/net/LocalStrings.properties index 720f07bdccc6..f433c46a8158 100644 --- a/java/org/apache/tomcat/util/net/LocalStrings.properties +++ b/java/org/apache/tomcat/util/net/LocalStrings.properties @@ -44,6 +44,7 @@ channel.nio.ssl.unexpectedStatusDuringWrap=Unexpected status [{0}] during handsh channel.nio.ssl.unwrapFail=Unable to unwrap data, invalid status [{0}] channel.nio.ssl.unwrapFailResize=Unable to unwrap data because buffer is too small, invalid status [{0}] channel.nio.ssl.wrapFail=Unable to wrap data, invalid status [{0}] +channel.nio.ssl.handshakeUnwrapBufferUnderflow=BUFFER_UNDERFLOW during handshake unwrap, more data needed from the network endpoint.accept.fail=Socket accept failed endpoint.alpn.fail=Failed to configure endpoint for ALPN using [{0}] diff --git a/java/org/apache/tomcat/util/net/SecureNioChannel.java b/java/org/apache/tomcat/util/net/SecureNioChannel.java index bb43c5c0989f..28c090d5c8af 100644 --- a/java/org/apache/tomcat/util/net/SecureNioChannel.java +++ b/java/org/apache/tomcat/util/net/SecureNioChannel.java @@ -492,6 +492,9 @@ protected SSLEngineResult handshakeUnwrap(boolean doread) throws IOException { // call unwrap getBufHandler().configureReadBufferForWrite(); result = sslEngine.unwrap(netInBuffer, getBufHandler().getReadBuffer()); + if (log.isDebugEnabled() && result.getStatus() == SSLEngineResult.Status.BUFFER_UNDERFLOW) { + log.debug(sm.getString("channel.nio.ssl.handshakeUnwrapBufferUnderflow")); + } /* * ByteBuffer.compact() is an optional method but netInBuffer is created from either ByteBuffer.allocate() * or ByteBuffer.allocateDirect() and the ByteBuffers returned by those methods do implement compact(). The diff --git a/test/org/apache/tomcat/util/net/TestLargeClientHello.java b/test/org/apache/tomcat/util/net/TestLargeClientHello.java new file mode 100644 index 000000000000..c476fe0a2417 --- /dev/null +++ b/test/org/apache/tomcat/util/net/TestLargeClientHello.java @@ -0,0 +1,86 @@ +/* + * 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.logging.Level; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; + +import org.junit.Assert; +import org.junit.Test; + +import org.apache.catalina.Context; +import org.apache.catalina.startup.Tomcat; +import org.apache.catalina.startup.TomcatBaseTest; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.DERUTF8String; +import org.bouncycastle.asn1.x509.BasicConstraints; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.KeyUsage; +import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils; + +public class TestLargeClientHello extends TomcatBaseTest { + + // https://bz.apache.org/bugzilla/show_bug.cgi?id=67938 + @Test + public void testLargeClientHelloWithSessionResumption() throws Exception { + File keystoreFile = TesterSupport.generateKeystore("localhost", "tomcat", + new String[]{"localhost", "*.localhost"}, + (keyPair, certBuilder) -> { + JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils(); + certBuilder.addExtension(Extension.subjectKeyIdentifier, false, + extUtils.createSubjectKeyIdentifier(keyPair.getPublic())); + certBuilder.addExtension(Extension.authorityKeyIdentifier, false, + extUtils.createAuthorityKeyIdentifier(keyPair.getPublic())); + certBuilder.addExtension(Extension.basicConstraints, true, + new BasicConstraints(true)); + certBuilder.addExtension(Extension.keyUsage, false, + new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment)); + certBuilder.addExtension(new ASN1ObjectIdentifier("2.999"), false, + new DERUTF8String("x".repeat(16922))); + }); + + Tomcat tomcat = getTomcatInstance(); + + Context ctx = getProgrammaticRootContext(); + Tomcat.addServlet(ctx, "hello", new HelloWorldServlet()); + ctx.addServletMappingDecoded("/", "hello"); + + TesterSupport.initSsl(tomcat, keystoreFile.getAbsolutePath(), false); + + try (LogCapture logCapture = attachLogCapture(Level.FINE, "org.apache.tomcat.util.net.SecureNioChannel")) { + + tomcat.start(); + + 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()); + + String url = "https://localhost:" + getPort() + "/"; + Assert.assertTrue(getUrl(url).toString().contains("Hello World")); + Assert.assertTrue(getUrl(url).toString().contains("Hello World")); + + Assert.assertTrue(logCapture.containsText( + TomcatBaseTest.getKeyFromPropertiesFile("org.apache.tomcat.util.net", + "channel.nio.ssl.handshakeUnwrapBufferUnderflow"))); + } + + } +} From 840c18370183b3803c38b0aa227cf5799a7a614a Mon Sep 17 00:00:00 2001 From: Dimitris Soumis Date: Fri, 27 Mar 2026 20:46:19 +0200 Subject: [PATCH 6/6] Move generateKeystore() from TesterSupport to independent TesterKeystoreGenerator class --- .../tomcat/util/net/TestLargeClientHello.java | 2 +- .../util/net/TesterKeystoreGenerator.java | 103 ++++++++++++++++++ .../apache/tomcat/util/net/TesterSupport.java | 76 ------------- 3 files changed, 104 insertions(+), 77 deletions(-) create mode 100644 test/org/apache/tomcat/util/net/TesterKeystoreGenerator.java diff --git a/test/org/apache/tomcat/util/net/TestLargeClientHello.java b/test/org/apache/tomcat/util/net/TestLargeClientHello.java index c476fe0a2417..43370f9c5450 100644 --- a/test/org/apache/tomcat/util/net/TestLargeClientHello.java +++ b/test/org/apache/tomcat/util/net/TestLargeClientHello.java @@ -41,7 +41,7 @@ public class TestLargeClientHello extends TomcatBaseTest { // https://bz.apache.org/bugzilla/show_bug.cgi?id=67938 @Test public void testLargeClientHelloWithSessionResumption() throws Exception { - File keystoreFile = TesterSupport.generateKeystore("localhost", "tomcat", + File keystoreFile = TesterKeystoreGenerator.generateKeystore("localhost", "tomcat", new String[]{"localhost", "*.localhost"}, (keyPair, certBuilder) -> { JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils(); diff --git a/test/org/apache/tomcat/util/net/TesterKeystoreGenerator.java b/test/org/apache/tomcat/util/net/TesterKeystoreGenerator.java new file mode 100644 index 000000000000..9fd4affde611 --- /dev/null +++ b/test/org/apache/tomcat/util/net/TesterKeystoreGenerator.java @@ -0,0 +1,103 @@ +/* + * 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.io.FileOutputStream; +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.cert.X509Certificate; +import java.util.Date; + +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x509.Extension; +import org.bouncycastle.asn1.x509.GeneralName; +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.operator.ContentSigner; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; + +public final class TesterKeystoreGenerator { + + private TesterKeystoreGenerator() {} + + @FunctionalInterface + public interface CertificateExtensionsCustomizer { + void customize(KeyPair keyPair, X509v3CertificateBuilder certBuilder) + throws Exception; + } + + /** + * Generate a temporary JKS keystore containing a self-signed RSA certificate. + * + * @param cn the Common Name for the certificate subject + * @param alias the keystore alias for the key entry + * @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 temporary keystore file with password {@link TesterSupport#JKS_PASS} + * + * @throws Exception if certificate generation or keystore creation fails + */ + public static File generateKeystore(String cn, String alias, String[] sanNames, + CertificateExtensionsCustomizer customizer) throws Exception { + KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); + kpg.initialize(4096); + KeyPair keyPair = kpg.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("SHA256withRSA").build(keyPair.getPrivate()); + X509Certificate certificate = new JcaX509CertificateConverter().getCertificate(certBuilder.build(signer)); + + KeyStore ks = KeyStore.getInstance("JKS"); + ks.load(null, null); + ks.setKeyEntry(alias, keyPair.getPrivate(), TesterSupport.JKS_PASS.toCharArray(), new X509Certificate[] { certificate }); + + File keystoreFile = File.createTempFile("test-cert-", ".jks"); + keystoreFile.deleteOnExit(); + try (FileOutputStream fos = new FileOutputStream(keystoreFile)) { + ks.store(fos, TesterSupport.JKS_PASS.toCharArray()); + } + + return keystoreFile; + } +} diff --git a/test/org/apache/tomcat/util/net/TesterSupport.java b/test/org/apache/tomcat/util/net/TesterSupport.java index 31a1c73240b5..9c3e0d209525 100644 --- a/test/org/apache/tomcat/util/net/TesterSupport.java +++ b/test/org/apache/tomcat/util/net/TesterSupport.java @@ -18,15 +18,11 @@ import java.io.File; import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.math.BigInteger; import java.net.InetAddress; import java.net.Socket; import java.net.UnknownHostException; -import java.security.KeyPair; -import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; @@ -40,7 +36,6 @@ import java.security.cert.TrustAnchor; import java.security.cert.X509CertSelector; import java.security.cert.X509Certificate; -import java.util.Date; import java.util.EnumSet; import java.util.Enumeration; import java.util.HashSet; @@ -84,15 +79,6 @@ import org.apache.tomcat.util.net.jsse.JSSEImplementation; import org.apache.tomcat.util.net.openssl.OpenSSLImplementation; import org.apache.tomcat.util.net.openssl.OpenSSLStatus; -import org.bouncycastle.asn1.x500.X500Name; -import org.bouncycastle.asn1.x509.Extension; -import org.bouncycastle.asn1.x509.GeneralName; -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.operator.ContentSigner; -import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; public final class TesterSupport { @@ -426,68 +412,6 @@ protected static boolean checkLastClientAuthRequestedIssuers() { new X500Principal(lastRequestedIssuers[0].getName())); } - @FunctionalInterface - public interface CertificateExtensionsCustomizer { - void customize(KeyPair keyPair, X509v3CertificateBuilder certBuilder) - throws Exception; - } - - /** - * Generate a temporary JKS keystore containing a self-signed RSA certificate. - * - * @param cn the Common Name for the certificate subject - * @param alias the keystore alias for the key entry - * @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 temporary keystore file with password {@link #JKS_PASS} - * - * @throws Exception if certificate generation or keystore creation fails - */ - public static File generateKeystore(String cn, String alias, String[] sanNames, - CertificateExtensionsCustomizer customizer) throws Exception { - KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); - kpg.initialize(4096); - KeyPair keyPair = kpg.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("SHA256withRSA").build(keyPair.getPrivate()); - X509Certificate certificate = new JcaX509CertificateConverter().getCertificate(certBuilder.build(signer)); - - KeyStore ks = KeyStore.getInstance("JKS"); - ks.load(null, null); - ks.setKeyEntry(alias, keyPair.getPrivate(), JKS_PASS.toCharArray(), new X509Certificate[] { certificate }); - - File keystoreFile = File.createTempFile("test-cert-", ".jks"); - keystoreFile.deleteOnExit(); - try (FileOutputStream fos = new FileOutputStream(keystoreFile)) { - ks.store(fos, JKS_PASS.toCharArray()); - } - - return keystoreFile; - } - - public static final byte DATA = (byte)33; public static class SimpleServlet extends HttpServlet {