mock-server / mockserver

MockServer enables easy mocking of any system you integrate with via HTTP or HTTPS with clients written in Java, JavaScript and Ruby. MockServer also includes a proxy that introspects all proxied traffic including encrypted SSL traffic and supports Port Forwarding, Web Proxying (i.e. HTTP proxy), HTTPS Tunneling Proxying (using HTTP CONNECT) and SOCKS Proxying (i.e. dynamic port forwarding).
http://mock-server.com
Apache License 2.0
4.59k stars 1.07k forks source link

Optional mTLS #1422

Closed mikaves closed 2 years ago

mikaves commented 2 years ago

It looks like we cannot use optional client certificate authentication. Setting the tlsMutualAuthenticationCertificateChain has effect only if tlsMutualAuthenticationRequired is true which makes the client certificate authentication mandatory.

If tlsMutualAuthenticationRequired is false client certificate authentication is made optional but we cannot then specify the acceptable client certificate list and mockserver sends empty list to client in tls handshake. Client cannot pick any suitable client certificate from the key store as it does not know which CAs server accepts.

Naturally this also raises the need to be able to verify that client provided client certificate in the case it should have been provided. Also it would be good to be able to assert which certificate was used as one could have application which has multiple key stores and one would need to assert that key was picked up from correct key store.

jamesdbloom commented 2 years ago

There is already a field called clientCertificateChain that is available in callbacks (method / closure OR class). Currently there is not support for assertion on client certificate chain but it would be fairly simple to create a closure callback that did this, for example:

mockServerClient
    .when(
        request()
            .withPath("/some_path")
    )
    .respond(
        httpRequest -> {
            if (httpRequest.isSecure()) {
                assertThat(httpRequest.getClientCertificateChain().size(), equalTo(2));
                assertThat(httpRequest.getClientCertificateChain().get(0).getSubjectDistinguishedName(), equalTo("C=UK, ST=England, L=London, O=MockServer, CN=localhost"));
                assertThat(httpRequest.getClientCertificateChain().get(1).getSubjectDistinguishedName(), equalTo("C=UK, ST=England, L=London, O=MockServer, CN=www.mockserver.com"));
            }
            return response()
                .withBody("some_response");
        }
    );

I'm not sure I full understand you first point. As I understand you want MockServer to configure the tlsMutualAuthenticationCertificateChain for client connections even when tlsMutualAuthenticationRequired is false so that clients that want to present their certificate can choose the correct certificate to present? If so I can change the logic as follows:

from:

final SslContextBuilder sslContextBuilder = SslContextBuilder
    .forServer(
        keyAndCertificateFactory.privateKey(),
        keyAndCertificateFactory.certificateChain()
    )
    .protocols(TLS_PROTOCOLS)
//                    .sslProvider(SslProvider.JDK)
    .clientAuth(configuration.tlsMutualAuthenticationRequired() ? ClientAuth.REQUIRE : ClientAuth.OPTIONAL);
if (configuration.tlsMutualAuthenticationRequired()) {
    sslContextBuilder.trustManager(trustCertificateChain(configuration.tlsMutualAuthenticationCertificateChain()));
} else {
    sslContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE);
}

to:

final SslContextBuilder sslContextBuilder = SslContextBuilder
    .forServer(
        keyAndCertificateFactory.privateKey(),
        keyAndCertificateFactory.certificateChain()
    )
    .protocols(TLS_PROTOCOLS)
//                    .sslProvider(SslProvider.JDK)
    .clientAuth(configuration.tlsMutualAuthenticationRequired() ? ClientAuth.REQUIRE : ClientAuth.OPTIONAL);
X509Certificate[] trustCertificateChain = trustCertificateChain(configuration.tlsMutualAuthenticationCertificateChain())
if (trustCertificateChain != null && trustCertificateChain.length > 0) {
    sslContextBuilder.trustManager(trustCertificateChain);
} else {
    sslContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE);
}

This way the tlsMutualAuthenticationCertificateChain will be used if it is configured even if tlsMutualAuthenticationRequired is false.

Does that cover both your points?