Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion BUILDING.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions java/org/apache/tomcat/util/net/LocalStrings.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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}]
Expand Down
3 changes: 3 additions & 0 deletions java/org/apache/tomcat/util/net/SecureNioChannel.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
40 changes: 40 additions & 0 deletions test/org/apache/catalina/authenticator/TestSSLAuthenticator.java
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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<String, List<String>> 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<String, List<String>> 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<String, List<String>> 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);
}

}
86 changes: 86 additions & 0 deletions test/org/apache/tomcat/util/net/TestLargeClientHello.java
Original file line number Diff line number Diff line change
@@ -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 = TesterKeystoreGenerator.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")));
}

}
}
103 changes: 103 additions & 0 deletions test/org/apache/tomcat/util/net/TesterKeystoreGenerator.java
Original file line number Diff line number Diff line change
@@ -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;
}
}