Skip to content

Conversation

@afarber
Copy link
Contributor

@afarber afarber commented Dec 25, 2025

Fixes #6067

Add setTrustManagerWrapper(UnaryOperator<X509ExtendedTrustManager>) to SslContextFactory.Server to allow intercepting certificate validation during TLS handshake.

This provides an API to access client certificates even when validation fails, without requiring subclassing.

  • Add getTrustManagerWrapper() / setTrustManagerWrapper() methods
  • Add newX509ExtendedTrustManager() protected method for subclass customization
  • Override getTrustManagers() to apply the wrapper
  • Add test demonstrating certificate chain capture during validation

@afarber
Copy link
Contributor Author

afarber commented Dec 25, 2025

Smoke tested by using expired self-signed certificate on Ubuntu 25.04:

Screenshot From 2025-12-25 19-40-55

@sbordet sbordet self-requested a review January 5, 2026 15:32
@sbordet sbordet moved this to 👀 In review in Jetty 12.1.7 Jan 5, 2026
@sbordet
Copy link
Contributor

sbordet commented Jan 5, 2026

@afarber can you make the image public? Seems private and I cannot access it to actually see the code. Thanks!

@joakime
Copy link
Contributor

joakime commented Jan 5, 2026

I downloaded and reincluded that image.

trustmanagerwrapper

@afarber
Copy link
Contributor Author

afarber commented Jan 5, 2026

Hi @sbordet here my test file: TrustWrapperDemo.java

import java.security.cert.*;
import javax.net.ssl.*;
import org.eclipse.jetty.server.*;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.ssl.*;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.http.HttpHeader;
import java.nio.ByteBuffer;

public class TrustWrapperDemo {
    public static void main(String[] args) throws Exception {
        Server server = new Server();

        SslContextFactory.Server ssl = new SslContextFactory.Server();
        ssl.setKeyStorePath("jetty-core/jetty-client/src/test/resources/keystore.p12");
        ssl.setKeyStorePassword("storepwd");
        ssl.setNeedClientAuth(true);
        ssl.setTrustAll(true);

        ssl.setTrustManagerWrapper(delegate ->
            new SslContextFactory.X509ExtendedTrustManagerWrapper(delegate) {
                @Override
                public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine)
                    throws CertificateException {
                    System.out.println("=== TrustManagerWrapper captured certificate chain ===");
                    System.out.println("Chain length: " + chain.length);
                    for (int i = 0; i < chain.length; i++) {
                        System.out.println("Certificate [" + i + "]:");
                        System.out.println("Subject: " + chain[i].getSubjectX500Principal());
                        System.out.println("Issuer: " + chain[i].getIssuerX500Principal());
                        System.out.println("Valid: " + chain[i].getNotBefore() + " to " + chain[i].getNotAfter());
                        try {
                            chain[i].checkValidity();
                            System.out.println("Status: VALID");
                        } catch (CertificateExpiredException e) {
                            System.out.println("Status: EXPIRED");
                        } catch (CertificateNotYetValidException e) {
                            System.out.println("Status: NOT YET VALID");
                        }
                    }
                    System.out.println("====================================================\n");

                    for (X509Certificate cert : chain) {
                        cert.checkValidity();
                    }
                }
            });

        ServerConnector connector = new ServerConnector(server, ssl);
        connector.setPort(8443);
        server.addConnector(connector);

        server.setHandler(new Handler.Abstract() {
            @Override
            public boolean handle(Request req, Response res, Callback cb) throws Exception {
                res.setStatus(200);
                res.getHeaders().put(HttpHeader.CONTENT_TYPE, "text/plain");
                res.write(true, ByteBuffer.wrap("OK\n".getBytes()), cb);
                return true;
            }
        });

        server.start();
        System.out.println("Server started on https://localhost:8443");
        System.out.println("Waiting for client with certificate...");
        System.out.println("\nTest with: curl -k --cert /tmp/expired.crt --key /tmp/expired.key https://localhost:8443/");
        server.join();
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: 👀 In review

Development

Successfully merging this pull request may close these issues.

Easier access to invalid client certificates

3 participants