package io.dropwizard.client.ssl; import io.dropwizard.Application; import io.dropwizard.Configuration; import io.dropwizard.client.DropwizardSSLConnectionSocketFactory; import io.dropwizard.client.JerseyClientBuilder; import io.dropwizard.client.JerseyClientConfiguration; import io.dropwizard.setup.Environment; import io.dropwizard.testing.ConfigOverride; import io.dropwizard.testing.ResourceHelpers; import io.dropwizard.testing.junit.DropwizardAppRule; import io.dropwizard.util.Duration; import org.glassfish.jersey.client.ClientResponse; import org.junit.Before; import org.junit.ClassRule; import org.junit.Test; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLException; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.ProcessingException; import javax.ws.rs.client.Client; import javax.ws.rs.core.Response; import java.io.File; import java.lang.reflect.Field; import java.net.SocketException; import java.util.Collections; import java.util.Optional; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.http.conn.ssl.NoopHostnameVerifier; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.catchThrowable; public class DropwizardSSLConnectionSocketFactoryTest { private TlsConfiguration tlsConfiguration; private JerseyClientConfiguration jerseyClientConfiguration; @Path("/") public static class TestResource { @GET public Response respondOk() { return Response.ok().build(); } } public static class TlsTestApplication extends Application<Configuration> { public static void main(String[] args) throws Exception { new TlsTestApplication().run(args); } @Override public void run(Configuration configuration, Environment environment) throws Exception { environment.jersey().register(TestResource.class); } } @ClassRule public static final DropwizardAppRule<Configuration> TLS_APP_RULE = new DropwizardAppRule<>(TlsTestApplication.class, ResourceHelpers.resourceFilePath("yaml/ssl_connection_socket_factory_test.yml"), Optional.of("tls"), ConfigOverride.config("tls", "server.applicationConnectors[0].keyStorePath", ResourceHelpers.resourceFilePath("stores/server/keycert.p12")), ConfigOverride.config("tls", "server.applicationConnectors[1].keyStorePath", ResourceHelpers.resourceFilePath("stores/server/self_sign_keycert.p12")), ConfigOverride.config("tls", "server.applicationConnectors[2].keyStorePath", ResourceHelpers.resourceFilePath("stores/server/keycert.p12")), ConfigOverride.config("tls", "server.applicationConnectors[2].trustStorePath", ResourceHelpers.resourceFilePath("stores/server/ca_truststore.ts")), ConfigOverride.config("tls", "server.applicationConnectors[2].wantClientAuth", "true"), ConfigOverride.config("tls", "server.applicationConnectors[2].needClientAuth", "true"), ConfigOverride.config("tls", "server.applicationConnectors[2].validatePeers", "false"), ConfigOverride.config("tls", "server.applicationConnectors[2].trustStorePassword", "password"), ConfigOverride.config("tls", "server.applicationConnectors[3].keyStorePath", ResourceHelpers.resourceFilePath("stores/server/bad_host_keycert.p12")), ConfigOverride.config("tls", "server.applicationConnectors[4].keyStorePath", ResourceHelpers.resourceFilePath("stores/server/keycert.p12")), ConfigOverride.config("tls", "server.applicationConnectors[4].supportedProtocols", "SSLv1,SSLv2,SSLv3")); @Before public void setUp() throws Exception { tlsConfiguration = new TlsConfiguration(); tlsConfiguration.setTrustStorePath(new File(ResourceHelpers.resourceFilePath("stores/server/ca_truststore.ts"))); tlsConfiguration.setTrustStorePassword("password"); jerseyClientConfiguration = new JerseyClientConfiguration(); jerseyClientConfiguration.setTlsConfiguration(tlsConfiguration); jerseyClientConfiguration.setConnectionTimeout(Duration.milliseconds(2000)); jerseyClientConfiguration.setTimeout(Duration.milliseconds(5000)); } @Test public void configOnlyConstructorShouldSetNullCustomVerifier() throws Exception { final DropwizardSSLConnectionSocketFactory socketFactory; socketFactory = new DropwizardSSLConnectionSocketFactory(tlsConfiguration); final Field verifierField = FieldUtils.getField(DropwizardSSLConnectionSocketFactory.class, "verifier", true); assertThat(verifierField.get(socketFactory)).isNull(); } @Test public void shouldReturn200IfServerCertInTruststore() throws Exception { final Client client = new JerseyClientBuilder(TLS_APP_RULE.getEnvironment()).using(jerseyClientConfiguration).build("tls_working_client"); final Response response = client.target(String.format("https://localhost:%d", TLS_APP_RULE.getLocalPort())).request().get(); assertThat(response.getStatus()).isEqualTo(200); } @Test public void shouldErrorIfServerCertNotFoundInTruststore() throws Exception { tlsConfiguration.setTrustStorePath(new File(ResourceHelpers.resourceFilePath("stores/server/other_cert_truststore.ts"))); final Client client = new JerseyClientBuilder(TLS_APP_RULE.getEnvironment()).using(jerseyClientConfiguration).build("tls_broken_client"); assertThatThrownBy(() -> client.target(String.format("https://localhost:%d", TLS_APP_RULE.getLocalPort())).request().get()) .isInstanceOf(ProcessingException.class) .hasCauseInstanceOf(SSLHandshakeException.class); } @Test public void shouldNotErrorIfServerCertSelfSignedAndSelfSignedCertsAllowed() throws Exception { tlsConfiguration.setTrustSelfSignedCertificates(true); final Client client = new JerseyClientBuilder(TLS_APP_RULE.getEnvironment()).using(jerseyClientConfiguration).build("self_sign_permitted"); final Response response = client.target(String.format("https://localhost:%d", TLS_APP_RULE.getTestSupport().getPort(1))).request().get(); assertThat(response.getStatus()).isEqualTo(200); } @Test public void shouldErrorIfServerCertSelfSignedAndSelfSignedCertsNotAllowed() throws Exception { final Client client = new JerseyClientBuilder(TLS_APP_RULE.getEnvironment()).using(jerseyClientConfiguration).build("self_sign_failure"); assertThatThrownBy(() -> client.target(String.format("https://localhost:%d", TLS_APP_RULE.getPort(1))).request().get(ClientResponse.class)) .isInstanceOf(ProcessingException.class) .hasCauseInstanceOf(SSLHandshakeException.class); } @Test public void shouldReturn200IfAbleToClientAuth() throws Exception { tlsConfiguration.setKeyStorePath(new File(ResourceHelpers.resourceFilePath("stores/client/keycert.p12"))); tlsConfiguration.setKeyStorePassword("password"); tlsConfiguration.setKeyStoreType("PKCS12"); final Client client = new JerseyClientBuilder(TLS_APP_RULE.getEnvironment()).using(jerseyClientConfiguration).build("client_auth_working"); final Response response = client.target(String.format("https://localhost:%d", TLS_APP_RULE.getPort(2))).request().get(); assertThat(response.getStatus()).isEqualTo(200); } @Test public void shouldErrorIfClientAuthFails() throws Exception { tlsConfiguration.setKeyStorePath(new File(ResourceHelpers.resourceFilePath("stores/server/self_sign_keycert.p12"))); tlsConfiguration.setKeyStorePassword("password"); tlsConfiguration.setKeyStoreType("PKCS12"); final Client client = new JerseyClientBuilder(TLS_APP_RULE.getEnvironment()).using(jerseyClientConfiguration).build("client_auth_broken"); final Throwable exn = catchThrowable(() -> client.target(String.format("https://localhost:%d", TLS_APP_RULE.getPort(2))).request().get()); assertThat(exn).isInstanceOf(ProcessingException.class); assertThat(exn.getCause()).isInstanceOfAny(SocketException.class, SSLHandshakeException.class); } @Test public void shouldReturn200IfAbleToClientAuthSpecifyingCertAliasForGoodCert() throws Exception { tlsConfiguration.setKeyStorePath(new File(ResourceHelpers.resourceFilePath("stores/client/twokeys.p12"))); tlsConfiguration.setKeyStorePassword("password"); tlsConfiguration.setKeyStoreType("PKCS12"); tlsConfiguration.setCertAlias("1"); final Client client = new JerseyClientBuilder(TLS_APP_RULE.getEnvironment()).using(jerseyClientConfiguration).build("client_auth_using_cert_alias_working"); final Response response = client.target(String.format("https://localhost:%d", TLS_APP_RULE.getPort(2))).request().get(); assertThat(response.getStatus()).isEqualTo(200); } @Test public void shouldErrorIfTryToClientAuthSpecifyingCertAliasForBadCert() throws Exception { tlsConfiguration.setKeyStorePath(new File(ResourceHelpers.resourceFilePath("stores/client/twokeys.p12"))); tlsConfiguration.setKeyStorePassword("password"); tlsConfiguration.setKeyStoreType("PKCS12"); tlsConfiguration.setCertAlias("2"); final Client client = new JerseyClientBuilder(TLS_APP_RULE.getEnvironment()).using(jerseyClientConfiguration).build("client_auth_using_cert_alias_broken"); final Throwable exn = catchThrowable(() -> client.target(String.format("https://localhost:%d", TLS_APP_RULE.getPort(2))).request().get()); assertThat(exn).isInstanceOf(ProcessingException.class); assertThat(exn.getCause()).isInstanceOfAny(SocketException.class, SSLHandshakeException.class); } @Test public void shouldErrorIfTryToClientAuthSpecifyingUnknownCertAlias() throws Exception { tlsConfiguration.setKeyStorePath(new File(ResourceHelpers.resourceFilePath("stores/client/twokeys.p12"))); tlsConfiguration.setKeyStorePassword("password"); tlsConfiguration.setKeyStoreType("PKCS12"); tlsConfiguration.setCertAlias("unknown"); final Client client = new JerseyClientBuilder(TLS_APP_RULE.getEnvironment()).using(jerseyClientConfiguration).build("client_auth_using_unknown_cert_alias_broken"); final Throwable exn = catchThrowable(() -> client.target(String.format("https://localhost:%d", TLS_APP_RULE.getPort(2))).request().get()); assertThat(exn).isInstanceOf(ProcessingException.class); assertThat(exn.getCause()).isInstanceOfAny(SocketException.class, SSLHandshakeException.class); } @Test public void shouldErrorIfHostnameVerificationOnAndServerHostnameDoesntMatch() throws Exception { final Client client = new JerseyClientBuilder(TLS_APP_RULE.getEnvironment()).using(jerseyClientConfiguration).build("bad_host_broken"); final Throwable exn = catchThrowable(() -> client.target(String.format("https://localhost:%d", TLS_APP_RULE.getPort(3))).request().get()); assertThat(exn).hasCauseExactlyInstanceOf(SSLPeerUnverifiedException.class); assertThat(exn.getCause()).hasMessage("Certificate for <localhost> doesn't match any of the subject alternative names: []"); } @Test public void shouldErrorIfHostnameVerificationOnAndServerHostnameMatchesAndFailVerifierSpecified() throws Exception { final Client client = new JerseyClientBuilder(TLS_APP_RULE.getEnvironment()).using(jerseyClientConfiguration).using(new FailVerifier()).build("bad_host_broken_fail_verifier"); final Throwable exn = catchThrowable(() -> client.target(String.format("https://localhost:%d", TLS_APP_RULE.getLocalPort())).request().get()); assertThat(exn).hasCauseExactlyInstanceOf(SSLPeerUnverifiedException.class); assertThat(exn.getCause()).hasMessage("Certificate for <localhost> doesn't match any of the subject alternative names: []"); } @Test public void shouldBeOkIfHostnameVerificationOnAndServerHostnameDoesntMatchAndNoopVerifierSpecified() throws Exception { final Client client = new JerseyClientBuilder(TLS_APP_RULE.getEnvironment()).using(jerseyClientConfiguration).using(new NoopHostnameVerifier()).build("bad_host_noop_verifier_working"); final Response response = client.target(String.format("https://localhost:%d", TLS_APP_RULE.getPort(3))).request().get(); assertThat(response.getStatus()).isEqualTo(200); } @Test public void shouldBeOkIfHostnameVerificationOffAndServerHostnameDoesntMatch() throws Exception { tlsConfiguration.setVerifyHostname(false); final Client client = new JerseyClientBuilder(TLS_APP_RULE.getEnvironment()).using(jerseyClientConfiguration).build("bad_host_working"); final Response response = client.target(String.format("https://localhost:%d", TLS_APP_RULE.getPort(3))).request().get(); assertThat(response.getStatus()).isEqualTo(200); } @Test public void shouldBeOkIfHostnameVerificationOffAndServerHostnameMatchesAndFailVerfierSpecified() throws Exception { tlsConfiguration.setVerifyHostname(false); final Client client = new JerseyClientBuilder(TLS_APP_RULE.getEnvironment()).using(jerseyClientConfiguration).using(new FailVerifier()).build("bad_host_fail_verifier_working"); final Response response = client.target(String.format("https://localhost:%d", TLS_APP_RULE.getLocalPort())).request().get(); assertThat(response.getStatus()).isEqualTo(200); } @Test public void shouldRejectNonSupportedProtocols() throws Exception { tlsConfiguration.setSupportedProtocols(Collections.singletonList("TLSv1.2")); final Client client = new JerseyClientBuilder(TLS_APP_RULE.getEnvironment()).using(jerseyClientConfiguration).build("reject_non_supported"); assertThatThrownBy(() -> client.target(String.format("https://localhost:%d", TLS_APP_RULE.getPort(4))).request().get()) .isInstanceOf(ProcessingException.class) .hasRootCauseInstanceOf(SSLException.class); } private static class FailVerifier implements HostnameVerifier { @Override public boolean verify(String arg0, SSLSession arg1) { return false; } } }