package io.vertx.core.net.impl;
import io.netty.buffer.ByteBufAllocator;
import io.netty.handler.ssl.*;
import io.netty.util.Mapping;
import io.vertx.core.VertxException;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.ClientAuth;
import io.vertx.core.http.HttpClientOptions;
import io.vertx.core.http.HttpServerOptions;
import io.vertx.core.http.HttpVersion;
import io.vertx.core.impl.VertxInternal;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
import io.vertx.core.net.JdkSSLEngineOptions;
import io.vertx.core.net.KeyCertOptions;
import io.vertx.core.net.NetClientOptions;
import io.vertx.core.net.NetServerOptions;
import io.vertx.core.net.OpenSSLEngineOptions;
import io.vertx.core.net.SSLEngineOptions;
import io.vertx.core.net.SocketAddress;
import io.vertx.core.net.TCPSSLOptions;
import io.vertx.core.net.TrustOptions;
import javax.net.ssl.*;
import java.io.ByteArrayInputStream;
import java.security.cert.CRL;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class SSLHelper {
public static SSLEngineOptions resolveEngineOptions(TCPSSLOptions options) {
SSLEngineOptions engineOptions = options.getSslEngineOptions();
if (engineOptions == null) {
if (options.isUseAlpn()) {
if (JdkSSLEngineOptions.isAlpnAvailable()) {
engineOptions = new JdkSSLEngineOptions();
} else if (OpenSSLEngineOptions.isAlpnAvailable()) {
engineOptions = new OpenSSLEngineOptions();
}
}
}
if (engineOptions == null) {
engineOptions = new JdkSSLEngineOptions();
} else if (engineOptions instanceof OpenSSLEngineOptions) {
if (!OpenSsl.isAvailable()) {
VertxException ex = new VertxException("OpenSSL is not available");
Throwable cause = OpenSsl.unavailabilityCause();
if (cause != null) {
ex.initCause(cause);
}
throw ex;
}
}
if (options.isUseAlpn()) {
if (engineOptions instanceof JdkSSLEngineOptions) {
if (!JdkSSLEngineOptions.isAlpnAvailable()) {
throw new VertxException("ALPN not available for JDK SSL/TLS engine");
}
}
if (engineOptions instanceof OpenSSLEngineOptions) {
if (!OpenSSLEngineOptions.isAlpnAvailable()) {
throw new VertxException("ALPN is not available for OpenSSL SSL/TLS engine");
}
}
}
return engineOptions;
}
private static final Map<HttpVersion, String> PROTOCOL_NAME_MAPPING = new EnumMap<>(HttpVersion.class);
static {
PROTOCOL_NAME_MAPPING.put(HttpVersion.HTTP_2, "h2");
PROTOCOL_NAME_MAPPING.put(HttpVersion.HTTP_1_1, "http/1.1");
PROTOCOL_NAME_MAPPING.put(HttpVersion.HTTP_1_0, "http/1.0");
}
private static final Logger log = LoggerFactory.getLogger(SSLHelper.class);
private boolean ssl;
private boolean sni;
private long sslHandshakeTimeout;
private TimeUnit sslHandshakeTimeoutUnit;
private KeyCertOptions keyCertOptions;
private TrustOptions trustOptions;
private boolean trustAll;
private ArrayList<String> crlPaths;
private ArrayList<Buffer> crlValues;
private ClientAuth clientAuth = ClientAuth.NONE;
private Set<String> enabledCipherSuites;
private boolean openSsl;
private boolean client;
private boolean useAlpn;
private List<HttpVersion> applicationProtocols;
private Set<String> enabledProtocols;
private String endpointIdentificationAlgorithm = "";
private SslContext sslContext;
private Map<Certificate, SslContext> sslContextMap = new ConcurrentHashMap<>();
private boolean openSslSessionCacheEnabled = true;
public SSLHelper(HttpClientOptions options, KeyCertOptions keyCertOptions, TrustOptions trustOptions) {
SSLEngineOptions sslEngineOptions = resolveEngineOptions(options);
this.ssl = options.isSsl();
this.sslHandshakeTimeout = options.getSslHandshakeTimeout();
this.sslHandshakeTimeoutUnit = options.getSslHandshakeTimeoutUnit();
this.keyCertOptions = keyCertOptions;
this.trustOptions = trustOptions;
this.trustAll = options.isTrustAll();
this.crlPaths = new ArrayList<>(options.getCrlPaths());
this.crlValues = new ArrayList<>(options.getCrlValues());
this.enabledCipherSuites = options.getEnabledCipherSuites();
this.openSsl = sslEngineOptions instanceof OpenSSLEngineOptions;
this.client = true;
this.useAlpn = options.isUseAlpn();
this.enabledProtocols = options.getEnabledSecureTransportProtocols();
if (options.isVerifyHost()) {
this.endpointIdentificationAlgorithm = "HTTPS";
}
this.openSslSessionCacheEnabled = (sslEngineOptions instanceof OpenSSLEngineOptions) && ((OpenSSLEngineOptions) sslEngineOptions).isSessionCacheEnabled();
}
public SSLHelper(HttpServerOptions options, KeyCertOptions keyCertOptions, TrustOptions trustOptions) {
SSLEngineOptions sslEngineOptions = resolveEngineOptions(options);
this.ssl = options.isSsl();
this.sslHandshakeTimeout = options.getSslHandshakeTimeout();
this.sslHandshakeTimeoutUnit = options.getSslHandshakeTimeoutUnit();
this.keyCertOptions = keyCertOptions;
this.trustOptions = trustOptions;
this.clientAuth = options.getClientAuth();
this.crlPaths = options.getCrlPaths() != null ? new ArrayList<>(options.getCrlPaths()) : null;
this.crlValues = options.getCrlValues() != null ? new ArrayList<>(options.getCrlValues()) : null;
this.enabledCipherSuites = options.getEnabledCipherSuites();
this.openSsl = sslEngineOptions instanceof OpenSSLEngineOptions;
this.client = false;
this.useAlpn = options.isUseAlpn();
this.enabledProtocols = options.getEnabledSecureTransportProtocols();
this.openSslSessionCacheEnabled = (sslEngineOptions instanceof OpenSSLEngineOptions) && ((OpenSSLEngineOptions) sslEngineOptions).isSessionCacheEnabled();
this.sni = options.isSni();
}
public SSLHelper(NetClientOptions options, KeyCertOptions keyCertOptions, TrustOptions trustOptions) {
SSLEngineOptions sslEngineOptions = resolveEngineOptions(options);
this.ssl = options.isSsl();
this.sslHandshakeTimeout = options.getSslHandshakeTimeout();
this.sslHandshakeTimeoutUnit = options.getSslHandshakeTimeoutUnit();
this.keyCertOptions = keyCertOptions;
this.trustOptions = trustOptions;
this.trustAll = options.isTrustAll();
this.crlPaths = new ArrayList<>(options.getCrlPaths());
this.crlValues = new ArrayList<>(options.getCrlValues());
this.enabledCipherSuites = options.getEnabledCipherSuites();
this.openSsl = sslEngineOptions instanceof OpenSSLEngineOptions;
this.client = true;
this.useAlpn = false;
this.enabledProtocols = options.getEnabledSecureTransportProtocols();
this.endpointIdentificationAlgorithm = options.getHostnameVerificationAlgorithm();
this.openSslSessionCacheEnabled = (sslEngineOptions instanceof OpenSSLEngineOptions) && ((OpenSSLEngineOptions) sslEngineOptions).isSessionCacheEnabled();
}
public SSLHelper(NetServerOptions options, KeyCertOptions keyCertOptions, TrustOptions trustOptions) {
SSLEngineOptions sslEngineOptions = resolveEngineOptions(options);
this.ssl = options.isSsl();
this.sslHandshakeTimeout = options.getSslHandshakeTimeout();
this.sslHandshakeTimeoutUnit = options.getSslHandshakeTimeoutUnit();
this.keyCertOptions = keyCertOptions;
this.trustOptions = trustOptions;
this.clientAuth = options.getClientAuth();
this.crlPaths = options.getCrlPaths() != null ? new ArrayList<>(options.getCrlPaths()) : null;
this.crlValues = options.getCrlValues() != null ? new ArrayList<>(options.getCrlValues()) : null;
this.enabledCipherSuites = options.getEnabledCipherSuites();
this.openSsl = sslEngineOptions instanceof OpenSSLEngineOptions;
this.client = false;
this.useAlpn = false;
this.enabledProtocols = options.getEnabledSecureTransportProtocols();
this.openSslSessionCacheEnabled = (options.getSslEngineOptions() instanceof OpenSSLEngineOptions) && ((OpenSSLEngineOptions) options.getSslEngineOptions()).isSessionCacheEnabled();
this.sni = options.isSni();
}
public boolean isSSL() {
return ssl;
}
public boolean isSNI() {
return sni;
}
public long getSslHandshakeTimeout() {
return sslHandshakeTimeout;
}
public TimeUnit getSslHandshakeTimeoutUnit() {
return sslHandshakeTimeoutUnit;
}
public ClientAuth getClientAuth() {
return clientAuth;
}
public List<HttpVersion> getApplicationProtocols() {
return applicationProtocols;
}
public SSLHelper setApplicationProtocols(List<HttpVersion> applicationProtocols) {
this.applicationProtocols = applicationProtocols;
return this;
}
private SslContext createContext(VertxInternal vertx, X509KeyManager mgr, TrustManagerFactory trustMgrFactory) {
try {
SslContextBuilder builder;
if (client) {
builder = SslContextBuilder.forClient();
KeyManagerFactory keyMgrFactory = getKeyMgrFactory(vertx);
if (keyMgrFactory != null) {
builder.keyManager(keyMgrFactory);
}
} else {
if (mgr != null) {
builder = SslContextBuilder.forServer(mgr.getPrivateKey(null), null, mgr.getCertificateChain(null));
} else {
KeyManagerFactory keyMgrFactory = getKeyMgrFactory(vertx);
if (keyMgrFactory == null) {
throw new VertxException("Key/certificate is mandatory for SSL");
}
builder = SslContextBuilder.forServer(keyMgrFactory);
}
}
Collection<String> cipherSuites = enabledCipherSuites;
if (openSsl) {
builder.sslProvider(SslProvider.OPENSSL);
if (cipherSuites == null || cipherSuites.isEmpty()) {
cipherSuites = OpenSsl.availableOpenSslCipherSuites();
}
} else {
builder.sslProvider(SslProvider.JDK);
if (cipherSuites == null || cipherSuites.isEmpty()) {
cipherSuites = DefaultJDKCipherSuite.get();
}
}
if (trustMgrFactory != null) {
builder.trustManager(trustMgrFactory);
}
if (cipherSuites != null && cipherSuites.size() > 0) {
builder.ciphers(cipherSuites);
}
if (useAlpn && applicationProtocols != null && applicationProtocols.size() > 0) {
builder.applicationProtocolConfig(new ApplicationProtocolConfig(
ApplicationProtocolConfig.Protocol.ALPN,
ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE,
ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT,
applicationProtocols.stream().map(PROTOCOL_NAME_MAPPING::get).collect(Collectors.toList())
));
}
SslContext ctx = builder.build();
if (ctx instanceof OpenSslServerContext){
SSLSessionContext sslSessionContext = ctx.sessionContext();
if (sslSessionContext instanceof OpenSslServerSessionContext){
((OpenSslServerSessionContext)sslSessionContext).setSessionCacheEnabled(openSslSessionCacheEnabled);
}
}
return ctx;
} catch (Exception e) {
throw new VertxException(e);
}
}
private KeyManagerFactory getKeyMgrFactory(VertxInternal vertx) throws Exception {
return keyCertOptions == null ? null : keyCertOptions.getKeyManagerFactory(vertx);
}
private TrustManagerFactory getTrustMgrFactory(VertxInternal vertx, String serverName) throws Exception {
TrustManager[] mgrs = null;
if (trustAll) {
mgrs = new TrustManager[]{createTrustAllTrustManager()};
} else if (trustOptions != null) {
if (serverName != null) {
Function<String, TrustManager[]> mapper = trustOptions.trustManagerMapper(vertx);
if (mapper != null) {
mgrs = mapper.apply(serverName);
}
if (mgrs == null) {
TrustManagerFactory fact = trustOptions.getTrustManagerFactory(vertx);
if (fact != null) {
mgrs = fact.getTrustManagers();
}
}
} else {
TrustManagerFactory fact = trustOptions.getTrustManagerFactory(vertx);
if (fact != null) {
mgrs = fact.getTrustManagers();
}
}
}
if (mgrs == null) {
return null;
}
if (crlPaths != null && crlValues != null && (crlPaths.size() > 0 || crlValues.size() > 0)) {
Stream<Buffer> tmp = crlPaths.
stream().
map(path -> vertx.resolveFile(path).getAbsolutePath()).
map(vertx.fileSystem()::readFileBlocking);
tmp = Stream.concat(tmp, crlValues.stream());
CertificateFactory certificatefactory = CertificateFactory.getInstance("X.509");
ArrayList<CRL> crls = new ArrayList<>();
for (Buffer crlValue : tmp.collect(Collectors.toList())) {
crls.addAll(certificatefactory.generateCRLs(new ByteArrayInputStream(crlValue.getBytes())));
}
mgrs = createUntrustRevokedCertTrustManager(mgrs, crls);
}
return new VertxTrustManagerFactory(mgrs);
}
private static TrustManager[] createUntrustRevokedCertTrustManager(TrustManager[] trustMgrs, ArrayList<CRL> crls) {
trustMgrs = trustMgrs.clone();
for (int i = 0;i < trustMgrs.length;i++) {
TrustManager trustMgr = trustMgrs[i];
if (trustMgr instanceof X509TrustManager) {
X509TrustManager x509TrustManager = (X509TrustManager) trustMgr;
trustMgrs[i] = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
checkRevoked(x509Certificates);
x509TrustManager.checkClientTrusted(x509Certificates, s);
}
@Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
checkRevoked(x509Certificates);
x509TrustManager.checkServerTrusted(x509Certificates, s);
}
private void checkRevoked(X509Certificate[] x509Certificates) throws CertificateException {
for (X509Certificate cert : x509Certificates) {
for (CRL crl : crls) {
if (crl.isRevoked(cert)) {
throw new CertificateException("Certificate revoked");
}
}
}
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return x509TrustManager.getAcceptedIssuers();
}
};
}
}
return trustMgrs;
}
private static TrustManager createTrustAllTrustManager() {
return new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};
}
public Mapping<? super String, ? extends SslContext> serverNameMapper(VertxInternal vertx) {
return serverName -> {
SslContext ctx = getContext(vertx, serverName);
if (ctx != null) {
ctx = new DelegatingSslContext(ctx) {
@Override
protected void initEngine(SSLEngine engine) {
configureEngine(engine, serverName);
}
};
}
return ctx;
};
}
public void configureEngine(SSLEngine engine, String serverName) {
if (enabledCipherSuites != null && !enabledCipherSuites.isEmpty()) {
String[] toUse = enabledCipherSuites.toArray(new String[enabledCipherSuites.size()]);
engine.setEnabledCipherSuites(toUse);
}
engine.setUseClientMode(client);
Set<String> protocols = new LinkedHashSet<>(enabledProtocols);
protocols.retainAll(Arrays.asList(engine.getSupportedProtocols()));
if (protocols.isEmpty()) {
log.warn("no SSL/TLS protocols are enabled due to configuration restrictions");
}
engine.setEnabledProtocols(protocols.toArray(new String[protocols.size()]));
if (!client) {
switch (getClientAuth()) {
case REQUEST: {
engine.setWantClientAuth(true);
break;
}
case REQUIRED: {
engine.setNeedClientAuth(true);
break;
}
case NONE: {
engine.setNeedClientAuth(false);
break;
}
}
} else if (!endpointIdentificationAlgorithm.isEmpty()) {
SSLParameters sslParameters = engine.getSSLParameters();
sslParameters.setEndpointIdentificationAlgorithm(endpointIdentificationAlgorithm);
engine.setSSLParameters(sslParameters);
}
if (serverName != null) {
SSLParameters sslParameters = engine.getSSLParameters();
sslParameters.setServerNames(Collections.singletonList(new SNIHostName(serverName)));
engine.setSSLParameters(sslParameters);
}
}
public SslContext getContext(VertxInternal vertx) {
return getContext(vertx, null);
}
public SslContext getContext(VertxInternal vertx, String serverName) {
if (serverName == null) {
if (sslContext == null) {
TrustManagerFactory trustMgrFactory = null;
try {
trustMgrFactory = getTrustMgrFactory(vertx, null);
} catch (Exception e) {
throw new VertxException(e);
}
sslContext = createContext(vertx, null, trustMgrFactory);
}
return sslContext;
} else {
X509KeyManager mgr;
try {
mgr = keyCertOptions.keyManagerMapper(vertx).apply(serverName);
} catch (Exception e) {
throw new RuntimeException(e);
}
if (mgr == null) {
return sslContext;
}
try {
TrustManagerFactory trustMgrFactory = getTrustMgrFactory(vertx, serverName);
return sslContextMap.computeIfAbsent(mgr.getCertificateChain(null)[0], s -> createContext(vertx, mgr, trustMgrFactory));
} catch (Exception e) {
throw new VertxException(e);
}
}
}
public synchronized void validate(VertxInternal vertx) {
if (ssl) {
getContext(vertx, null);
}
}
public SSLEngine createEngine(SslContext sslContext) {
SSLEngine engine = sslContext.newEngine(ByteBufAllocator.DEFAULT);
configureEngine(engine, null);
return engine;
}
public SSLEngine createEngine(VertxInternal vertx, SocketAddress socketAddress, String serverName) {
SslContext context = getContext(vertx, null);
SSLEngine engine;
if (socketAddress.path() != null) {
engine = context.newEngine(ByteBufAllocator.DEFAULT);
} else {
engine = context.newEngine(ByteBufAllocator.DEFAULT, socketAddress.host(), socketAddress.port());
}
configureEngine(engine, serverName);
return engine;
}
public SSLEngine createEngine(VertxInternal vertx, String host, int port, boolean forceSNI) {
SSLEngine engine = getContext(vertx, null).newEngine(ByteBufAllocator.DEFAULT, host, port);
configureEngine(engine, forceSNI ? host : null);
return engine;
}
public SSLEngine createEngine(VertxInternal vertx, String host, int port) {
SSLEngine engine = getContext(vertx, null).newEngine(ByteBufAllocator.DEFAULT, host, port);
configureEngine(engine, null);
return engine;
}
public SSLEngine createEngine(VertxInternal vertx) {
SSLEngine engine = getContext(vertx, null).newEngine(ByteBufAllocator.DEFAULT);
configureEngine(engine, null);
return engine;
}
}