Files
apache-tomcat-8.5.51/test/org/apache/tomcat/util/net/TesterSupport.java
2024-11-30 19:03:49 +08:00

699 lines
26 KiB
Java

/*
* 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.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Locale;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;
import javax.security.auth.x500.X500Principal;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.Assert;
import org.apache.catalina.Context;
import org.apache.catalina.authenticator.SSLAuthenticator;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.AprLifecycleListener;
import org.apache.catalina.core.StandardServer;
import org.apache.catalina.startup.TesterMapRealm;
import org.apache.catalina.startup.Tomcat;
import org.apache.tomcat.jni.Library;
import org.apache.tomcat.jni.LibraryNotFoundError;
import org.apache.tomcat.jni.SSL;
import org.apache.tomcat.util.compat.JreCompat;
import org.apache.tomcat.util.descriptor.web.LoginConfig;
import org.apache.tomcat.util.descriptor.web.SecurityCollection;
import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
import org.apache.tomcat.util.net.SSLHostConfigCertificate.Type;
public final class TesterSupport {
public static final String SSL_DIR = "test/org/apache/tomcat/util/net/";
public static final String CA_ALIAS = "ca";
public static final String CA_JKS = SSL_DIR + CA_ALIAS + ".jks";
public static final String CLIENT_ALIAS = "user1";
public static final String CLIENT_JKS = SSL_DIR + CLIENT_ALIAS + ".jks";
public static final String LOCALHOST_EC_JKS = SSL_DIR + "localhost-ec.jks";
public static final String LOCALHOST_RSA_JKS = SSL_DIR + "localhost-rsa.jks";
public static final String LOCALHOST_KEYPASS_JKS = SSL_DIR + "localhost-rsa-copy1.jks";
public static final String JKS_PASS = "changeit";
public static final String JKS_KEY_PASS = "tomcatpass";
public static final String CA_CERT_PEM = SSL_DIR + CA_ALIAS + "-cert.pem";
public static final String LOCALHOST_EC_CERT_PEM = SSL_DIR + "localhost-ec-cert.pem";
public static final String LOCALHOST_EC_KEY_PEM = SSL_DIR + "localhost-ec-key.pem";
public static final String LOCALHOST_RSA_CERT_PEM = SSL_DIR + "localhost-rsa-cert.pem";
public static final String LOCALHOST_RSA_KEY_PEM = SSL_DIR + "localhost-rsa-key.pem";
public static final boolean OPENSSL_AVAILABLE;
public static final int OPENSSL_VERSION;
public static final boolean TLSV13_AVAILABLE;
public static final String ROLE = "testrole";
private static String clientAuthExpectedIssuer;
private static String lastUsage = "NONE";
private static Principal[] lastRequestedIssuers = new Principal[0];
static {
boolean available = false;
int version = 0;
try {
Library.initialize(null);
available = true;
version = SSL.version();
Library.terminate();
} catch (Exception | LibraryNotFoundError ex) {
// Ignore
}
OPENSSL_AVAILABLE = available;
OPENSSL_VERSION = version;
available = false;
try {
SSLContext.getInstance(Constants.SSL_PROTO_TLSv1_3);
available = true;
} catch (NoSuchAlgorithmException ex) {
}
TLSV13_AVAILABLE = available;
}
public static boolean isOpensslAvailable() {
return OPENSSL_AVAILABLE;
}
public static int getOpensslVersion() {
return OPENSSL_VERSION;
}
public static boolean isTlsv13Available() {
return TLSV13_AVAILABLE;
}
public static void initSsl(Tomcat tomcat) {
initSsl(tomcat, LOCALHOST_RSA_JKS, null, null);
}
protected static void initSsl(Tomcat tomcat, String keystore,
String keystorePass, String keyPass) {
Connector connector = tomcat.getConnector();
connector.setSecure(true);
Assert.assertTrue(connector.setProperty("SSLEnabled", "true"));
SSLHostConfig sslHostConfig = new SSLHostConfig();
SSLHostConfigCertificate certificate = new SSLHostConfigCertificate(sslHostConfig, Type.UNDEFINED);
sslHostConfig.addCertificate(certificate);
connector.addSslHostConfig(sslHostConfig);
String protocol = tomcat.getConnector().getProtocolHandlerClassName();
if (!protocol.contains("Apr")) {
String sslImplementation = System.getProperty("tomcat.test.sslImplementation");
if (sslImplementation != null && !"${test.sslImplementation}".equals(sslImplementation)) {
StandardServer server = (StandardServer) tomcat.getServer();
AprLifecycleListener listener = new AprLifecycleListener();
listener.setSSLRandomSeed("/dev/urandom");
server.addLifecycleListener(listener);
connector.setAttribute("sslImplementationName", sslImplementation);
}
sslHostConfig.setSslProtocol("tls");
certificate.setCertificateKeystoreFile(new File(keystore).getAbsolutePath());
sslHostConfig.setTruststoreFile(new File(CA_JKS).getAbsolutePath());
if (keystorePass != null) {
certificate.setCertificateKeystorePassword(keystorePass);
}
if (keyPass != null) {
certificate.setCertificateKeyPassword(keyPass);
}
} else {
certificate.setCertificateFile(new File(LOCALHOST_RSA_CERT_PEM).getAbsolutePath());
certificate.setCertificateKeyFile(new File(LOCALHOST_RSA_KEY_PEM).getAbsolutePath());
sslHostConfig.setCaCertificateFile(new File(CA_CERT_PEM).getAbsolutePath());
}
}
protected static KeyManager[] getUser1KeyManagers() throws Exception {
KeyManagerFactory kmf = KeyManagerFactory.getInstance(
KeyManagerFactory.getDefaultAlgorithm());
kmf.init(getKeyStore(CLIENT_JKS), JKS_PASS.toCharArray());
KeyManager[] managers = kmf.getKeyManagers();
KeyManager manager;
for (int i=0; i < managers.length; i++) {
manager = managers[i];
if (manager instanceof X509ExtendedKeyManager) {
managers[i] = new TrackingExtendedKeyManager((X509ExtendedKeyManager)manager);
} else if (manager instanceof X509KeyManager) {
managers[i] = new TrackingKeyManager((X509KeyManager)manager);
}
}
return managers;
}
protected static TrustManager[] getTrustManagers() throws Exception {
TrustManagerFactory tmf = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
tmf.init(getKeyStore(CA_JKS));
return tmf.getTrustManagers();
}
protected static ClientSSLSocketFactory configureClientSsl() {
ClientSSLSocketFactory clientSSLSocketFactory = null;
try {
SSLContext sc = SSLContext.getInstance(Constants.SSL_PROTO_TLS);
sc.init(TesterSupport.getUser1KeyManagers(),
TesterSupport.getTrustManagers(),
null);
clientSSLSocketFactory = new ClientSSLSocketFactory(sc.getSocketFactory());
javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory(clientSSLSocketFactory);
} catch (Exception e) {
e.printStackTrace();
}
return clientSSLSocketFactory;
}
private static KeyStore getKeyStore(String keystore) throws Exception {
File keystoreFile = new File(keystore);
KeyStore ks = KeyStore.getInstance("JKS");
try (InputStream is = new FileInputStream(keystoreFile)) {
ks.load(is, JKS_PASS.toCharArray());
}
return ks;
}
protected static boolean isMacOs() {
return System.getProperty("os.name").toLowerCase(Locale.ENGLISH).startsWith("mac os x");
}
protected static boolean isRenegotiationSupported(Tomcat tomcat) {
String protocol = tomcat.getConnector().getProtocolHandlerClassName();
if (protocol.contains("Apr")) {
// Disabled by default in 1.1.20 windows binary (2010-07-27)
return false;
}
return true;
}
protected static boolean isClientRenegotiationSupported(Tomcat tomcat) {
String protocol = tomcat.getConnector().getProtocolHandlerClassName();
if (protocol.contains("Apr")) {
// Disabled by default in 1.1.20 windows binary (2010-07-27)
return false;
}
if (protocol.contains("NioProtocol") || (protocol.contains("Nio2Protocol") && isMacOs())) {
// Doesn't work on all platforms - see BZ 56448.
return false;
}
String sslImplementation = System.getProperty("tomcat.test.sslImplementation");
if (sslImplementation != null && !"${test.sslImplementation}".equals(sslImplementation)) {
// Assume custom SSL is not supporting this
return false;
}
return true;
}
protected static void configureClientCertContext(Tomcat tomcat) {
TesterSupport.initSsl(tomcat);
/* When running on Java 11, TLSv1.3 is enabled by default. The JSSE
* implementation of TLSv1.3 does not support
* certificateVerification="optional", a setting on which these tests
* depend.
* Java 7 does not enable TLSv1.1 or TLS1.2 by default
*
* Ensure these tests pass with all JREs from Java 7 onwards.
*/
if (JreCompat.isJre8Available()) {
tomcat.getConnector().findSslHostConfigs()[0].setProtocols(Constants.SSL_PROTO_TLSv1_2);
} else {
tomcat.getConnector().findSslHostConfigs()[0].setProtocols(Constants.SSL_PROTO_TLSv1);
}
// Need a web application with a protected and unprotected URL
// No file system docBase required
Context ctx = tomcat.addContext("", null);
Tomcat.addServlet(ctx, "simple", new SimpleServlet());
ctx.addServletMappingDecoded("/unprotected", "simple");
ctx.addServletMappingDecoded("/protected", "simple");
// Security constraints
SecurityCollection collection = new SecurityCollection();
collection.addPatternDecoded("/protected");
SecurityConstraint sc = new SecurityConstraint();
sc.addAuthRole(ROLE);
sc.addCollection(collection);
ctx.addConstraint(sc);
// Configure the Realm
TesterMapRealm realm = new TesterMapRealm();
// Get the CA subject the server should send us for client cert selection
try {
KeyStore ks = getKeyStore(CA_JKS);
X509Certificate cert = (X509Certificate)ks.getCertificate(CA_ALIAS);
clientAuthExpectedIssuer = cert.getSubjectDN().getName();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
String cn = "NOTFOUND";
try {
KeyStore ks = getKeyStore(CLIENT_JKS);
X509Certificate cert = (X509Certificate)ks.getCertificate(CLIENT_ALIAS);
cn = cert.getSubjectDN().getName();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
realm.addUser(cn, "not used");
realm.addUserRole(cn, ROLE);
ctx.setRealm(realm);
// Configure the authenticator
LoginConfig lc = new LoginConfig();
lc.setAuthMethod("CLIENT-CERT");
ctx.setLoginConfig(lc);
ctx.getPipeline().addValve(new SSLAuthenticator());
// Clear the tracking data
lastUsage = "NONE";
lastRequestedIssuers = new Principal[0];
}
protected static String getClientAuthExpectedIssuer() {
return clientAuthExpectedIssuer;
}
protected static void trackTrackingKeyManagers(@SuppressWarnings("unused") KeyManager wrapper,
@SuppressWarnings("unused") KeyManager wrapped, String usage, Principal[] issuers) {
lastUsage = usage;
lastRequestedIssuers = issuers;
}
protected static String getLastClientAuthKeyManagerUsage() {
return lastUsage;
}
protected static int getLastClientAuthRequestedIssuerCount() {
return lastRequestedIssuers == null ? 0 : lastRequestedIssuers.length;
}
protected static Principal getLastClientAuthRequestedIssuer(int index) {
return lastRequestedIssuers[index];
}
protected static boolean checkLastClientAuthRequestedIssuers() {
if (lastRequestedIssuers == null || lastRequestedIssuers.length != 1)
return false;
return (new X500Principal(clientAuthExpectedIssuer)).equals(
new X500Principal(lastRequestedIssuers[0].getName()));
}
public static final byte DATA = (byte)33;
public static class SimpleServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/plain");
resp.getWriter().print("OK");
if (req.isUserInRole(ROLE)) {
resp.getWriter().print("-" + ROLE);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// Swallow any request body
int read = 0;
int len = 0;
byte[] buffer = new byte[4096];
InputStream is = req.getInputStream();
boolean contentOK = true;
while (len > -1) {
len = is.read(buffer);
read = read + len;
for (int i=0; i<len && contentOK; i++) {
contentOK = (buffer[i] == DATA);
}
}
// len will have been -1 on last iteration
read++;
// Report the number of bytes read
resp.setContentType("text/plain");
if (contentOK)
resp.getWriter().print("OK-" + read);
else
resp.getWriter().print("CONTENT-MISMATCH-" + read);
}
}
public static class TrackingKeyManager implements X509KeyManager {
private X509KeyManager manager = null;
public TrackingKeyManager(X509KeyManager manager) {
this.manager = manager;
}
@Override
public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
trackTrackingKeyManagers(this, manager, "chooseClientAlias", issuers);
return manager.chooseClientAlias(keyType, issuers, socket);
}
@Override
public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
trackTrackingKeyManagers(this, manager, "chooseServerAlias", issuers);
return manager.chooseServerAlias(keyType, issuers, socket);
}
@Override
public X509Certificate[] getCertificateChain(String alias) {
return manager.getCertificateChain(alias);
}
@Override
public String[] getClientAliases(String keyType, Principal[] issuers) {
trackTrackingKeyManagers(this, manager, "getClientAliases", issuers);
return manager.getClientAliases(keyType, issuers);
}
@Override
public PrivateKey getPrivateKey(String alias) {
return manager.getPrivateKey(alias);
}
@Override
public String[] getServerAliases(String keyType, Principal[] issuers) {
trackTrackingKeyManagers(this, manager, "getServerAliases", issuers);
return manager.getServerAliases(keyType, issuers);
}
}
public static class TrackingExtendedKeyManager extends X509ExtendedKeyManager {
private X509ExtendedKeyManager manager = null;
public TrackingExtendedKeyManager(X509ExtendedKeyManager manager) {
this.manager = manager;
}
@Override
public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
trackTrackingKeyManagers(this, manager, "chooseClientAlias", issuers);
return manager.chooseClientAlias(keyType, issuers, socket);
}
@Override
public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
trackTrackingKeyManagers(this, manager, "chooseServerAlias", issuers);
return manager.chooseServerAlias(keyType, issuers, socket);
}
@Override
public X509Certificate[] getCertificateChain(String alias) {
return manager.getCertificateChain(alias);
}
@Override
public String[] getClientAliases(String keyType, Principal[] issuers) {
trackTrackingKeyManagers(this, manager, "getClientAliases", issuers);
return manager.getClientAliases(keyType, issuers);
}
@Override
public PrivateKey getPrivateKey(String alias) {
return manager.getPrivateKey(alias);
}
@Override
public String[] getServerAliases(String keyType, Principal[] issuers) {
trackTrackingKeyManagers(this, manager, "getServerAliases", issuers);
return manager.getServerAliases(keyType, issuers);
}
@Override
public String chooseEngineClientAlias(String[] keyType, Principal[] issuers, SSLEngine engine) {
trackTrackingKeyManagers(this, manager, "chooseEngineClientAlias", issuers);
return manager.chooseEngineClientAlias(keyType, issuers, engine);
}
@Override
public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine) {
trackTrackingKeyManagers(this, manager, "chooseEngineServerAlias", issuers);
return manager.chooseEngineServerAlias(keyType, issuers, engine);
}
}
public static class TrustAllCerts implements X509TrustManager {
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
@Override
public void checkClientTrusted(X509Certificate[] certs,
String authType) {
// NOOP - Trust everything
}
@Override
public void checkServerTrusted(X509Certificate[] certs,
String authType) {
// NOOP - Trust everything
}
}
public static class SequentialTrustManager implements X509TrustManager {
private static X509TrustManager[] tms;
private static X509Certificate[] certs;
static {
try {
TrustManager[] managers = getTrustManagers();
int mcount = 0;
int ccount = 0;
for (TrustManager tm : managers) {
if (tm instanceof X509TrustManager) {
mcount++;
ccount += ((X509TrustManager)tm).getAcceptedIssuers().length;
}
}
tms = new X509TrustManager[mcount];
certs = new X509Certificate[ccount];
mcount = 0;
ccount = 0;
for (TrustManager tm : managers) {
if (tm instanceof X509TrustManager) {
tms[mcount] = (X509TrustManager)tm;
mcount++;
for (X509Certificate cert : ((X509TrustManager)tm).getAcceptedIssuers()) {
certs[ccount] = cert;
ccount++;
}
}
}
} catch (Exception ex) {
tms = new X509TrustManager[1];
tms[0] = new TrustAllCerts();
certs = new X509Certificate[0];
}
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return certs;
}
@Override
public void checkClientTrusted(X509Certificate[] certs,
String authType) throws CertificateException {
boolean trust = false;
for (X509TrustManager tm : tms) {
try {
tm.checkClientTrusted(certs, authType);
trust = true;
} catch (CertificateException ex) {
// Ignore
}
}
if (!trust) {
throw new CertificateException();
}
}
@Override
public void checkServerTrusted(X509Certificate[] certs,
String authType) throws CertificateException {
boolean trust = false;
for (X509TrustManager tm : tms) {
try {
tm.checkServerTrusted(certs, authType);
trust = true;
} catch (CertificateException ex) {
// Ignore
}
}
if (!trust) {
throw new CertificateException();
}
}
}
public static class ClientSSLSocketFactory extends SSLSocketFactory {
private final SSLSocketFactory delegate;
private String[] ciphers = null;
public ClientSSLSocketFactory(SSLSocketFactory delegate) {
this.delegate = delegate;
}
/**
* Forces the use of the specified cipher.
*
* @param ciphers Array of standard JSSE names of ciphers to use
*/
public void setCipher(String[] ciphers) {
this.ciphers = ciphers;
}
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
Socket result = delegate.createSocket(s, host, port, autoClose);
reconfigureSocket(result);
return result;
}
@Override
public String[] getDefaultCipherSuites() {
return delegate.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return delegate.getSupportedCipherSuites();
}
@Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
Socket result = delegate.createSocket(host, port);
reconfigureSocket(result);
return result;
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
Socket result = delegate.createSocket(host, port);
reconfigureSocket(result);
return result;
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort)
throws IOException, UnknownHostException {
Socket result = delegate.createSocket(host, port, localHost, localPort);
reconfigureSocket(result);
return result;
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort)
throws IOException {
Socket result = delegate.createSocket(address, port, localAddress, localPort);
reconfigureSocket(result);
return result;
}
private Socket reconfigureSocket(Socket socket) {
if (ciphers != null) {
((SSLSocket) socket).setEnabledCipherSuites(ciphers);
}
return socket;
}
}
/*
* We want to use TLS 1.3 where we can but this requires TLS 1.3 to be
* supported on the client and the server.
*/
public static String getDefaultTLSProtocolForTesting(Connector connector) {
// Clients always use JSSE
if (!TLSV13_AVAILABLE) {
// Client doesn't support TLS 1.3 so we have to use TLS 1.2
return Constants.SSL_PROTO_TLSv1_2;
}
if (connector.getProtocolHandlerClassName().contains("Apr")) {
// APR connector so OpenSSL is used for TLS.
if (SSL.version() >= 0x1010100f) {
return Constants.SSL_PROTO_TLSv1_3;
} else {
return Constants.SSL_PROTO_TLSv1_2;
}
} else {
// NIO or NIO2. Tests do not use JSSE+OpenSSL so JSSE will be used.
// Due to check above, it is known that TLS 1.3 is available
return Constants.SSL_PROTO_TLSv1_3;
}
}
public static boolean isDefaultTLSProtocolForTesting13(Connector connector) {
return Constants.SSL_PROTO_TLSv1_3.equals(
TesterSupport.getDefaultTLSProtocolForTesting(connector));
}
}