Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@
package org.eclipse.jetty.client.ssl;

import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSession;
Expand Down Expand Up @@ -243,4 +247,46 @@ public void handshakeSucceeded(Event event)
assertEquals(HttpStatus.OK_200, response.getStatus());
assertTrue(handshakeLatch.await(10, TimeUnit.SECONDS));
}

@Test
public void testTrustManagerWrapperAccessToCertChain() throws Exception
{
// Track certificate chain seen during validation
AtomicReference<X509Certificate[]> seenCerts = new AtomicReference<>();

SslContextFactory.Server serverSSL = createServerSslContextFactory();
serverSSL.setNeedClientAuth(true);

// Wrap TrustManager to capture certificate chain during validation
serverSSL.setTrustManagerWrapper(delegate ->
new SslContextFactory.X509ExtendedTrustManagerWrapper(delegate)
{
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine)
throws CertificateException
{
// Capture the certificate chain before validation
seenCerts.set(chain);
super.checkClientTrusted(chain, authType, engine);
}
});

startServer(serverSSL, new EmptyServerHandler());

// Client presents a certificate
SslContextFactory.Client clientSSL = new SslContextFactory.Client(true);
clientSSL.setKeyStorePath("src/test/resources/client_keystore.p12");
clientSSL.setKeyStorePassword("storepwd");
startClient(clientSSL);

ContentResponse response = client.newRequest("https://localhost:" + connector.getLocalPort())
.timeout(5, TimeUnit.SECONDS)
.send();

assertEquals(HttpStatus.OK_200, response.getStatus());

// The wrapper should have captured the client certificate chain
assertNotNull(seenCerts.get());
assertTrue(seenCerts.get().length > 0);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.net.ssl.CertPathTrustManagerParameters;
Expand Down Expand Up @@ -2247,6 +2248,7 @@ public static class Server extends SslContextFactory implements SniX509ExtendedK
private boolean _wantClientAuth;
private boolean _sniRequired;
private SniX509ExtendedKeyManager.SniSelector _sniSelector;
private UnaryOperator<X509ExtendedTrustManager> _trustManagerWrapper;

public Server()
{
Expand Down Expand Up @@ -2423,6 +2425,54 @@ protected X509ExtendedKeyManager newSniX509ExtendedKeyManager(X509ExtendedKeyMan
{
return new SniX509ExtendedKeyManager(keyManager, this);
}

/**
* @return the custom function to wrap trust managers
*/
public UnaryOperator<X509ExtendedTrustManager> getTrustManagerWrapper()
{
return _trustManagerWrapper;
}

/**
* <p>Sets a custom function to wrap trust managers.</p>
* <p>This allows intercepting certificate validation to access
* certificate chains even when validation fails.</p>
*
* @param wrapper the wrapper function
*/
public void setTrustManagerWrapper(UnaryOperator<X509ExtendedTrustManager> wrapper)
{
_trustManagerWrapper = wrapper;
}

/**
* <p>Creates a new X509ExtendedTrustManager, possibly wrapping the given one.</p>
* <p>Subclasses may override to provide custom trust manager implementations.</p>
*
* @param trustManager the trust manager to wrap
* @return the (possibly wrapped) trust manager
*/
protected X509ExtendedTrustManager newX509ExtendedTrustManager(X509ExtendedTrustManager trustManager)
{
UnaryOperator<X509ExtendedTrustManager> wrapper = getTrustManagerWrapper();
return wrapper != null ? wrapper.apply(trustManager) : trustManager;
}

@Override
protected TrustManager[] getTrustManagers(KeyStore trustStore, Collection<? extends CRL> crls) throws Exception
{
TrustManager[] managers = super.getTrustManagers(trustStore, crls);
if (managers != null)
{
for (int idx = 0; idx < managers.length; idx++)
{
if (managers[idx] instanceof X509ExtendedTrustManager x509TrustManager)
managers[idx] = newX509ExtendedTrustManager(x509TrustManager);
}
}
return managers;
}
}

/**
Expand Down