mirror of https://github.com/shred/acme4j
Refactor, use new HttpConnector for connecting to server
parent
b12ee4a28a
commit
0f4d5e114d
|
@ -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");
|
||||
|
|
|
@ -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.
|
||||
* <p>
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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<String, Class<? extends Challenge>> challenges = new HashMap<>();
|
||||
|
||||
public AbstractAcmeClientProvider() {
|
||||
|
@ -86,13 +84,16 @@ public abstract class AbstractAcmeClientProvider implements AcmeClientProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
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;
|
||||
public Connection createConnection() {
|
||||
return new Connection(createHttpConnector());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link HttpConnector}. Subclasses may override this method to
|
||||
* configure the {@link HttpConnector}.
|
||||
*/
|
||||
protected HttpConnector createHttpConnector() {
|
||||
return new HttpConnector();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -13,14 +13,13 @@
|
|||
*/
|
||||
package org.shredzone.acme4j.provider;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URI;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
import org.shredzone.acme4j.AcmeClient;
|
||||
import org.shredzone.acme4j.challenge.Challenge;
|
||||
import org.shredzone.acme4j.challenge.GenericChallenge;
|
||||
import org.shredzone.acme4j.connector.Connection;
|
||||
|
||||
/**
|
||||
* An {@link AcmeClientProvider} creates {@link AcmeClient} instances to be used for
|
||||
|
@ -70,13 +69,10 @@ public interface AcmeClientProvider {
|
|||
<T extends Challenge> T createChallenge(String type);
|
||||
|
||||
/**
|
||||
* Opens a {@link HttpURLConnection} to the given {@link URI}. Implementations may
|
||||
* configure the connection, e.g. pin it to a concrete SSL certificate.
|
||||
* Creates a {@link Connection} for communication with the ACME server.
|
||||
*
|
||||
* @param uri
|
||||
* {@link URI} to connect to
|
||||
* @return {@link HttpURLConnection} connected to the {@link URI}
|
||||
* @return {@link Connection} that was generated
|
||||
*/
|
||||
HttpURLConnection openConnection(URI uri) throws IOException;
|
||||
Connection createConnection();
|
||||
|
||||
}
|
||||
|
|
|
@ -17,14 +17,13 @@ import static org.hamcrest.Matchers.*;
|
|||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.shredzone.acme4j.challenge.Challenge;
|
||||
import org.shredzone.acme4j.connector.Connection;
|
||||
import org.shredzone.acme4j.exception.AcmeException;
|
||||
import org.shredzone.acme4j.provider.AcmeClientProvider;
|
||||
|
||||
|
@ -88,7 +87,7 @@ public class AcmeClientFactoryTest {
|
|||
}
|
||||
|
||||
@Override
|
||||
public HttpURLConnection openConnection(URI uri) throws IOException {
|
||||
public Connection createConnection() {
|
||||
fail("not supposed to be invoked");
|
||||
return null;
|
||||
}
|
||||
|
@ -114,7 +113,7 @@ public class AcmeClientFactoryTest {
|
|||
}
|
||||
|
||||
@Override
|
||||
public HttpURLConnection openConnection(URI uri) throws IOException {
|
||||
public Connection createConnection() {
|
||||
fail("not supposed to be invoked");
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -41,7 +41,6 @@ import org.junit.Test;
|
|||
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.shredzone.acme4j.util.TestUtils;
|
||||
|
||||
|
@ -53,8 +52,8 @@ import org.shredzone.acme4j.util.TestUtils;
|
|||
public class ConnectionTest {
|
||||
|
||||
private URI requestUri;
|
||||
private AcmeClientProvider mockProvider;
|
||||
private HttpURLConnection mockUrlConnection;
|
||||
private HttpConnector mockHttpConnection;
|
||||
|
||||
@Before
|
||||
public void setup() throws IOException, URISyntaxException {
|
||||
|
@ -62,8 +61,8 @@ public class ConnectionTest {
|
|||
|
||||
mockUrlConnection = mock(HttpURLConnection.class);
|
||||
|
||||
mockProvider = mock(AcmeClientProvider.class);
|
||||
when(mockProvider.openConnection(requestUri)).thenReturn(mockUrlConnection);
|
||||
mockHttpConnection = mock(HttpConnector.class);
|
||||
when(mockHttpConnection.openConnection(requestUri)).thenReturn(mockUrlConnection);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -74,7 +73,7 @@ public class ConnectionTest {
|
|||
public void testNoNonceFromHeader() throws AcmeException {
|
||||
when(mockUrlConnection.getHeaderField("Replay-Nonce")).thenReturn(null);
|
||||
|
||||
try (Connection conn = new Connection(mockProvider)) {
|
||||
try (Connection conn = new Connection(mockHttpConnection)) {
|
||||
conn.getNonceFromHeader(mockUrlConnection);
|
||||
fail("Expected to fail");
|
||||
} catch (AcmeException ex) {
|
||||
|
@ -83,7 +82,6 @@ public class ConnectionTest {
|
|||
|
||||
verify(mockUrlConnection).getHeaderField("Replay-Nonce");
|
||||
verifyNoMoreInteractions(mockUrlConnection);
|
||||
verifyZeroInteractions(mockProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -97,14 +95,13 @@ public class ConnectionTest {
|
|||
when(mockUrlConnection.getHeaderField("Replay-Nonce"))
|
||||
.thenReturn(Base64Url.encode(nonce));
|
||||
|
||||
try (Connection conn = new Connection(mockProvider)) {
|
||||
try (Connection conn = new Connection(mockHttpConnection)) {
|
||||
byte[] nonceFromHeader = conn.getNonceFromHeader(mockUrlConnection);
|
||||
assertThat(nonceFromHeader, is(nonce));
|
||||
}
|
||||
|
||||
verify(mockUrlConnection).getHeaderField("Replay-Nonce");
|
||||
verifyNoMoreInteractions(mockUrlConnection);
|
||||
verifyZeroInteractions(mockProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -117,7 +114,7 @@ public class ConnectionTest {
|
|||
|
||||
when(mockUrlConnection.getHeaderField("Replay-Nonce")).thenReturn(badNonce);
|
||||
|
||||
try (Connection conn = new Connection(mockProvider)) {
|
||||
try (Connection conn = new Connection(mockHttpConnection)) {
|
||||
conn.getNonceFromHeader(mockUrlConnection);
|
||||
fail("Expected to fail");
|
||||
} catch (AcmeException ex) {
|
||||
|
@ -126,7 +123,6 @@ public class ConnectionTest {
|
|||
|
||||
verify(mockUrlConnection).getHeaderField("Replay-Nonce");
|
||||
verifyNoMoreInteractions(mockUrlConnection);
|
||||
verifyZeroInteractions(mockProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -136,7 +132,7 @@ public class ConnectionTest {
|
|||
public void testGetLocation() throws Exception {
|
||||
when(mockUrlConnection.getHeaderField("Location")).thenReturn("http://example.com/otherlocation");
|
||||
|
||||
try (Connection conn = new Connection(mockProvider)) {
|
||||
try (Connection conn = new Connection(mockHttpConnection)) {
|
||||
conn.conn = mockUrlConnection;
|
||||
URI location = conn.getLocation();
|
||||
assertThat(location, is(new URI("http://example.com/otherlocation")));
|
||||
|
@ -144,7 +140,6 @@ public class ConnectionTest {
|
|||
|
||||
verify(mockUrlConnection).getHeaderField("Location");
|
||||
verifyNoMoreInteractions(mockUrlConnection);
|
||||
verifyZeroInteractions(mockProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -152,7 +147,7 @@ public class ConnectionTest {
|
|||
*/
|
||||
@Test
|
||||
public void testNoLocation() throws Exception {
|
||||
try (Connection conn = new Connection(mockProvider)) {
|
||||
try (Connection conn = new Connection(mockHttpConnection)) {
|
||||
conn.conn = mockUrlConnection;
|
||||
URI location = conn.getLocation();
|
||||
assertThat(location, is(nullValue()));
|
||||
|
@ -160,7 +155,6 @@ public class ConnectionTest {
|
|||
|
||||
verify(mockUrlConnection).getHeaderField("Location");
|
||||
verifyNoMoreInteractions(mockUrlConnection);
|
||||
verifyZeroInteractions(mockProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -170,14 +164,13 @@ public class ConnectionTest {
|
|||
public void testNoThrowException() throws AcmeException {
|
||||
when(mockUrlConnection.getHeaderField("Content-Type")).thenReturn("application/json");
|
||||
|
||||
try (Connection conn = new Connection(mockProvider)) {
|
||||
try (Connection conn = new Connection(mockHttpConnection)) {
|
||||
conn.conn = mockUrlConnection;
|
||||
conn.throwException();
|
||||
}
|
||||
|
||||
verify(mockUrlConnection).getHeaderField("Content-Type");
|
||||
verifyNoMoreInteractions(mockUrlConnection);
|
||||
verifyZeroInteractions(mockProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -191,7 +184,7 @@ public class ConnectionTest {
|
|||
when(mockUrlConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_FORBIDDEN);
|
||||
when(mockUrlConnection.getErrorStream()).thenReturn(new ByteArrayInputStream(jsonData.getBytes("utf-8")));
|
||||
|
||||
try (Connection conn = new Connection(mockProvider)) {
|
||||
try (Connection conn = new Connection(mockHttpConnection)) {
|
||||
conn.conn = mockUrlConnection;
|
||||
conn.throwException();
|
||||
fail("Expected to fail");
|
||||
|
@ -207,7 +200,6 @@ public class ConnectionTest {
|
|||
verify(mockUrlConnection).getResponseCode();
|
||||
verify(mockUrlConnection).getErrorStream();
|
||||
verifyNoMoreInteractions(mockUrlConnection);
|
||||
verifyZeroInteractions(mockProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -218,7 +210,7 @@ public class ConnectionTest {
|
|||
when(mockUrlConnection.getHeaderField("Content-Type"))
|
||||
.thenReturn("application/problem+json");
|
||||
|
||||
try (Connection conn = new Connection(mockProvider) {
|
||||
try (Connection conn = new Connection(mockHttpConnection) {
|
||||
@Override
|
||||
public Map<String,Object> readJsonResponse() throws AcmeException {
|
||||
Map<String, Object> result = new HashMap<String, Object>();
|
||||
|
@ -240,7 +232,6 @@ public class ConnectionTest {
|
|||
|
||||
verify(mockUrlConnection).getHeaderField("Content-Type");
|
||||
verifyNoMoreInteractions(mockUrlConnection);
|
||||
verifyZeroInteractions(mockProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -254,7 +245,7 @@ public class ConnectionTest {
|
|||
.thenReturn(Base64Url.encode(nonce));
|
||||
|
||||
Session session = new Session();
|
||||
try (Connection conn = new Connection(mockProvider)) {
|
||||
try (Connection conn = new Connection(mockHttpConnection)) {
|
||||
conn.startSession(requestUri, session);
|
||||
}
|
||||
assertThat(session.getNonce(), is(nonce));
|
||||
|
@ -262,9 +253,7 @@ public class ConnectionTest {
|
|||
verify(mockUrlConnection).setRequestMethod("HEAD");
|
||||
verify(mockUrlConnection).connect();
|
||||
verify(mockUrlConnection).getHeaderField("Replay-Nonce");
|
||||
verify(mockProvider).openConnection(requestUri);
|
||||
verifyNoMoreInteractions(mockUrlConnection);
|
||||
verifyNoMoreInteractions(mockProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -274,7 +263,7 @@ public class ConnectionTest {
|
|||
public void testSendRequest() throws Exception {
|
||||
final Set<String> invoked = new HashSet<>();
|
||||
|
||||
try (Connection conn = new Connection(mockProvider) {
|
||||
try (Connection conn = new Connection(mockHttpConnection) {
|
||||
@Override
|
||||
protected void throwException() throws AcmeException {
|
||||
invoked.add("throwException");
|
||||
|
@ -288,9 +277,7 @@ public class ConnectionTest {
|
|||
verify(mockUrlConnection).setDoOutput(false);
|
||||
verify(mockUrlConnection).connect();
|
||||
verify(mockUrlConnection).getResponseCode();
|
||||
verify(mockProvider).openConnection(requestUri);
|
||||
verifyNoMoreInteractions(mockUrlConnection);
|
||||
verifyNoMoreInteractions(mockProvider);
|
||||
assertThat(invoked, hasItem("throwException"));
|
||||
}
|
||||
|
||||
|
@ -308,7 +295,7 @@ public class ConnectionTest {
|
|||
when(mockUrlConnection.getOutputStream()).thenReturn(outputStream);
|
||||
when(mockUrlConnection.getHeaderField("Replay-Nonce")).thenReturn(Base64Url.encode(nonce2));
|
||||
|
||||
try (Connection conn = new Connection(mockProvider) {
|
||||
try (Connection conn = new Connection(mockHttpConnection) {
|
||||
@Override
|
||||
protected void throwException() throws AcmeException {
|
||||
invoked.add("throwException");
|
||||
|
@ -342,9 +329,7 @@ public class ConnectionTest {
|
|||
verify(mockUrlConnection, atLeastOnce()).getHeaderField(anyString());
|
||||
verify(mockUrlConnection).getOutputStream();
|
||||
verify(mockUrlConnection).getResponseCode();
|
||||
verify(mockProvider).openConnection(requestUri);
|
||||
verifyNoMoreInteractions(mockUrlConnection);
|
||||
verifyNoMoreInteractions(mockProvider);
|
||||
assertThat(invoked, hasItems("throwException", "startSession"));
|
||||
|
||||
String[] written = CompactSerializer.deserialize(new String(outputStream.toByteArray(), "utf-8"));
|
||||
|
@ -378,7 +363,7 @@ public class ConnectionTest {
|
|||
when(mockUrlConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK);
|
||||
when(mockUrlConnection.getInputStream()).thenReturn(new ByteArrayInputStream(jsonData.getBytes("utf-8")));
|
||||
|
||||
try (Connection conn = new Connection(mockProvider)) {
|
||||
try (Connection conn = new Connection(mockHttpConnection)) {
|
||||
conn.conn = mockUrlConnection;
|
||||
Map<String, Object> result = conn.readJsonResponse();
|
||||
assertThat(result.keySet(), hasSize(2));
|
||||
|
@ -390,7 +375,6 @@ public class ConnectionTest {
|
|||
verify(mockUrlConnection).getResponseCode();
|
||||
verify(mockUrlConnection).getInputStream();
|
||||
verifyNoMoreInteractions(mockUrlConnection);
|
||||
verifyZeroInteractions(mockProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -408,7 +392,7 @@ public class ConnectionTest {
|
|||
when(mockUrlConnection.getInputStream()).thenReturn(new ByteArrayInputStream(original.getEncoded()));
|
||||
|
||||
X509Certificate downloaded;
|
||||
try (Connection conn = new Connection(mockProvider)) {
|
||||
try (Connection conn = new Connection(mockHttpConnection)) {
|
||||
conn.conn = mockUrlConnection;
|
||||
downloaded = conn.readCertificate();
|
||||
}
|
||||
|
@ -420,7 +404,6 @@ public class ConnectionTest {
|
|||
verify(mockUrlConnection).getHeaderField("Content-Type");
|
||||
verify(mockUrlConnection).getInputStream();
|
||||
verifyNoMoreInteractions(mockUrlConnection);
|
||||
verifyZeroInteractions(mockProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -438,7 +421,7 @@ public class ConnectionTest {
|
|||
when(mockUrlConnection.getHeaderField("Content-Type")).thenReturn("application/json");
|
||||
when(mockUrlConnection.getInputStream()).thenReturn(new ByteArrayInputStream(jsonData.toString().getBytes("utf-8")));
|
||||
|
||||
try (Connection conn = new Connection(mockProvider)) {
|
||||
try (Connection conn = new Connection(mockHttpConnection)) {
|
||||
conn.conn = mockUrlConnection;
|
||||
Map<Resource, URI> result = conn.readDirectory();
|
||||
assertThat(result.keySet(), hasSize(2));
|
||||
|
@ -450,7 +433,6 @@ public class ConnectionTest {
|
|||
verify(mockUrlConnection).getHeaderField("Content-Type");
|
||||
verify(mockUrlConnection).getInputStream();
|
||||
verifyNoMoreInteractions(mockUrlConnection);
|
||||
verifyZeroInteractions(mockProvider);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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 static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link HttpConnector}.
|
||||
*
|
||||
* @author Richard "Shred" Körber
|
||||
*/
|
||||
public class HttpConnectorTest {
|
||||
|
||||
/**
|
||||
* Test if a HTTP connection can be opened.
|
||||
* <p>
|
||||
* 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));
|
||||
}
|
||||
|
||||
}
|
|
@ -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.
|
||||
* <p>
|
||||
* 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.
|
||||
|
|
|
@ -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 <em>Let's Encrypt</em>.
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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.
|
||||
* <p>
|
||||
* 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)));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
* <p>
|
||||
* 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)));
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue