diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/connector/Connection.java b/acme4j-client/src/main/java/org/shredzone/acme4j/connector/Connection.java index bd80ebcf..e0420a4c 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/connector/Connection.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/connector/Connection.java @@ -38,7 +38,6 @@ import org.jose4j.lang.JoseException; import org.shredzone.acme4j.Account; import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.exception.AcmeServerException; -import org.shredzone.acme4j.provider.AcmeClientProvider; import org.shredzone.acme4j.util.ClaimBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,16 +48,15 @@ import org.slf4j.LoggerFactory; * @author Richard "Shred" Körber */ public class Connection implements AutoCloseable { - private static final Logger LOG = LoggerFactory.getLogger(Connection.class); private static final Pattern BASE64URL_PATTERN = Pattern.compile("[0-9A-Za-z_-]+"); - private final AcmeClientProvider provider; + protected final HttpConnector httpConnector; protected HttpURLConnection conn; - public Connection(AcmeClientProvider provider) { - this.provider = provider; + public Connection(HttpConnector httpConnector) { + this.httpConnector = httpConnector; } @Override @@ -78,7 +76,7 @@ public class Connection implements AutoCloseable { public void startSession(URI uri, Session session) throws AcmeException { try { LOG.debug("Initial replay nonce from {}", uri); - HttpURLConnection localConn = provider.openConnection(uri); + HttpURLConnection localConn = httpConnector.openConnection(uri); localConn.setRequestMethod("HEAD"); localConn.connect(); @@ -99,7 +97,7 @@ public class Connection implements AutoCloseable { try { LOG.debug("GET {}", uri); - conn = provider.openConnection(uri); + conn = httpConnector.openConnection(uri); conn.setRequestMethod("GET"); conn.setRequestProperty("Accept-Charset", "utf-8"); conn.setDoOutput(false); @@ -140,7 +138,7 @@ public class Connection implements AutoCloseable { LOG.debug("POST {} with claims: {}", uri, claims); - conn = provider.openConnection(uri); + conn = httpConnector.openConnection(uri); conn.setRequestMethod("POST"); conn.setRequestProperty("Accept", "application/json"); conn.setRequestProperty("Accept-Charset", "utf-8"); diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/connector/HttpConnector.java b/acme4j-client/src/main/java/org/shredzone/acme4j/connector/HttpConnector.java new file mode 100644 index 00000000..4a5b36f9 --- /dev/null +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/connector/HttpConnector.java @@ -0,0 +1,49 @@ +/* + * acme4j - Java ACME client + * + * Copyright (C) 2015 Richard "Shred" Körber + * http://acme4j.shredzone.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ +package org.shredzone.acme4j.connector; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URI; + +/** + * A generic HTTP connector. It connects to the given URI with a 10 seconds connection and + * read timeout. + *
+ * Subclasses may reconfigure the {@link HttpURLConnection} and pin it to a concrete SSL
+ * certificate.
+ *
+ * @author Richard "Shred" Körber
+ */
+public class HttpConnector {
+
+ private static final int TIMEOUT = 10000;
+
+ /**
+ * Opens a {@link HttpURLConnection} to the given {@link URI}.
+ *
+ * @param uri
+ * {@link URI} to connect to
+ * @return {@link HttpURLConnection} connected to the {@link URI}
+ */
+ public HttpURLConnection openConnection(URI uri) throws IOException {
+ HttpURLConnection conn = (HttpURLConnection) uri.toURL().openConnection();
+ conn.setConnectTimeout(TIMEOUT);
+ conn.setReadTimeout(TIMEOUT);
+ conn.setUseCaches(false);
+ conn.setRequestProperty("User-Agent", "acme4j");
+ return conn;
+ }
+
+}
diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/impl/AbstractAcmeClient.java b/acme4j-client/src/main/java/org/shredzone/acme4j/impl/AbstractAcmeClient.java
index d9f99de6..aa9c7d7f 100644
--- a/acme4j-client/src/main/java/org/shredzone/acme4j/impl/AbstractAcmeClient.java
+++ b/acme4j-client/src/main/java/org/shredzone/acme4j/impl/AbstractAcmeClient.java
@@ -66,11 +66,11 @@ public abstract class AbstractAcmeClient implements AcmeClient {
*
* @return {@link Connection} instance
*/
- protected abstract Connection connect();
+ protected abstract Connection createConnection();
@Override
public void newRegistration(Account account, Registration registration) throws AcmeException {
- try (Connection conn = connect()) {
+ try (Connection conn = createConnection()) {
ClaimBuilder claims = new ClaimBuilder();
claims.putResource(Resource.NEW_REG);
if (!registration.getContacts().isEmpty()) {
@@ -98,7 +98,7 @@ public abstract class AbstractAcmeClient implements AcmeClient {
throw new IllegalArgumentException("location must be set. Use newRegistration() if not known.");
}
- try (Connection conn = connect()) {
+ try (Connection conn = createConnection()) {
ClaimBuilder claims = new ClaimBuilder();
claims.putResource("reg");
if (!registration.getContacts().isEmpty()) {
@@ -116,7 +116,7 @@ public abstract class AbstractAcmeClient implements AcmeClient {
@Override
public void newAuthorization(Account account, Authorization auth) throws AcmeException {
- try (Connection conn = connect()) {
+ try (Connection conn = createConnection()) {
ClaimBuilder claims = new ClaimBuilder();
claims.putResource(Resource.NEW_AUTHZ);
claims.object("identifier")
@@ -163,7 +163,7 @@ public abstract class AbstractAcmeClient implements AcmeClient {
@Override
public void triggerChallenge(Account account, Challenge challenge) throws AcmeException {
- try (Connection conn = connect()) {
+ try (Connection conn = createConnection()) {
ClaimBuilder claims = new ClaimBuilder();
claims.putResource("challenge");
challenge.marshall(claims);
@@ -176,7 +176,7 @@ public abstract class AbstractAcmeClient implements AcmeClient {
@Override
public void updateChallenge(Account account, Challenge challenge) throws AcmeException {
- try (Connection conn = connect()) {
+ try (Connection conn = createConnection()) {
conn.sendRequest(challenge.getUri());
challenge.unmarshall(conn.readJsonResponse());
}
@@ -184,7 +184,7 @@ public abstract class AbstractAcmeClient implements AcmeClient {
@Override
public URI requestCertificate(Account account, byte[] csr) throws AcmeException {
- try (Connection conn = connect()) {
+ try (Connection conn = createConnection()) {
ClaimBuilder claims = new ClaimBuilder();
claims.putResource(Resource.NEW_CERT);
claims.putBase64("csr", csr);
@@ -200,7 +200,7 @@ public abstract class AbstractAcmeClient implements AcmeClient {
@Override
public X509Certificate downloadCertificate(URI certUri) throws AcmeException {
- try (Connection conn = connect()) {
+ try (Connection conn = createConnection()) {
conn.sendRequest(certUri);
return conn.readCertificate();
}
diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/impl/GenericAcmeClient.java b/acme4j-client/src/main/java/org/shredzone/acme4j/impl/GenericAcmeClient.java
index 357ca195..bc6ce559 100644
--- a/acme4j-client/src/main/java/org/shredzone/acme4j/impl/GenericAcmeClient.java
+++ b/acme4j-client/src/main/java/org/shredzone/acme4j/impl/GenericAcmeClient.java
@@ -55,14 +55,14 @@ public class GenericAcmeClient extends AbstractAcmeClient {
}
@Override
- protected Connection connect() {
- return new Connection(provider);
+ protected Connection createConnection() {
+ return provider.createConnection();
}
@Override
protected URI resourceUri(Resource resource) throws AcmeException {
if (directoryMap.isEmpty()) {
- try (Connection conn = connect()) {
+ try (Connection conn = createConnection()) {
conn.sendRequest(directoryUri);
directoryMap.putAll(conn.readDirectory());
}
diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/provider/AbstractAcmeClientProvider.java b/acme4j-client/src/main/java/org/shredzone/acme4j/provider/AbstractAcmeClientProvider.java
index 616c0490..203b4ee6 100644
--- a/acme4j-client/src/main/java/org/shredzone/acme4j/provider/AbstractAcmeClientProvider.java
+++ b/acme4j-client/src/main/java/org/shredzone/acme4j/provider/AbstractAcmeClientProvider.java
@@ -13,8 +13,6 @@
*/
package org.shredzone.acme4j.provider;
-import java.io.IOException;
-import java.net.HttpURLConnection;
import java.net.URI;
import java.util.Collection;
import java.util.Collections;
@@ -28,6 +26,8 @@ import org.shredzone.acme4j.challenge.GenericChallenge;
import org.shredzone.acme4j.challenge.HttpChallenge;
import org.shredzone.acme4j.challenge.ProofOfPossessionChallenge;
import org.shredzone.acme4j.challenge.TlsSniChallenge;
+import org.shredzone.acme4j.connector.Connection;
+import org.shredzone.acme4j.connector.HttpConnector;
import org.shredzone.acme4j.impl.GenericAcmeClient;
/**
@@ -41,8 +41,6 @@ import org.shredzone.acme4j.impl.GenericAcmeClient;
*/
public abstract class AbstractAcmeClientProvider implements AcmeClientProvider {
- private static final int TIMEOUT = 10000;
-
private final Map
+ * This test requires a network connection. It should be excluded from automated
+ * builds.
+ */
+ @Test
+ @Category(HttpURLConnection.class)
+ public void testOpenConnection() throws IOException, URISyntaxException {
+ HttpConnector connector = new HttpConnector();
+ HttpURLConnection conn = connector.openConnection(new URI("http://example.com"));
+ assertThat(conn, not(nullValue()));
+ conn.connect();
+ assertThat(conn.getResponseCode(), is(HttpURLConnection.HTTP_OK));
+ }
+
+}
diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/provider/AbstractAcmeClientProviderTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/provider/AbstractAcmeClientProviderTest.java
index ba38cdab..37399ebe 100644
--- a/acme4j-client/src/test/java/org/shredzone/acme4j/provider/AbstractAcmeClientProviderTest.java
+++ b/acme4j-client/src/test/java/org/shredzone/acme4j/provider/AbstractAcmeClientProviderTest.java
@@ -16,13 +16,10 @@ package org.shredzone.acme4j.provider;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
-import java.io.IOException;
-import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import org.junit.Test;
-import org.junit.experimental.categories.Category;
import org.shredzone.acme4j.AcmeClient;
import org.shredzone.acme4j.challenge.Challenge;
import org.shredzone.acme4j.challenge.DnsChallenge;
@@ -76,33 +73,6 @@ public class AbstractAcmeClientProviderTest {
}
}
- /**
- * Test if a HTTP connection can be opened.
- *
- * This test requires a network connection. It should be excluded from automated
- * builds.
- */
- @Test
- @Category(HttpURLConnection.class)
- public void testOpenConnection() throws IOException, URISyntaxException {
- AbstractAcmeClientProvider provider = new AbstractAcmeClientProvider() {
- @Override
- public boolean accepts(URI serverUri) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- protected URI resolve(URI serverUri) {
- throw new UnsupportedOperationException();
- }
- };
-
- HttpURLConnection conn = provider.openConnection(new URI("http://example.com"));
- assertThat(conn, not(nullValue()));
- conn.connect();
- assertThat(conn.getResponseCode(), is(HttpURLConnection.HTTP_OK));
- }
-
/**
* Test that all base challenges are registered on initialization, and that additional
* challenges are properly registered.
diff --git a/acme4j-letsencrypt/src/main/java/org/shredzone/acme4j/provider/LetsEncryptAcmeClientProvider.java b/acme4j-letsencrypt/src/main/java/org/shredzone/acme4j/provider/LetsEncryptAcmeClientProvider.java
index ac543600..bdc62e46 100644
--- a/acme4j-letsencrypt/src/main/java/org/shredzone/acme4j/provider/LetsEncryptAcmeClientProvider.java
+++ b/acme4j-letsencrypt/src/main/java/org/shredzone/acme4j/provider/LetsEncryptAcmeClientProvider.java
@@ -13,20 +13,10 @@
*/
package org.shredzone.acme4j.provider;
-import java.io.IOException;
-import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
-import java.security.KeyManagementException;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.cert.CertificateException;
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSocketFactory;
-import javax.net.ssl.TrustManagerFactory;
+import org.shredzone.acme4j.connector.HttpConnector;
/**
* An {@link AcmeClientProvider} for Let's Encrypt.
@@ -45,8 +35,6 @@ public class LetsEncryptAcmeClientProvider extends AbstractAcmeClientProvider {
private static final String V01_DIRECTORY_URI = "https://acme-v01.api.letsencrypt.org/directory";
private static final String STAGING_DIRECTORY_URI = "https://acme-staging.api.letsencrypt.org/directory";
- private SSLSocketFactory sslSocketFactory;
-
@Override
public boolean accepts(URI serverUri) {
return "acme".equals(serverUri.getScheme())
@@ -73,38 +61,8 @@ public class LetsEncryptAcmeClientProvider extends AbstractAcmeClientProvider {
}
@Override
- public HttpURLConnection openConnection(URI uri) throws IOException {
- HttpURLConnection conn = super.openConnection(uri);
- if (conn instanceof HttpsURLConnection) {
- ((HttpsURLConnection) conn).setSSLSocketFactory(createSocketFactory());
- }
- return conn;
- }
-
- /**
- * Lazily creates an {@link SSLSocketFactory} that exclusively accepts the Let's
- * Encrypt certificate.
- */
- protected SSLSocketFactory createSocketFactory() throws IOException {
- if (sslSocketFactory == null) {
- try {
- KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
- keystore.load(getClass().getResourceAsStream("/org/shredzone/acme4j/letsencrypt.truststore"),
- "acme4j".toCharArray());
-
- TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
- tmf.init(keystore);
-
- SSLContext ctx = SSLContext.getInstance("TLS");
- ctx.init(null, tmf.getTrustManagers(), null);
-
- sslSocketFactory = ctx.getSocketFactory();
- } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException
- | KeyManagementException ex) {
- throw new IOException("Could not create truststore", ex);
- }
- }
- return sslSocketFactory;
+ protected HttpConnector createHttpConnector() {
+ return new LetsEncryptHttpConnector();
}
}
diff --git a/acme4j-letsencrypt/src/main/java/org/shredzone/acme4j/provider/LetsEncryptHttpConnector.java b/acme4j-letsencrypt/src/main/java/org/shredzone/acme4j/provider/LetsEncryptHttpConnector.java
new file mode 100644
index 00000000..eaa5d710
--- /dev/null
+++ b/acme4j-letsencrypt/src/main/java/org/shredzone/acme4j/provider/LetsEncryptHttpConnector.java
@@ -0,0 +1,77 @@
+/*
+ * acme4j - Java ACME client
+ *
+ * Copyright (C) 2015 Richard "Shred" Körber
+ * http://acme4j.shredzone.org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ */
+package org.shredzone.acme4j.provider;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URI;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManagerFactory;
+
+import org.shredzone.acme4j.connector.HttpConnector;
+
+/**
+ * {@link HttpConnector} to be used for Let's Encrypt. It is pinned to the Let's Encrypt
+ * server certificate.
+ *
+ * @author Richard "Shred" Körber
+ */
+public class LetsEncryptHttpConnector extends HttpConnector {
+
+ private SSLSocketFactory sslSocketFactory;
+
+ @Override
+ public HttpURLConnection openConnection(URI uri) throws IOException {
+ HttpURLConnection conn = super.openConnection(uri);
+ if (conn instanceof HttpsURLConnection) {
+ ((HttpsURLConnection) conn).setSSLSocketFactory(createSocketFactory());
+ }
+ return conn;
+ }
+
+ /**
+ * Lazily creates an {@link SSLSocketFactory} that exclusively accepts the Let's
+ * Encrypt certificate.
+ */
+ protected SSLSocketFactory createSocketFactory() throws IOException {
+ if (sslSocketFactory == null) {
+ try {
+ KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
+ keystore.load(getClass().getResourceAsStream("/org/shredzone/acme4j/letsencrypt.truststore"),
+ "acme4j".toCharArray());
+
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ tmf.init(keystore);
+
+ SSLContext ctx = SSLContext.getInstance("TLS");
+ ctx.init(null, tmf.getTrustManagers(), null);
+
+ sslSocketFactory = ctx.getSocketFactory();
+ } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException
+ | KeyManagementException ex) {
+ throw new IOException("Could not create truststore", ex);
+ }
+ }
+ return sslSocketFactory;
+ }
+
+}
diff --git a/acme4j-letsencrypt/src/test/java/org/shredzone/acme4j/provider/LetsEncryptAcmeClientProviderTest.java b/acme4j-letsencrypt/src/test/java/org/shredzone/acme4j/provider/LetsEncryptAcmeClientProviderTest.java
index fbb44593..894f5786 100644
--- a/acme4j-letsencrypt/src/test/java/org/shredzone/acme4j/provider/LetsEncryptAcmeClientProviderTest.java
+++ b/acme4j-letsencrypt/src/test/java/org/shredzone/acme4j/provider/LetsEncryptAcmeClientProviderTest.java
@@ -13,20 +13,13 @@
*/
package org.shredzone.acme4j.provider;
-import static org.hamcrest.Matchers.*;
+import static org.hamcrest.Matchers.is;
import static org.junit.Assert.*;
-import java.io.IOException;
-import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.SSLHandshakeException;
-import javax.net.ssl.SSLSocketFactory;
-
import org.junit.Test;
-import org.junit.experimental.categories.Category;
/**
* Unit tests for {@link LetsEncryptAcmeClientProvider}.
@@ -74,50 +67,4 @@ public class LetsEncryptAcmeClientProviderTest {
}
}
- /**
- * Test if the {@link LetsEncryptAcmeClientProvider#openConnection(URI)} accepts only
- * the Let's Encrypt certificate.
- *
- * This test requires a network connection. It should be excluded from automated
- * builds.
- */
- @Test
- @Category(HttpURLConnection.class)
- public void testCertificate() throws IOException, URISyntaxException {
- LetsEncryptAcmeClientProvider provider = new LetsEncryptAcmeClientProvider();
-
- try {
- HttpURLConnection goodConn = provider.openConnection(
- new URI("https://acme-staging.api.letsencrypt.org/directory"));
- assertThat(goodConn, is(instanceOf(HttpsURLConnection.class)));
- goodConn.connect();
- } catch (SSLHandshakeException ex) {
- fail("Connection does not accept Let's Encrypt certificate");
- }
-
- try {
- HttpURLConnection badConn = provider.openConnection(
- new URI("https://www.google.com"));
- assertThat(badConn, is(instanceOf(HttpsURLConnection.class)));
- badConn.connect();
- fail("Connection accepts foreign certificate");
- } catch (SSLHandshakeException ex) {
- // expected
- }
- }
-
- /**
- * Test that the {@link SSLSocketFactory} can be instantiated and is cached.
- */
- @Test
- public void testCreateSocketFactory() throws IOException {
- LetsEncryptAcmeClientProvider provider = new LetsEncryptAcmeClientProvider();
-
- SSLSocketFactory factory1 = provider.createSocketFactory();
- assertThat(factory1, is(notNullValue()));
-
- SSLSocketFactory factory2 = provider.createSocketFactory();
- assertThat(factory1, is(sameInstance(factory2)));
- }
-
}
diff --git a/acme4j-letsencrypt/src/test/java/org/shredzone/acme4j/provider/LetsEncryptHttpConnectorTest.java b/acme4j-letsencrypt/src/test/java/org/shredzone/acme4j/provider/LetsEncryptHttpConnectorTest.java
new file mode 100644
index 00000000..e16575d3
--- /dev/null
+++ b/acme4j-letsencrypt/src/test/java/org/shredzone/acme4j/provider/LetsEncryptHttpConnectorTest.java
@@ -0,0 +1,84 @@
+/*
+ * acme4j - Java ACME client
+ *
+ * Copyright (C) 2015 Richard "Shred" Körber
+ * http://acme4j.shredzone.org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ */
+package org.shredzone.acme4j.provider;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.SSLSocketFactory;
+
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+/**
+ * Unit test for {@link LetsEncryptHttpConnector}.
+ *
+ * @author Richard "Shred" Körber
+ */
+public class LetsEncryptHttpConnectorTest {
+
+ /**
+ * Test if the {@link LetsEncryptAcmeClientProvider#openConnection(URI)} accepts only
+ * the Let's Encrypt certificate.
+ *
+ * This test requires a network connection. It should be excluded from automated
+ * builds.
+ */
+ @Test
+ @Category(HttpURLConnection.class)
+ public void testCertificate() throws IOException, URISyntaxException {
+ LetsEncryptHttpConnector connector = new LetsEncryptHttpConnector();
+
+ try {
+ HttpURLConnection goodConn = connector.openConnection(
+ new URI("https://acme-staging.api.letsencrypt.org/directory"));
+ assertThat(goodConn, is(instanceOf(HttpsURLConnection.class)));
+ goodConn.connect();
+ } catch (SSLHandshakeException ex) {
+ fail("Connection does not accept Let's Encrypt certificate");
+ }
+
+ try {
+ HttpURLConnection badConn = connector.openConnection(
+ new URI("https://www.google.com"));
+ assertThat(badConn, is(instanceOf(HttpsURLConnection.class)));
+ badConn.connect();
+ fail("Connection accepts foreign certificate");
+ } catch (SSLHandshakeException ex) {
+ // expected
+ }
+ }
+
+ /**
+ * Test that the {@link SSLSocketFactory} can be instantiated and is cached.
+ */
+ @Test
+ public void testCreateSocketFactory() throws IOException {
+ LetsEncryptHttpConnector connector = new LetsEncryptHttpConnector();
+
+ SSLSocketFactory factory1 = connector.createSocketFactory();
+ assertThat(factory1, is(notNullValue()));
+
+ SSLSocketFactory factory2 = connector.createSocketFactory();
+ assertThat(factory1, is(sameInstance(factory2)));
+ }
+
+}