Skip to content

Commit

Permalink
fix: restore OCSP support
Browse files Browse the repository at this point in the history
  • Loading branch information
NiccoMlt committed Oct 9, 2024
1 parent 46c16f8 commit 16c6b5c
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,11 @@ private void bootListener(final NetworkListenerConfiguration config) throws Inte
.protocol(config.protocols().toArray(HttpProtocol[]::new));
if (config.ssl()) {
httpServer = httpServer.secure(sslContextSpec -> {
final var sslContextBuilder = sslContextSpec.sslContext(listeningChannel.defaultSslContext());
final var sslCtx = listeningChannel.defaultSslContext();
final var sslContextBuilder = sslContextSpec.sslContext(sslCtx);
if (listeningChannel.isOcspEnabled()) {
sslContextBuilder.handlerConfigurator(new OcspSslHandler(sslCtx, parent.getOcspStaplingManager()));
}
for (final var certificate : this.currentConfiguration.getCertificates().values()) {
final var id = certificate.getId();
listeningChannel.applySslContext(id, sslContextBuilder);
Expand All @@ -215,11 +219,6 @@ private void bootListener(final NetworkListenerConfiguration config) throws Inte
.doOnChannelInit((observer, channel, remoteAddress) -> {
final var idle = new IdleStateHandler(0, 0, currentConfiguration.getClientsIdleTimeoutSeconds());
channel.pipeline().addFirst("idleStateHandler", idle);
// todo re-enable OCSP
/* if (config.ssl()) {
final var sni = new ListenersSniHandler(currentConfiguration, parent, listeningChannel);
channel.pipeline().addFirst(sni);
} */
})
.doOnConnection(conn -> {
CURRENT_CONNECTED_CLIENTS_GAUGE.inc();
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -211,9 +211,17 @@ public void applySslContext(final String sniHostname, final reactor.netty.tcp.Ss
try {
// #map should cache the certificate after the first search of the different SANs of the same certificate
final SslContext sslContext = map(sniHostname);
if (isOcspEnabled()) {
sslContextBuilder.handlerConfigurator(new OcspSslHandler(sslContext, parent.getOcspStaplingManager()));
}
sslContextBuilder.addSniMapping(sniHostname, spec -> spec.sslContext(sslContext));
} catch (final ConfigurationNotValidException e) {
throw new RuntimeException(e);
}
}

public boolean isOcspEnabled() {
return currentConfiguration.isOcspEnabled() && OpenSsl.isOcspSupported();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.carapaceproxy.core;

import io.netty.handler.ssl.ReferenceCountedOpenSslEngine;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslHandler;
import io.netty.util.AttributeKey;
import java.io.IOException;
import java.security.cert.Certificate;
import java.util.function.Consumer;
import org.carapaceproxy.server.certificates.ocsp.OcspStaplingManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class OcspSslHandler implements Consumer<SslHandler> {
private static final Logger LOG = LoggerFactory.getLogger(OcspSslHandler.class);
private static final AttributeKey<Certificate> ATTRIBUTE = AttributeKey.valueOf(Listeners.OCSP_CERTIFICATE_CHAIN);

private final SslContext sslContext;
private final OcspStaplingManager ocspStaplingManager;

public OcspSslHandler(final SslContext sslContext, final OcspStaplingManager ocspStaplingManager1) {
this.sslContext = sslContext;
this.ocspStaplingManager = ocspStaplingManager1;
}

@Override
public void accept(final SslHandler sslHandler) {
final var cert = sslContext.attributes().attr(ATTRIBUTE).get();
if (cert == null) {
LOG.error("Cannot set OCSP response without the certificate");
return;
}
try {
final var engine = (ReferenceCountedOpenSslEngine) sslHandler.engine();
engine.setOcspResponse(ocspStaplingManager.getOcspResponseForCertificate(cert));
} catch (IOException ex) {
LOG.error("Error setting OCSP response.", ex);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import static org.carapaceproxy.server.config.NetworkListenerConfiguration.DEFAULT_SSL_PROTOCOLS;
import static org.carapaceproxy.server.config.SSLCertificateConfiguration.CertificateMode.STATIC;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static reactor.netty.http.HttpProtocol.HTTP11;
Expand Down Expand Up @@ -74,7 +75,7 @@ public void testSelectCertWithoutSNI() throws Exception {
TestEndpointMapper mapper = new TestEndpointMapper("localhost", wireMockRule.port(), true);
wireMockRule.port();

try (HttpProxyServer server = new HttpProxyServer(mapper, tmpDir.getRoot());) {
try (HttpProxyServer server = new HttpProxyServer(mapper, tmpDir.getRoot())) {
server.addCertificate(new SSLCertificateConfiguration(nonLocalhost, null, certificate, "testproxy", STATIC));
server.addListener(new NetworkListenerConfiguration(nonLocalhost, 0, true, null, nonLocalhost /* default */, DEFAULT_SSL_PROTOCOLS, 128, true, 300, 60, 8, 1000, DEFAULT_FORWARDED_STRATEGY, Set.of(), Set.of(HTTP11), new DefaultChannelGroup(new DefaultEventExecutor())));
server.start();
Expand All @@ -93,7 +94,7 @@ public void testSelectCertWithoutSNI() throws Exception {
public void testChooseCertificate() throws Exception {
TestEndpointMapper mapper = new TestEndpointMapper("localhost", wireMockRule.port(), true);

try (HttpProxyServer server = new HttpProxyServer(mapper, tmpDir.getRoot());) {
try (HttpProxyServer server = new HttpProxyServer(mapper, tmpDir.getRoot())) {

server.addCertificate(new SSLCertificateConfiguration("other", null, "cert", "pwd", STATIC));
server.addCertificate(new SSLCertificateConfiguration("*.example.com", Set.of("example.com", "*.example2.com"), "cert", "pwd", STATIC));
Expand All @@ -103,53 +104,63 @@ public void testChooseCertificate() throws Exception {


// client requests bad SNI, bad default in listener
assertNull(CertificatesUtils.chooseCertificate(server.getListeners().getCurrentConfiguration(), "no", "no-default"));
assertNull(chooseCert(server, "no", "no-default"));

assertEquals("*.qatest.pexample.it", CertificatesUtils.chooseCertificate(server.getListeners().getCurrentConfiguration(), "test2.qatest.pexample.it", "no-default").getId());
assertEquals("*.qatest.pexample.it", chooseCertId(server, "test2.qatest.pexample.it", "no-default"));
// client requests SNI, bad default in listener
assertEquals("other", CertificatesUtils.chooseCertificate(server.getListeners().getCurrentConfiguration(), "other", "no-default").getId());
assertEquals("other", chooseCertId(server, "other", "no-default"));

assertEquals("www.example.com", CertificatesUtils.chooseCertificate(server.getListeners().getCurrentConfiguration(), "unkn-other", "www.example.com").getId());
assertEquals("www.example.com", chooseCertId(server, "unkn-other", "www.example.com"));
// client without SNI
assertEquals("www.example.com", CertificatesUtils.chooseCertificate(server.getListeners().getCurrentConfiguration(), null, "www.example.com").getId());
assertEquals("www.example.com", chooseCertId(server, null, "www.example.com"));
// exact match
assertEquals("www.example.com", CertificatesUtils.chooseCertificate(server.getListeners().getCurrentConfiguration(), "www.example.com", "no-default").getId());
assertEquals("www.example.com", chooseCertId(server, "www.example.com", "no-default"));
// wildcard
assertEquals("*.example.com", CertificatesUtils.chooseCertificate(server.getListeners().getCurrentConfiguration(), "test.example.com", "no-default").getId());
assertEquals("*.example.com", chooseCertId(server, "test.example.com", "no-default"));
// san
assertEquals("*.example.com", CertificatesUtils.chooseCertificate(server.getListeners().getCurrentConfiguration(), "example.com", "no-default").getId());
assertEquals("*.example.com", CertificatesUtils.chooseCertificate(server.getListeners().getCurrentConfiguration(), "test.example2.com", "no-default").getId());
assertEquals("*.example.com", chooseCertId(server, "example.com", "no-default"));
assertEquals("*.example.com", chooseCertId(server, "test.example2.com", "no-default"));

// full wildcard
server.addCertificate(new SSLCertificateConfiguration("*", null, "cert", "pwd", STATIC));
// full wildcard has not to hide more specific wildcard one
assertEquals("*.example.com", CertificatesUtils.chooseCertificate(server.getListeners().getCurrentConfiguration(), "test.example.com", "no-default").getId());
assertEquals("*.example.com", chooseCertId(server, "test.example.com", "no-default"));
// san
assertEquals("*.example.com", CertificatesUtils.chooseCertificate(server.getListeners().getCurrentConfiguration(), "example.com", "no-default").getId());
assertEquals("*.example.com", CertificatesUtils.chooseCertificate(server.getListeners().getCurrentConfiguration(), "test.example2.com", "no-default").getId());
assertEquals("*.example.com", chooseCertId(server, "example.com", "no-default"));
assertEquals("*.example.com", chooseCertId(server, "test.example2.com", "no-default"));

// more specific wildcard
server.addCertificate(new SSLCertificateConfiguration("*.test.example.com", null, "cert", "pwd", STATIC));
// more specific wildcard has to hide less specific one (*.example.com)
assertEquals("*.test.example.com", CertificatesUtils.chooseCertificate(server.getListeners().getCurrentConfiguration(), "pippo.test.example.com", "no-default").getId());
assertEquals("*.test.example.com", chooseCertId(server, "pippo.test.example.com", "no-default"));
// san
assertEquals("*.example.com", CertificatesUtils.chooseCertificate(server.getListeners().getCurrentConfiguration(), "example.com", "no-default").getId());
assertEquals("*.example.com", CertificatesUtils.chooseCertificate(server.getListeners().getCurrentConfiguration(), "test.example2.com", "no-default").getId());
assertEquals("*.example.com", chooseCertId(server, "example.com", "no-default"));
assertEquals("*.example.com", chooseCertId(server, "test.example2.com", "no-default"));
}

try (HttpProxyServer server = new HttpProxyServer(mapper, tmpDir.getRoot());) {
try (HttpProxyServer server = new HttpProxyServer(mapper, tmpDir.getRoot())) {

// full wildcard
server.addCertificate(new SSLCertificateConfiguration("*", null, "cert", "pwd", STATIC));

assertEquals("*", CertificatesUtils.chooseCertificate(server.getListeners().getCurrentConfiguration(), null, "www.example.com").getId());
assertEquals("*", CertificatesUtils.chooseCertificate(server.getListeners().getCurrentConfiguration(), "www.example.com", null).getId());
assertEquals("*", CertificatesUtils.chooseCertificate(server.getListeners().getCurrentConfiguration(), null, null).getId());
assertEquals("*", CertificatesUtils.chooseCertificate(server.getListeners().getCurrentConfiguration(), "", null).getId());
assertEquals("*", CertificatesUtils.chooseCertificate(server.getListeners().getCurrentConfiguration(), null, "").getId());
assertEquals("*", chooseCertId(server, null, "www.example.com"));
assertEquals("*", chooseCertId(server, "www.example.com", null));
assertEquals("*", chooseCertId(server, null, null));
assertEquals("*", chooseCertId(server, "", null));
assertEquals("*", chooseCertId(server, null, ""));
}
}

private static SSLCertificateConfiguration chooseCert(final HttpProxyServer server, final String sniHostname, final String defaultCertificate) {
return CertificatesUtils.chooseCertificate(server.getListeners().getCurrentConfiguration(), sniHostname, defaultCertificate);
}

private static String chooseCertId(final HttpProxyServer server, final String sniHostname, final String defaultCertificate) {
final var certificate = chooseCert(server, sniHostname, defaultCertificate);
assertNotNull(certificate);

Check failure on line 160 in carapace-server/src/test/java/org/carapaceproxy/listeners/SSLSNITest.java

View workflow job for this annotation

GitHub Actions / Maven Tests

org.carapaceproxy.listeners.SSLSNITest ► testChooseCertificate

Failed test found in: carapace-server/target/surefire-reports/TEST-org.carapaceproxy.listeners.SSLSNITest.xml Error: java.lang.AssertionError
Raw output
java.lang.AssertionError
	at org.junit.Assert.fail(Assert.java:87)
	at org.junit.Assert.assertTrue(Assert.java:42)
	at org.junit.Assert.assertNotNull(Assert.java:713)
	at org.junit.Assert.assertNotNull(Assert.java:723)
	at org.carapaceproxy.listeners.SSLSNITest.chooseCertId(SSLSNITest.java:160)
	at org.carapaceproxy.listeners.SSLSNITest.testChooseCertificate(SSLSNITest.java:122)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:54)
	at com.github.tomakehurst.wiremock.junit.WireMockRule$1.evaluate(WireMockRule.java:67)
	at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
	at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
	at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
	at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
	at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
	at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:316)
	at org.apache.maven.surefire.junit4.JUnit4Provider.executeWithRerun(JUnit4Provider.java:240)
	at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:214)
	at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:155)
	at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:385)
	at org.apache.maven.surefire.booter.ForkedBooter.execute(ForkedBooter.java:162)
	at org.apache.maven.surefire.booter.ForkedBooter.run(ForkedBooter.java:507)
	at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:495)
return certificate.getId();
}

@Test
public void testTLSVersion() throws Exception {
String nonLocalhost = InetAddress.getLocalHost().getCanonicalHostName();
Expand Down

0 comments on commit 16c6b5c

Please sign in to comment.