package org.apache.tomcat.util.net.openssl;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSessionContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.jni.CertificateVerifier;
import org.apache.tomcat.jni.Pool;
import org.apache.tomcat.jni.SSL;
import org.apache.tomcat.jni.SSLConf;
import org.apache.tomcat.jni.SSLContext;
import org.apache.tomcat.util.net.AbstractEndpoint;
import org.apache.tomcat.util.net.Constants;
import org.apache.tomcat.util.net.SSLHostConfig;
import org.apache.tomcat.util.net.SSLHostConfig.CertificateVerification;
import org.apache.tomcat.util.net.SSLHostConfigCertificate;
import org.apache.tomcat.util.net.SSLHostConfigCertificate.Type;
import org.apache.tomcat.util.res.StringManager;
public class OpenSSLContext implements org.apache.tomcat.util.net.SSLContext {
private static final Log log = LogFactory.getLog(OpenSSLContext.class);
private static final StringManager netSm = StringManager.getManager(AbstractEndpoint.class);
private static final StringManager sm = StringManager.getManager(OpenSSLContext.class);
private static final String defaultProtocol = "TLS";
private final SSLHostConfig sslHostConfig;
private final SSLHostConfigCertificate certificate;
private OpenSSLSessionContext sessionContext;
private X509TrustManager x509TrustManager;
private final List<String> negotiableProtocols;
private String enabledProtocol;
public String getEnabledProtocol() {
return enabledProtocol;
}
public void setEnabledProtocol(String protocol) {
enabledProtocol = (protocol == null) ? defaultProtocol : protocol;
}
private final long aprPool;
private final AtomicInteger aprPoolDestroyed = new AtomicInteger(0);
protected final long cctx;
protected final long ctx;
static final CertificateFactory X509_CERT_FACTORY;
private static final String BEGIN_KEY = "-----BEGIN PRIVATE KEY-----\n";
private static final Object END_KEY = "\n-----END PRIVATE KEY-----";
private boolean initialized = false;
static {
try {
X509_CERT_FACTORY = CertificateFactory.getInstance("X.509");
} catch (CertificateException e) {
throw new IllegalStateException(sm.getString("openssl.X509FactoryError"), e);
}
}
public OpenSSLContext(SSLHostConfigCertificate certificate, List<String> negotiableProtocols)
throws SSLException {
this.sslHostConfig = certificate.getSSLHostConfig();
this.certificate = certificate;
aprPool = Pool.create(0);
boolean success = false;
try {
OpenSSLConf openSslConf = sslHostConfig.getOpenSslConf();
if (openSslConf != null) {
try {
if (log.isDebugEnabled())
log.debug(sm.getString("openssl.makeConf"));
cctx = SSLConf.make(aprPool,
SSL.SSL_CONF_FLAG_FILE |
SSL.SSL_CONF_FLAG_SERVER |
SSL.SSL_CONF_FLAG_CERTIFICATE |
SSL.SSL_CONF_FLAG_SHOW_ERRORS);
} catch (Exception e) {
throw new SSLException(sm.getString("openssl.errMakeConf"), e);
}
} else {
cctx = 0;
}
sslHostConfig.setOpenSslConfContext(Long.valueOf(cctx));
int value = SSL.SSL_PROTOCOL_NONE;
for (String protocol : sslHostConfig.getEnabledProtocols()) {
if (Constants.SSL_PROTO_SSLv2Hello.equalsIgnoreCase(protocol)) {
} else if (Constants.SSL_PROTO_SSLv2.equalsIgnoreCase(protocol)) {
value |= SSL.SSL_PROTOCOL_SSLV2;
} else if (Constants.SSL_PROTO_SSLv3.equalsIgnoreCase(protocol)) {
value |= SSL.SSL_PROTOCOL_SSLV3;
} else if (Constants.SSL_PROTO_TLSv1.equalsIgnoreCase(protocol)) {
value |= SSL.SSL_PROTOCOL_TLSV1;
} else if (Constants.SSL_PROTO_TLSv1_1.equalsIgnoreCase(protocol)) {
value |= SSL.SSL_PROTOCOL_TLSV1_1;
} else if (Constants.SSL_PROTO_TLSv1_2.equalsIgnoreCase(protocol)) {
value |= SSL.SSL_PROTOCOL_TLSV1_2;
} else if (Constants.SSL_PROTO_TLSv1_3.equalsIgnoreCase(protocol)) {
value |= SSL.SSL_PROTOCOL_TLSV1_3;
} else if (Constants.SSL_PROTO_ALL.equalsIgnoreCase(protocol)) {
value |= SSL.SSL_PROTOCOL_ALL;
} else {
throw new Exception(netSm.getString(
"endpoint.apr.invalidSslProtocol", protocol));
}
}
try {
ctx = SSLContext.make(aprPool, value, SSL.SSL_MODE_SERVER);
} catch (Exception e) {
throw new Exception(
netSm.getString("endpoint.apr.failSslContextMake"), e);
}
this.negotiableProtocols = negotiableProtocols;
success = true;
} catch(Exception e) {
throw new SSLException(sm.getString("openssl.errorSSLCtxInit"), e);
} finally {
if (!success) {
destroy();
}
}
}
@Override
public synchronized void destroy() {
if (aprPoolDestroyed.compareAndSet(0, 1)) {
if (ctx != 0) {
SSLContext.free(ctx);
}
if (cctx != 0) {
SSLConf.free(cctx);
}
if (aprPool != 0) {
Pool.destroy(aprPool);
}
}
}
@Override
public synchronized void init(KeyManager[] kms, TrustManager[] tms, SecureRandom sr) {
if (initialized) {
log.warn(sm.getString("openssl.doubleInit"));
return;
}
try {
if (sslHostConfig.getInsecureRenegotiation()) {
SSLContext.setOptions(ctx, SSL.SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION);
} else {
SSLContext.clearOptions(ctx, SSL.SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION);
}
if (sslHostConfig.getHonorCipherOrder()) {
SSLContext.setOptions(ctx, SSL.SSL_OP_CIPHER_SERVER_PREFERENCE);
} else {
SSLContext.clearOptions(ctx, SSL.SSL_OP_CIPHER_SERVER_PREFERENCE);
}
if (sslHostConfig.getDisableCompression()) {
SSLContext.setOptions(ctx, SSL.SSL_OP_NO_COMPRESSION);
} else {
SSLContext.clearOptions(ctx, SSL.SSL_OP_NO_COMPRESSION);
}
if (sslHostConfig.getDisableSessionTickets()) {
SSLContext.setOptions(ctx, SSL.SSL_OP_NO_TICKET);
} else {
SSLContext.clearOptions(ctx, SSL.SSL_OP_NO_TICKET);
}
SSLContext.setCipherSuite(ctx, sslHostConfig.getCiphers());
if (certificate.getCertificateFile() == null) {
certificate.setCertificateKeyManager(OpenSSLUtil.chooseKeyManager(kms));
}
addCertificate(certificate);
int value = 0;
switch (sslHostConfig.getCertificateVerification()) {
case NONE:
value = SSL.SSL_CVERIFY_NONE;
break;
case OPTIONAL:
value = SSL.SSL_CVERIFY_OPTIONAL;
break;
case OPTIONAL_NO_CA:
value = SSL.SSL_CVERIFY_OPTIONAL_NO_CA;
break;
case REQUIRED:
value = SSL.SSL_CVERIFY_REQUIRE;
break;
}
SSLContext.setVerify(ctx, value, sslHostConfig.getCertificateVerificationDepth());
if (tms != null) {
x509TrustManager = chooseTrustManager(tms);
SSLContext.setCertVerifyCallback(ctx, new CertificateVerifier() {
@Override
public boolean verify(long ssl, byte[][] chain, String auth) {
X509Certificate[] peerCerts = certificates(chain);
try {
x509TrustManager.checkClientTrusted(peerCerts, auth);
return true;
} catch (Exception e) {
log.debug(sm.getString("openssl.certificateVerificationFailed"), e);
}
return false;
}
});
for (X509Certificate caCert : x509TrustManager.getAcceptedIssuers()) {
SSLContext.addClientCACertificateRaw(ctx, caCert.getEncoded());
if (log.isDebugEnabled())
log.debug(sm.getString("openssl.addedClientCaCert", caCert.toString()));
}
} else {
SSLContext.setCACertificate(ctx,
SSLHostConfig.adjustRelativePath(sslHostConfig.getCaCertificateFile()),
SSLHostConfig.adjustRelativePath(sslHostConfig.getCaCertificatePath()));
}
if (negotiableProtocols != null && negotiableProtocols.size() > 0) {
List<String> protocols = new ArrayList<>(negotiableProtocols);
protocols.add("http/1.1");
String[] protocolsArray = protocols.toArray(new String[0]);
SSLContext.setAlpnProtos(ctx, protocolsArray, SSL.SSL_SELECTOR_FAILURE_NO_ADVERTISE);
SSLContext.setNpnProtos(ctx, protocolsArray, SSL.SSL_SELECTOR_FAILURE_NO_ADVERTISE);
}
OpenSSLConf openSslConf = sslHostConfig.getOpenSslConf();
if (openSslConf != null && cctx != 0) {
if (log.isDebugEnabled())
log.debug(sm.getString("openssl.checkConf"));
try {
if (!openSslConf.check(cctx)) {
log.error(sm.getString("openssl.errCheckConf"));
throw new Exception(sm.getString("openssl.errCheckConf"));
}
} catch (Exception e) {
throw new Exception(sm.getString("openssl.errCheckConf"), e);
}
if (log.isDebugEnabled())
log.debug(sm.getString("openssl.applyConf"));
try {
if (!openSslConf.apply(cctx, ctx)) {
log.error(sm.getString("openssl.errApplyConf"));
throw new SSLException(sm.getString("openssl.errApplyConf"));
}
} catch (Exception e) {
throw new SSLException(sm.getString("openssl.errApplyConf"), e);
}
int opts = SSLContext.getOptions(ctx);
List<String> enabled = new ArrayList<>();
enabled.add(Constants.SSL_PROTO_SSLv2Hello);
if ((opts & SSL.SSL_OP_NO_TLSv1) == 0) {
enabled.add(Constants.SSL_PROTO_TLSv1);
}
if ((opts & SSL.SSL_OP_NO_TLSv1_1) == 0) {
enabled.add(Constants.SSL_PROTO_TLSv1_1);
}
if ((opts & SSL.SSL_OP_NO_TLSv1_2) == 0) {
enabled.add(Constants.SSL_PROTO_TLSv1_2);
}
if ((opts & SSL.SSL_OP_NO_SSLv2) == 0) {
enabled.add(Constants.SSL_PROTO_SSLv2);
}
if ((opts & SSL.SSL_OP_NO_SSLv3) == 0) {
enabled.add(Constants.SSL_PROTO_SSLv3);
}
sslHostConfig.setEnabledProtocols(
enabled.toArray(new String[0]));
sslHostConfig.setEnabledCiphers(SSLContext.getCiphers(ctx));
}
sessionContext = new OpenSSLSessionContext(this);
sessionContext.setSessionIdContext(SSLContext.DEFAULT_SESSION_ID_CONTEXT);
sslHostConfig.setOpenSslContext(Long.valueOf(ctx));
initialized = true;
} catch (Exception e) {
log.warn(sm.getString("openssl.errorSSLCtxInit"), e);
destroy();
}
}
public void addCertificate(SSLHostConfigCertificate certificate) throws Exception {
if (certificate.getCertificateFile() != null) {
SSLContext.setCertificate(ctx,
SSLHostConfig.adjustRelativePath(certificate.getCertificateFile()),
SSLHostConfig.adjustRelativePath(certificate.getCertificateKeyFile()),
certificate.getCertificateKeyPassword(), getCertificateIndex(certificate));
SSLContext.setCertificateChainFile(ctx,
SSLHostConfig.adjustRelativePath(certificate.getCertificateChainFile()), false);
SSLContext.setCARevocation(ctx,
SSLHostConfig.adjustRelativePath(
sslHostConfig.getCertificateRevocationListFile()),
SSLHostConfig.adjustRelativePath(
sslHostConfig.getCertificateRevocationListPath()));
} else {
String alias = certificate.getCertificateKeyAlias();
X509KeyManager x509KeyManager = certificate.getCertificateKeyManager();
if (alias == null) {
alias = "tomcat";
}
X509Certificate[] chain = x509KeyManager.getCertificateChain(alias);
if (chain == null) {
alias = findAlias(x509KeyManager, certificate);
chain = x509KeyManager.getCertificateChain(alias);
}
PrivateKey key = x509KeyManager.getPrivateKey(alias);
StringBuilder sb = new StringBuilder(BEGIN_KEY);
sb.append(Base64.getMimeEncoder(64, new byte[] {'\n'}).encodeToString(key.getEncoded()));
sb.append(END_KEY);
SSLContext.setCertificateRaw(ctx, chain[0].getEncoded(),
sb.toString().getBytes(StandardCharsets.US_ASCII),
getCertificateIndex(certificate));
for (int i = 1; i < chain.length; i++) {
SSLContext.addChainCertificateRaw(ctx, chain[i].getEncoded());
}
}
}
private static int getCertificateIndex(SSLHostConfigCertificate certificate) {
int result;
if (certificate.getType() == Type.RSA || certificate.getType() == Type.UNDEFINED) {
result = SSL.SSL_AIDX_RSA;
} else if (certificate.getType() == Type.EC) {
result = SSL.SSL_AIDX_ECC;
} else if (certificate.getType() == Type.DSA) {
result = SSL.SSL_AIDX_DSA;
} else {
result = SSL.SSL_AIDX_MAX;
}
return result;
}
private static String findAlias(X509KeyManager keyManager,
SSLHostConfigCertificate certificate) {
Type type = certificate.getType();
String result = null;
List<Type> candidateTypes = new ArrayList<>();
if (Type.UNDEFINED.equals(type)) {
candidateTypes.addAll(Arrays.asList(Type.values()));
candidateTypes.remove(Type.UNDEFINED);
} else {
candidateTypes.add(type);
}
Iterator<Type> iter = candidateTypes.iterator();
while (result == null && iter.hasNext()) {
result = keyManager.chooseServerAlias(iter.next().toString(), null, null);
}
return result;
}
private static X509TrustManager chooseTrustManager(TrustManager[] managers) {
for (TrustManager m : managers) {
if (m instanceof X509TrustManager) {
return (X509TrustManager) m;
}
}
throw new IllegalStateException(sm.getString("openssl.trustManagerMissing"));
}
private static X509Certificate[] certificates(byte[][] chain) {
X509Certificate[] peerCerts = new X509Certificate[chain.length];
for (int i = 0; i < peerCerts.length; i++) {
peerCerts[i] = new OpenSSLX509Certificate(chain[i]);
}
return peerCerts;
}
long getSSLContextID() {
return ctx;
}
@Override
public SSLSessionContext getServerSessionContext() {
return sessionContext;
}
@Override
public SSLEngine createSSLEngine() {
return new OpenSSLEngine(ctx, defaultProtocol, false, sessionContext,
(negotiableProtocols != null && negotiableProtocols.size() > 0), initialized,
sslHostConfig.getCertificateVerificationDepth(),
sslHostConfig.getCertificateVerification() == CertificateVerification.OPTIONAL_NO_CA);
}
@Override
public SSLServerSocketFactory getServerSocketFactory() {
throw new UnsupportedOperationException();
}
@Override
public SSLParameters getSupportedSSLParameters() {
throw new UnsupportedOperationException();
}
@Override
public X509Certificate[] getCertificateChain(String alias) {
X509Certificate[] chain = null;
X509KeyManager x509KeyManager = certificate.getCertificateKeyManager();
if (x509KeyManager != null) {
if (alias == null) {
alias = "tomcat";
}
chain = x509KeyManager.getCertificateChain(alias);
if (chain == null) {
alias = findAlias(x509KeyManager, certificate);
chain = x509KeyManager.getCertificateChain(alias);
}
}
return chain;
}
@Override
public X509Certificate[] getAcceptedIssuers() {
X509Certificate[] acceptedCerts = null;
if (x509TrustManager != null) {
acceptedCerts = x509TrustManager.getAcceptedIssuers();
}
return acceptedCerts;
}
@Override
protected void finalize() throws Throwable {
try {
destroy();
} finally {
super.finalize();
}
}
}