Java tutorial
/* * Copyright (C) 2011 Thomas Akehurst * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.tomakehurst.wiremock; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.core.Options; import com.github.tomakehurst.wiremock.core.WireMockConfiguration; import com.github.tomakehurst.wiremock.http.Fault; import com.github.tomakehurst.wiremock.http.HttpClientFactory; import com.google.common.io.Resources; import org.apache.commons.lang3.SystemUtils; import org.apache.http.HttpResponse; import org.apache.http.MalformedChunkCodingException; import org.apache.http.NoHttpResponseException; import org.apache.http.ProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.conn.ssl.SSLContexts; import org.apache.http.conn.ssl.TrustSelfSignedStrategy; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import org.hamcrest.Matchers; import org.hamcrest.core.IsInstanceOf; import org.junit.After; import org.junit.Rule; import org.junit.Test; import org.junit.internal.matchers.ThrowableCauseMatcher; import org.junit.rules.ExpectedException; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLHandshakeException; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.net.SocketException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import static com.github.tomakehurst.wiremock.client.WireMock.*; import static com.github.tomakehurst.wiremock.common.Exceptions.throwUnchecked; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.junit.Assume.assumeFalse; import static org.junit.Assume.assumeTrue; public class HttpsAcceptanceTest { private static final String TRUST_STORE_PATH = toPath("test-clientstore"); private static final String KEY_STORE_PATH = toPath("test-keystore"); private static final String TRUST_STORE_PASSWORD = "mytruststorepassword"; private WireMockServer wireMockServer; private WireMockServer proxy; private HttpClient httpClient; @After public void serverShutdown() { if (wireMockServer != null) { wireMockServer.stop(); } if (proxy != null) { proxy.shutdown(); } } @Test public void shouldReturnStubOnSpecifiedPort() throws Exception { startServerWithDefaultKeystore(); stubFor(get(urlEqualTo("/https-test")).willReturn(aResponse().withStatus(200).withBody("HTTPS content"))); assertThat(contentFor(url("/https-test")), is("HTTPS content")); } @Rule public final ExpectedException exception = ExpectedException.none(); @Test public void connectionResetByPeerFault() throws IOException { assumeFalse("This feature does not work on Windows " + "because of differing native socket behaviour", SystemUtils.IS_OS_WINDOWS); startServerWithDefaultKeystore(); stubFor(get(urlEqualTo("/connection/reset")) .willReturn(aResponse().withFault(Fault.CONNECTION_RESET_BY_PEER))); exception.expect(SocketException.class); exception.expectMessage("Connection reset"); httpClient.execute(new HttpGet(url("/connection/reset"))).getEntity(); } @Test public void emptyResponseFault() { startServerWithDefaultKeystore(); stubFor(get(urlEqualTo("/empty/response")).willReturn(aResponse().withFault(Fault.EMPTY_RESPONSE))); getAndAssertUnderlyingExceptionInstanceClass(url("/empty/response"), NoHttpResponseException.class); } @Test public void malformedResponseChunkFault() { startServerWithDefaultKeystore(); stubFor(get(urlEqualTo("/malformed/response")) .willReturn(aResponse().withFault(Fault.MALFORMED_RESPONSE_CHUNK))); getAndAssertUnderlyingExceptionInstanceClass(url("/malformed/response"), MalformedChunkCodingException.class); } @Test public void randomDataOnSocketFault() { startServerWithDefaultKeystore(); stubFor(get(urlEqualTo("/random/data")).willReturn(aResponse().withFault(Fault.RANDOM_DATA_THEN_CLOSE))); getAndAssertUnderlyingExceptionInstanceClass(url("/random/data"), ProtocolException.class); } @Test(expected = Exception.class) public void throwsExceptionWhenBadAlternativeKeystore() { String testKeystorePath = Resources.getResource("bad-keystore").toString(); startServerWithKeystore(testKeystorePath); } @Test public void acceptsAlternativeKeystore() throws Exception { String testKeystorePath = Resources.getResource("test-keystore").toString(); startServerWithKeystore(testKeystorePath); stubFor(get(urlEqualTo("/https-test")).willReturn(aResponse().withStatus(200).withBody("HTTPS content"))); assertThat(contentFor(url("/https-test")), is("HTTPS content")); } @Test public void acceptsAlternativeKeystoreWithNonDefaultPassword() throws Exception { String keystorePath = Resources.getResource("test-keystore-pwd").toString(); startServerWithKeystore(keystorePath, "anotherpassword"); stubFor(get(urlEqualTo("/alt-password-https")) .willReturn(aResponse().withStatus(200).withBody("HTTPS content"))); assertThat(contentFor(url("/alt-password-https")), is("HTTPS content")); } @Test public void rejectsWithoutClientCertificate() { startServerEnforcingClientCert(KEY_STORE_PATH, TRUST_STORE_PATH, TRUST_STORE_PASSWORD); wireMockServer.stubFor( get(urlEqualTo("/https-test")).willReturn(aResponse().withStatus(200).withBody("HTTPS content"))); try { contentFor(url("/https-test")); // this lacks the required client certificate fail("Expected a SocketException or SSLHandshakeException to be thrown"); } catch (Exception e) { assertThat(e.getClass().getName(), Matchers.anyOf(is(SocketException.class.getName()), is(SSLHandshakeException.class.getName()))); } } @Test public void acceptWithClientCertificate() throws Exception { String testTrustStorePath = TRUST_STORE_PATH; String testClientCertPath = TRUST_STORE_PATH; startServerEnforcingClientCert(KEY_STORE_PATH, testTrustStorePath, TRUST_STORE_PASSWORD); wireMockServer.stubFor( get(urlEqualTo("/https-test")).willReturn(aResponse().withStatus(200).withBody("HTTPS content"))); assertThat(secureContentFor(url("/https-test"), testClientCertPath, TRUST_STORE_PASSWORD), is("HTTPS content")); } @Test public void supportsProxyingWhenTargetRequiresClientCert() throws Exception { startServerEnforcingClientCert(KEY_STORE_PATH, TRUST_STORE_PATH, TRUST_STORE_PASSWORD); wireMockServer.stubFor(get(urlEqualTo("/client-cert-proxy")).willReturn(aResponse().withStatus(200))); proxy = new WireMockServer(wireMockConfig().port(Options.DYNAMIC_PORT).trustStorePath(TRUST_STORE_PATH) .trustStorePassword(TRUST_STORE_PASSWORD)); proxy.start(); proxy.stubFor(get(urlEqualTo("/client-cert-proxy")) .willReturn(aResponse().proxiedFrom("https://localhost:" + wireMockServer.httpsPort()))); HttpGet get = new HttpGet("http://localhost:" + proxy.port() + "/client-cert-proxy"); HttpResponse response = httpClient.execute(get); assertThat(response.getStatusLine().getStatusCode(), is(200)); } @Test public void proxyingFailsWhenTargetServiceRequiresClientCertificatesAndProxyDoesNotSend() throws Exception { startServerEnforcingClientCert(KEY_STORE_PATH, TRUST_STORE_PATH, TRUST_STORE_PASSWORD); wireMockServer.stubFor(get(urlEqualTo("/client-cert-proxy-fail")).willReturn(aResponse().withStatus(200))); proxy = new WireMockServer(wireMockConfig().port(Options.DYNAMIC_PORT)); proxy.start(); proxy.stubFor(get(urlEqualTo("/client-cert-proxy-fail")) .willReturn(aResponse().proxiedFrom("https://localhost:" + wireMockServer.httpsPort()))); HttpGet get = new HttpGet("http://localhost:" + proxy.port() + "/client-cert-proxy-fail"); HttpResponse response = httpClient.execute(get); assertThat(response.getStatusLine().getStatusCode(), is(500)); } private String url(String path) { return String.format("https://localhost:%d%s", wireMockServer.httpsPort(), path); } private static String toPath(String resourcePath) { try { return new File(Resources.getResource(resourcePath).toURI()).getCanonicalPath(); } catch (Exception e) { return throwUnchecked(e, String.class); } } private void getAndAssertUnderlyingExceptionInstanceClass(String url, Class<?> expectedClass) { boolean thrown = false; try { contentFor(url); } catch (Exception e) { Throwable cause = e.getCause(); e.printStackTrace(); if (cause != null) { assertThat(e.getCause(), instanceOf(expectedClass)); } else { assertThat(e, instanceOf(expectedClass)); } thrown = true; } assertTrue("No exception was thrown", thrown); } private String contentFor(String url) throws Exception { HttpGet get = new HttpGet(url); HttpResponse response = httpClient.execute(get); String content = EntityUtils.toString(response.getEntity()); return content; } private void startServerEnforcingClientCert(String keystorePath, String truststorePath, String trustStorePassword) { WireMockConfiguration config = wireMockConfig().dynamicPort().dynamicHttpsPort(); if (keystorePath != null) { config.keystorePath(keystorePath); } if (truststorePath != null) { config.trustStorePath(truststorePath); config.trustStorePassword(trustStorePassword); config.needClientAuth(true); } config.bindAddress("localhost"); wireMockServer = new WireMockServer(config); wireMockServer.start(); WireMock.configureFor("https", "localhost", wireMockServer.httpsPort()); httpClient = HttpClientFactory.createClient(); } private void startServerWithKeystore(String keystorePath, String keystorePassword) { WireMockConfiguration config = wireMockConfig().dynamicPort().dynamicHttpsPort(); if (keystorePath != null) { config.keystorePath(keystorePath); config.keystorePassword(keystorePassword); } wireMockServer = new WireMockServer(config); wireMockServer.start(); WireMock.configureFor(wireMockServer.port()); httpClient = HttpClientFactory.createClient(); } private void startServerWithKeystore(String keystorePath) { startServerWithKeystore(keystorePath, "password"); } private void startServerWithDefaultKeystore() { startServerWithKeystore(null); } static String secureContentFor(String url, String clientTrustStore, String trustStorePassword) throws Exception { KeyStore trustStore = readKeyStore(clientTrustStore, trustStorePassword); // Trust own CA and all self-signed certs SSLContext sslcontext = SSLContexts.custom().loadTrustMaterial(null, new TrustSelfSignedStrategy()) .loadKeyMaterial(trustStore, trustStorePassword.toCharArray()).useTLS().build(); // Allow TLSv1 protocol only SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[] { "TLSv1" }, // supported protocols null, // supported cipher suites SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslsf).build(); HttpGet get = new HttpGet(url); HttpResponse response = httpClient.execute(get); String content = EntityUtils.toString(response.getEntity()); return content; } static KeyStore readKeyStore(String path, String password) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); FileInputStream instream = new FileInputStream(path); try { trustStore.load(instream, password.toCharArray()); } finally { instream.close(); } return trustStore; } }