diff --git a/carapace-server/src/main/java/org/carapaceproxy/core/DisposableChannelListener.java b/carapace-server/src/main/java/org/carapaceproxy/core/DisposableChannelListener.java index b22b5686b..cdcd11d63 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/core/DisposableChannelListener.java +++ b/carapace-server/src/main/java/org/carapaceproxy/core/DisposableChannelListener.java @@ -1,21 +1,29 @@ package org.carapaceproxy.core; +import static org.carapaceproxy.core.Listeners.OCSP_CERTIFICATE_CHAIN; import static reactor.netty.ConnectionObserver.State.CONNECTED; +import io.netty.buffer.ByteBufAllocator; import io.netty.channel.ChannelOption; import io.netty.channel.epoll.Epoll; import io.netty.channel.epoll.EpollChannelOption; import io.netty.channel.socket.nio.NioChannelOption; +import io.netty.handler.ssl.OpenSsl; +import io.netty.handler.ssl.ReferenceCountedOpenSslEngine; +import io.netty.handler.ssl.SniHandler; import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslHandler; import io.netty.handler.timeout.IdleStateHandler; -import java.io.File; +import io.netty.util.AttributeKey; +import java.io.IOException; import java.net.InetSocketAddress; +import java.security.cert.Certificate; import java.time.Duration; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.concurrent.ConcurrentMap; import java.util.function.Function; import jdk.net.ExtendedSocketOptions; -import org.carapaceproxy.core.ssl.SslContextConfigurator; +import org.carapaceproxy.core.ssl.SniMapper; import org.carapaceproxy.core.stats.ListenerStats; import org.carapaceproxy.server.config.HostPort; import org.carapaceproxy.server.config.NetworkListenerConfiguration; @@ -37,45 +45,35 @@ public class DisposableChannelListener { private final HttpProxyServer parent; private final HostPort hostPort; - private final NetworkListenerConfiguration configuration; + private final RuntimeServerConfiguration runtimeConfiguration; + private final NetworkListenerConfiguration listenerConfiguration; private final ListenerStats stats; private final ConcurrentMap sslContextsCache; private ListenerStats.StatCounter totalRequests; private DisposableChannel listeningChannel; - public DisposableChannelListener(final HostPort hostPort, final HttpProxyServer parent, final NetworkListenerConfiguration configuration, final ListenerStats stats, final ConcurrentMap sslContextsCache) { + public DisposableChannelListener(final HostPort hostPort, final HttpProxyServer parent, final RuntimeServerConfiguration runtimeConfiguration, final NetworkListenerConfiguration listenerConfiguration, final ListenerStats stats, final ConcurrentMap sslContextsCache) { this.parent = parent; this.hostPort = hostPort; - this.configuration = configuration; - // todo I think we need to address this at some point - // this.configuration = getCurrentConfiguration().getListener(hostPort); - // requireNonNull(this.configuration, "Parent server configuration doesn't define any listener for " + hostPort); + this.runtimeConfiguration = runtimeConfiguration; + this.listenerConfiguration = listenerConfiguration; this.sslContextsCache = sslContextsCache; this.listeningChannel = null; this.stats = stats; this.totalRequests = null; } - private RuntimeServerConfiguration getCurrentConfiguration() { - return parent.getCurrentConfiguration(); - } - public NetworkListenerConfiguration getConfig() { - return configuration; + return listenerConfiguration; } public HostPort getHostPort() { return hostPort; } - private File getBasePath() { - return parent.getBasePath(); - } - public void start() { final var hostPort = this.hostPort.offsetPort(parent.getListenersOffsetPort()); - final var config = this.configuration; - final var currentConfiguration = getCurrentConfiguration(); + final var config = this.listenerConfiguration; final var clients = stats.clients(); totalRequests = stats.requests(hostPort); LOG.info("Starting listener at {} ssl:{}", hostPort, config.isSsl()); @@ -83,8 +81,14 @@ public void start() { .host(hostPort.host()) .port(hostPort.port()) .protocol(config.getProtocols().toArray(HttpProtocol[]::new)); + final SniMapper sslProviderBuilder; if (config.isSsl()) { - httpServer = httpServer.secure(new SslContextConfigurator(parent, config, hostPort, sslContextsCache)); + sslProviderBuilder = new SniMapper( + parent, runtimeConfiguration, config, hostPort, sslContextsCache + ); + httpServer = httpServer.secure(sslProviderBuilder.sslContextSpecConsumer()); + } else { + sslProviderBuilder = null; } httpServer = httpServer .metrics(true, Function.identity()) @@ -103,15 +107,15 @@ public void start() { : NioChannelOption.of(ExtendedSocketOptions.TCP_KEEPCOUNT), config.getKeepAliveCount()) .maxKeepAliveRequests(config.getMaxKeepAliveRequests()) .doOnChannelInit((observer, channel, remoteAddress) -> { - final var handler = new IdleStateHandler(0, 0, currentConfiguration.getClientsIdleTimeoutSeconds()); + final var handler = new IdleStateHandler(0, 0, this.runtimeConfiguration.getClientsIdleTimeoutSeconds()); channel.pipeline().addFirst("idleStateHandler", handler); - /* // todo if (config.isSsl()) { - SniHandler sni = new SniHandler(listeningChannel) { + assert sslProviderBuilder != null; + SniHandler sni = new SniHandler(sslProviderBuilder.sslContextAsyncMapping()) { @Override protected SslHandler newSslHandler(SslContext context, ByteBufAllocator allocator) { SslHandler handler = super.newSslHandler(context, allocator); - if (currentConfiguration.isOcspEnabled() && OpenSsl.isOcspSupported()) { + if (runtimeConfiguration.isOcspEnabled() && OpenSsl.isOcspSupported()) { Certificate cert = (Certificate) context.attributes().attr(AttributeKey.valueOf(OCSP_CERTIFICATE_CHAIN)).get(); if (cert != null) { try { @@ -128,7 +132,7 @@ protected SslHandler newSslHandler(SslContext context, ByteBufAllocator allocato } }; channel.pipeline().addFirst(sni); - } */ + } }) .doOnConnection(conn -> { clients.increment(); @@ -140,7 +144,7 @@ protected SslHandler newSslHandler(SslContext context, ByteBufAllocator allocato UriCleanerHandler.INSTANCE.addToPipeline(connection.channel()); } }) - .httpRequestDecoder(option -> option.maxHeaderSize(currentConfiguration.getMaxHeaderSize())) + .httpRequestDecoder(option -> option.maxHeaderSize(this.runtimeConfiguration.getMaxHeaderSize())) .handle((request, response) -> { if (CarapaceLogger.isLoggingDebugEnabled()) { CarapaceLogger.debug("Receive request " + request.uri() @@ -151,11 +155,11 @@ protected SslHandler newSslHandler(SslContext context, ByteBufAllocator allocato ProxyRequest proxyRequest = new ProxyRequest(request, response, hostPort); return parent.getProxyRequestsManager().processRequest(proxyRequest); }); - if (currentConfiguration.getResponseCompressionThreshold() >= 0) { + if (this.runtimeConfiguration.getResponseCompressionThreshold() >= 0) { CarapaceLogger.debug("Response compression enabled with min size = {0} bytes for listener {1}", - currentConfiguration.getResponseCompressionThreshold(), hostPort + this.runtimeConfiguration.getResponseCompressionThreshold(), hostPort ); - httpServer = httpServer.compress(currentConfiguration.getResponseCompressionThreshold()); + httpServer = httpServer.compress(this.runtimeConfiguration.getResponseCompressionThreshold()); } else { CarapaceLogger.debug("Response compression disabled for listener {0}", hostPort); } @@ -173,7 +177,7 @@ protected SslHandler newSslHandler(SslContext context, ByteBufAllocator allocato public void stop() throws InterruptedException { if (this.isStarted()) { this.listeningChannel.disposeNow(Duration.ofSeconds(10)); - FutureMono.from(this.configuration.getGroup().close()).block(Duration.ofSeconds(10)); + FutureMono.from(this.listenerConfiguration.getGroup().close()).block(Duration.ofSeconds(10)); } } diff --git a/carapace-server/src/main/java/org/carapaceproxy/core/Listeners.java b/carapace-server/src/main/java/org/carapaceproxy/core/Listeners.java index 45742c43e..da3c0fa17 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/core/Listeners.java +++ b/carapace-server/src/main/java/org/carapaceproxy/core/Listeners.java @@ -156,7 +156,7 @@ void reloadConfiguration(final RuntimeServerConfiguration newConfiguration) thro private void bootListener(final HostPort hostPort, final NetworkListenerConfiguration configuration) { final var newListener = new DisposableChannelListener( - hostPort, this.parent, configuration, PrometheusListenerStats.INSTANCE, sslContexts + hostPort, parent, currentConfiguration, configuration, PrometheusListenerStats.INSTANCE, sslContexts ); listeningChannels.put(hostPort, newListener); newListener.start(); diff --git a/carapace-server/src/main/java/org/carapaceproxy/core/ssl/CertificatesUtils.java b/carapace-server/src/main/java/org/carapaceproxy/core/ssl/CertificatesUtils.java index aeaa6ef07..9699f9868 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/core/ssl/CertificatesUtils.java +++ b/carapace-server/src/main/java/org/carapaceproxy/core/ssl/CertificatesUtils.java @@ -19,16 +19,31 @@ */ package org.carapaceproxy.core.ssl; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; -import java.io.*; -import java.security.*; +import io.netty.handler.ssl.SslContext; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; -import java.util.*; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; +import org.carapaceproxy.server.config.HostPort; /** * Utilitis for Certificates storing as Keystores @@ -47,7 +62,13 @@ public final class CertificatesUtils { * @param chain to store into a keystore * @param key private key for the chain * @return keystore data - * @throws GeneralSecurityException + * @throws KeyStoreException if no provider supports a {@code KeyStoreSpi} implementation for the specified type, + * if the keystore has not been initialized, + * the given key cannot be protected, + * or this operation fails for some other reason + * @throws NoSuchAlgorithmException if the algorithm used to check the integrity of the keystore cannot be found + * @throws CertificateException if any of the certificates in the keystore could not be loaded + * @throws GeneralSecurityException if something else goes wrong, i.e., because of a {@link IOException} */ public static byte[] createKeystore(Certificate[] chain, PrivateKey key) throws GeneralSecurityException { try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { @@ -66,7 +87,11 @@ public static byte[] createKeystore(Certificate[] chain, PrivateKey key) throws * * @param data keystore data. * @return certificate chain contained into the keystore. - * @throws GeneralSecurityException + * @throws KeyStoreException if no provider supports a {@code KeyStoreSpi} implementation for the specified type, + * or if the keystore has not been initialized + * @throws NoSuchAlgorithmException if the algorithm used to check the integrity of the keystore cannot be found + * @throws CertificateException if any of the certificates in the keystore could not be loaded + * @throws GeneralSecurityException if something else goes wrong, i.e., because of a {@link IOException} */ public static Certificate[] readChainFromKeystore(byte[] data) throws GeneralSecurityException { try (ByteArrayInputStream is = new ByteArrayInputStream(data)) { @@ -84,7 +109,10 @@ public static Certificate[] readChainFromKeystore(byte[] data) throws GeneralSec * * @param keystore keystore. * @return certificate chain contained into the keystore. - * @throws GeneralSecurityException + * @throws KeyStoreException if no provider supports a {@code KeyStoreSpi} implementation for the specified type, + * or if the keystore has not been initialized + * @throws NoSuchAlgorithmException if the algorithm used to check the integrity of the keystore cannot be found + * @throws CertificateException if any of the certificates in the keystore could not be loaded */ public static Certificate[] readChainFromKeystore(KeyStore keystore) throws GeneralSecurityException { Iterator iter = keystore.aliases().asIterator(); @@ -100,7 +128,11 @@ public static Certificate[] readChainFromKeystore(KeyStore keystore) throws Gene /** * @param data keystore data. - * @return whether a valid keystore can be retrived from data. + * @return whether a valid keystore can be retrieved from data. + * @throws KeyStoreException if no provider supports a {@code KeyStoreSpi} implementation for the specified type, + * or if the keystore has not been initialized + * @throws NoSuchAlgorithmException if the algorithm used to check the integrity of the keystore cannot be found + * @throws CertificateException if any of the certificates in the keystore could not be loaded */ public static boolean validateKeystore(byte[] data) throws GeneralSecurityException { try (ByteArrayInputStream is = new ByteArrayInputStream(data)) { @@ -160,17 +192,20 @@ public static boolean isCertificateExpired(Date expiringDate, int daysBeforeRene /** * Extract certificate private key - * @param data Certificate data. + * + * @param data Certificate data. * @param password Private key password. * @return PrivateKey. - * @throws Exception + * @throws GeneralSecurityException if something goes wrong with the keystore + * @throws IOException if there is an I/O or format problem with the keystore data, + * if a password is required but not given, + * or if the given password was incorrect. */ - public static PrivateKey loadPrivateKey(byte[] data, String password) throws Exception { + public static PrivateKey loadPrivateKey(byte[] data, String password) throws GeneralSecurityException, IOException { KeyStore ks = KeyStore.getInstance(KEYSTORE_FORMAT); ks.load(new ByteArrayInputStream(data), password.trim().toCharArray()); String alias = ks.aliases().nextElement(); - PrivateKey key = (PrivateKey) ks.getKey(alias, password.trim().toCharArray()); - return key; + return (PrivateKey) ks.getKey(alias, password.trim().toCharArray()); } /** @@ -184,6 +219,9 @@ public static boolean compareChains(Certificate[] c1, Certificate[] c2) { if (c1 == null && c2 != null || c2 == null && c1 != null) { return false; } + if (c1 == null /* && c2 == null */) { + return true; + } if (c1.length != c2.length) { return false; } @@ -211,4 +249,15 @@ public static String addWildcard(String name) { return WILDCARD_PREFIX + Objects.requireNonNull(name); } + /** + * {@link SslContext}s are cached at listener level. + * This method computes the key from hostname, port, and SNI. + * + * @param hostPort + * @param sniHostname the Server Name Indication (SNI) indication + * @return the cache key + */ + public static String computeKey(final HostPort hostPort, final String sniHostname) { + return hostPort.host() + ":" + hostPort.port() + "+" + sniHostname; + } } diff --git a/carapace-server/src/main/java/org/carapaceproxy/core/ssl/SniMapper.java b/carapace-server/src/main/java/org/carapaceproxy/core/ssl/SniMapper.java index a9bbf353b..353821780 100644 --- a/carapace-server/src/main/java/org/carapaceproxy/core/ssl/SniMapper.java +++ b/carapace-server/src/main/java/org/carapaceproxy/core/ssl/SniMapper.java @@ -19,6 +19,8 @@ import java.security.cert.Certificate; import java.util.Arrays; import java.util.List; +import java.util.concurrent.ConcurrentMap; +import java.util.function.Consumer; import javax.net.ssl.KeyManagerFactory; import org.carapaceproxy.core.HttpProxyServer; import org.carapaceproxy.core.Listeners; @@ -30,44 +32,53 @@ import org.slf4j.LoggerFactory; import reactor.netty.tcp.SslProvider; -public final class SniMapper implements AsyncMapping { +public final class SniMapper { private static final Logger LOG = LoggerFactory.getLogger(SniMapper.class); private final HttpProxyServer parent; private final RuntimeServerConfiguration runtimeConfiguration; private final NetworkListenerConfiguration listenerConfiguration; private final HostPort hostPort; + private final ConcurrentMap sslContextsCache; public SniMapper( final HttpProxyServer parent, final RuntimeServerConfiguration runtimeConfiguration, final NetworkListenerConfiguration listenerConfiguration, - final HostPort hostPort + final HostPort hostPort, + final ConcurrentMap sslContextsCache ) { this.parent = parent; + /* + * todo I don't think we actually need to store these data that should already be in the `parent`... + * sadly, this breaks reload of configuration after replacing the ConfigurationStore; + * one problem at a time though, this should be a different GitHub issue! + */ this.runtimeConfiguration = runtimeConfiguration; this.listenerConfiguration = listenerConfiguration; this.hostPort = hostPort; + this.sslContextsCache = sslContextsCache; } - private boolean isEnableOcsp() { - return runtimeConfiguration.isOcspEnabled() && OpenSsl.isOcspSupported(); + public AsyncMapping sslContextAsyncMapping() { + return this::computeContext; } - private List getSslCiphers() { - final var sslCiphers = listenerConfiguration.getSslCiphers(); - if (sslCiphers != null && !sslCiphers.isEmpty()) { - LOG.debug("required sslCiphers {}", sslCiphers); - return Arrays.asList(sslCiphers.split(",")); + private Future computeContext(final String sniHostname, final Promise promise) { + try { + return promise.setSuccess(computeContext(sniHostname)); + } catch (ConfigurationNotValidException e) { + return promise.setFailure(e); } - return null; } - @Override - public Future map(final String sniHostname, final Promise promise) { + public SslContext computeContext(final String sniHostname) throws ConfigurationNotValidException { try { - final var key = computeKey(sniHostname); + final var key = CertificatesUtils.computeKey(hostPort, sniHostname); LOG.debug("resolve SNI mapping {}, key: {}", sniHostname, key); try { + if (sslContextsCache.containsKey(key)) { + return sslContextsCache.get(key); + } final var defaultCertificate = listenerConfiguration.getDefaultCertificate(); var chosen = Listeners.chooseCertificate(runtimeConfiguration, sniHostname, defaultCertificate); if (chosen == null) { @@ -87,7 +98,7 @@ public Future map(final String sniHostname, final Promise map(final String sniHostname, final Promise map(final String sniHostname, final Promise 0) { + // ... so, in addition to `.enableOcsp(isEnableOcsp())` we need to store some additional info, + // that will be inspected on channel init by our SNI handler; + // see DisposableChannelListener#start parent.getOcspStaplingManager().addCertificateForStapling(chain); Attribute attr = sslContext.attributes().attr(AttributeKey.valueOf(Listeners.OCSP_CERTIFICATE_CHAIN)); attr.set(chain[0]); } - return promise.setSuccess(SslProvider.builder().sslContext(sslContext).build()); + sslContextsCache.put(key, sslContext); + return sslContext; } catch (IOException | GeneralSecurityException err) { LOG.error("ERROR booting listener", err); - return promise.setFailure(new ConfigurationNotValidException(err)); + throw new ConfigurationNotValidException(err); } } catch (RuntimeException err) { if (err.getCause() instanceof ConfigurationNotValidException) { @@ -125,7 +140,7 @@ public Future map(final String sniHostname, final Promise getSslCiphers() { + final var sslCiphers = listenerConfiguration.getSslCiphers(); + if (sslCiphers != null && !sslCiphers.isEmpty()) { + LOG.debug("required sslCiphers {}", sslCiphers); + return Arrays.asList(sslCiphers.split(",")); + } + return null; + } + public Consumer sslContextSpecConsumer() { + return this::configureSpec; + } + + private void configureSpec(final SslProvider.SslContextSpec sslContextSpec) { + try { + final var defaultSslContext = this.computeContext(listenerConfiguration.getDefaultCertificate()); + sslContextSpec.sslContext(defaultSslContext).setSniAsyncMappings(this.sslProviderAsyncMapping()); + } catch (ConfigurationNotValidException e) { + throw new RuntimeException(e); + } + } + + public AsyncMapping sslProviderAsyncMapping() { + return this::computeProvider; + } + + private Future computeProvider(final String sniHostname, final Promise promise) { + try { + return promise.setSuccess(SslProvider.builder().sslContext(computeContext(sniHostname)).build()); + } catch (ConfigurationNotValidException e) { + return promise.setFailure(e); + } } } diff --git a/carapace-server/src/main/java/org/carapaceproxy/core/ssl/SslContextConfigurator.java b/carapace-server/src/main/java/org/carapaceproxy/core/ssl/SslContextConfigurator.java deleted file mode 100644 index c3f81ef92..000000000 --- a/carapace-server/src/main/java/org/carapaceproxy/core/ssl/SslContextConfigurator.java +++ /dev/null @@ -1,143 +0,0 @@ -package org.carapaceproxy.core.ssl; - -import static org.carapaceproxy.core.ssl.CertificatesUtils.loadKeyStoreData; -import static org.carapaceproxy.core.ssl.CertificatesUtils.loadKeyStoreFromFile; -import static org.carapaceproxy.core.ssl.CertificatesUtils.readChainFromKeystore; -import io.netty.handler.ssl.OpenSsl; -import io.netty.handler.ssl.OpenSslCachingX509KeyManagerFactory; -import io.netty.handler.ssl.SslContext; -import io.netty.handler.ssl.SslContextBuilder; -import io.netty.util.Attribute; -import io.netty.util.AttributeKey; -import java.io.File; -import java.io.IOException; -import java.security.GeneralSecurityException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableKeyException; -import java.security.cert.CertificateException; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.ConcurrentMap; -import java.util.function.Consumer; -import javax.annotation.Nullable; -import javax.net.ssl.KeyManagerFactory; -import org.carapaceproxy.core.HttpProxyServer; -import org.carapaceproxy.core.Listeners; -import org.carapaceproxy.core.RuntimeServerConfiguration; -import org.carapaceproxy.server.config.ConfigurationNotValidException; -import org.carapaceproxy.server.config.HostPort; -import org.carapaceproxy.server.config.NetworkListenerConfiguration; -import org.carapaceproxy.server.config.SSLCertificateConfiguration; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import reactor.netty.tcp.SslProvider; - -public final class SslContextConfigurator implements Consumer { - private static final Logger LOG = LoggerFactory.getLogger(SslContextConfigurator.class); - - private final HttpProxyServer parent; - private final RuntimeServerConfiguration runtimeConfiguration; - private final NetworkListenerConfiguration listenerConfiguration; - private final HostPort hostPort; - private final ConcurrentMap sslContextsCache; - - public SslContextConfigurator( - final HttpProxyServer parent, - final NetworkListenerConfiguration listenerConfiguration, - final HostPort hostPort, - final ConcurrentMap sslContextsCache - ) { - this.parent = parent; - this.runtimeConfiguration = parent.getCurrentConfiguration(); - this.listenerConfiguration = listenerConfiguration; - this.hostPort = hostPort; - this.sslContextsCache = sslContextsCache; - } - - @Override - public void accept(final SslProvider.SslContextSpec sslContextSpec) { - final SslContext sslContext; - try { - if (!listenerConfiguration.isSsl()) { - throw new ConfigurationNotValidException("SSL not enabled"); - } - final var defaultSslConfiguration = getDefaultSslConfiguration(); - if (defaultSslConfiguration == null) { - throw new ConfigurationNotValidException("Unable to boot SSL context for listener " + listenerConfiguration.getHost() + ": no default certificate setup."); - } - final var keyStore = getKeyStore(hostPort, defaultSslConfiguration); - // todo compute key and store into cache - sslContext = SslContextBuilder - .forServer(getKeyFactory(keyStore, defaultSslConfiguration)) - .enableOcsp(isEnableOcsp()) - .trustManager(parent.getTrustStoreManager().getTrustManagerFactory()) - .sslProvider(io.netty.handler.ssl.SslProvider.OPENSSL) - .protocols(listenerConfiguration.getSslProtocols()) - .ciphers(getSslCiphers()) - .build(); - final var chain = readChainFromKeystore(keyStore); - if (isEnableOcsp() && chain.length > 0) { - parent.getOcspStaplingManager().addCertificateForStapling(chain); - Attribute attr = sslContext.attributes().attr(AttributeKey.valueOf(Listeners.OCSP_CERTIFICATE_CHAIN)); - attr.set(chain[0]); - // todo i'm not sure if `.enableOcsp(isEnableOcsp())` and this part are enough, - // or if I should plug a `SniHandler` into the channel pipeline like we did in `Listeners` - } - } catch (final ConfigurationNotValidException | IOException | GeneralSecurityException e) { - throw new RuntimeException(e); - } - sslContextSpec.sslContext(sslContext).setSniAsyncMappings(new SniMapper(parent, runtimeConfiguration, listenerConfiguration, hostPort)); - } - - @Nullable - private SSLCertificateConfiguration getDefaultSslConfiguration() { - return runtimeConfiguration.getCertificates().get(getDefaultCertificate()); - } - - private KeyStore getKeyStore(final HostPort hostPort, final SSLCertificateConfiguration sslConfiguration) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, ConfigurationNotValidException, UnrecoverableKeyException { - final var keyStoreContent = getCertificateForDomain(sslConfiguration); - final KeyStore keyStore; - if (keyStoreContent != null) { - LOG.debug("start SSL with dynamic certificate id {}, on listener {}", sslConfiguration.getId(), hostPort); - keyStore = loadKeyStoreData(keyStoreContent, sslConfiguration.getPassword()); - } else { - LOG.debug("start SSL with certificate id {}, on listener {} file={}", sslConfiguration.getId(), hostPort, sslConfiguration.getFile()); - keyStore = loadKeyStoreFromFile(sslConfiguration.getFile(), sslConfiguration.getPassword(), getBasePath()); - } - return keyStore; - } - - private static KeyManagerFactory getKeyFactory(final KeyStore keyStore, final SSLCertificateConfiguration defaultSslConfiguration) throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException { - final var keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - final var keyFactoryInstance = new OpenSslCachingX509KeyManagerFactory(keyFactory); - keyFactoryInstance.init(keyStore, defaultSslConfiguration.getPassword().toCharArray()); - return keyFactoryInstance; - } - - private boolean isEnableOcsp() { - return runtimeConfiguration.isOcspEnabled() && OpenSsl.isOcspSupported(); - } - - private List getSslCiphers() { - final var sslCiphers = listenerConfiguration.getSslCiphers(); - if (sslCiphers != null && !sslCiphers.isEmpty()) { - LOG.debug("required sslCiphers {}", sslCiphers); - return Arrays.asList(sslCiphers.split(",")); - } - return null; - } - - private String getDefaultCertificate() { - return listenerConfiguration.getDefaultCertificate(); - } - - private byte[] getCertificateForDomain(final SSLCertificateConfiguration sslConfiguration) { - return parent.getDynamicCertificatesManager().getCertificateForDomain(sslConfiguration.getId()); - } - - private File getBasePath() { - return parent.getBasePath(); - } -}