mirror of https://github.com/shred/acme4j
Boulder needs a pre-draft-15 compatibility mode
parent
1297ca4de2
commit
bb35678c2d
|
@ -137,32 +137,7 @@ public class DefaultConnection implements Connection {
|
|||
|
||||
@Override
|
||||
public void sendRequest(URL url, Session session) throws AcmeException {
|
||||
Objects.requireNonNull(url, "url");
|
||||
Objects.requireNonNull(session, "session");
|
||||
assertConnectionIsClosed();
|
||||
|
||||
LOG.debug("GET {}", url);
|
||||
|
||||
try {
|
||||
conn = httpConnector.openConnection(url, session.getProxy());
|
||||
conn.setRequestMethod("GET");
|
||||
conn.setRequestProperty(ACCEPT_HEADER, MIME_JSON);
|
||||
conn.setRequestProperty(ACCEPT_CHARSET_HEADER, DEFAULT_CHARSET);
|
||||
conn.setRequestProperty(ACCEPT_LANGUAGE_HEADER, session.getLocale().toLanguageTag());
|
||||
conn.setDoOutput(false);
|
||||
|
||||
conn.connect();
|
||||
|
||||
logHeaders();
|
||||
|
||||
int rc = conn.getResponseCode();
|
||||
if (rc != HttpURLConnection.HTTP_OK) {
|
||||
throwAcmeException();
|
||||
}
|
||||
|
||||
} catch (IOException ex) {
|
||||
throw new AcmeNetworkException(ex);
|
||||
}
|
||||
sendRequest(url, session, MIME_JSON);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -189,31 +164,6 @@ public class DefaultConnection implements Connection {
|
|||
return sendSignedRequest(url, claims, session, keypair, null, MIME_JSON);
|
||||
}
|
||||
|
||||
private int sendSignedRequest(URL url, @Nullable JSONBuilder claims, Session session,
|
||||
KeyPair keypair, @Nullable URL accountLocation, String accept) throws AcmeException {
|
||||
Objects.requireNonNull(url, "url");
|
||||
Objects.requireNonNull(session, "session");
|
||||
Objects.requireNonNull(keypair, "keypair");
|
||||
Objects.requireNonNull(accept, "accept");
|
||||
assertConnectionIsClosed();
|
||||
|
||||
AcmeException lastException = null;
|
||||
|
||||
for (int attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
||||
try {
|
||||
return performRequest(url, claims, session, keypair, accountLocation, accept);
|
||||
} catch (AcmeServerException ex) {
|
||||
if (!BAD_NONCE_ERROR.equals(ex.getType())) {
|
||||
throw ex;
|
||||
}
|
||||
lastException = ex;
|
||||
LOG.info("Bad Replay Nonce, trying again (attempt {}/{})", attempt, MAX_ATTEMPTS);
|
||||
}
|
||||
}
|
||||
|
||||
throw new AcmeException("Too many reattempts", lastException);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CheckForNull
|
||||
public JSON readJsonResponse() throws AcmeException {
|
||||
|
@ -320,6 +270,96 @@ public class DefaultConnection implements Connection {
|
|||
conn = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an unsigned GET request.
|
||||
*
|
||||
* @param url
|
||||
* {@link URL} to send the request to.
|
||||
* @param session
|
||||
* {@link Session} instance to be used for signing and tracking
|
||||
* @param accept
|
||||
* Accept header
|
||||
* @return HTTP 200 class status that was returned
|
||||
*/
|
||||
protected int sendRequest(URL url, Session session, String accept) throws AcmeException {
|
||||
Objects.requireNonNull(url, "url");
|
||||
Objects.requireNonNull(session, "session");
|
||||
Objects.requireNonNull(accept, "accept");
|
||||
assertConnectionIsClosed();
|
||||
|
||||
LOG.debug("GET {}", url);
|
||||
|
||||
try {
|
||||
conn = httpConnector.openConnection(url, session.getProxy());
|
||||
conn.setRequestMethod("GET");
|
||||
conn.setRequestProperty(ACCEPT_HEADER, accept);
|
||||
conn.setRequestProperty(ACCEPT_CHARSET_HEADER, DEFAULT_CHARSET);
|
||||
conn.setRequestProperty(ACCEPT_LANGUAGE_HEADER, session.getLocale().toLanguageTag());
|
||||
conn.setDoOutput(false);
|
||||
|
||||
conn.connect();
|
||||
|
||||
logHeaders();
|
||||
|
||||
String nonce = getNonce();
|
||||
if (nonce != null) {
|
||||
session.setNonce(nonce);
|
||||
}
|
||||
|
||||
int rc = conn.getResponseCode();
|
||||
if (rc != HttpURLConnection.HTTP_OK && rc != HttpURLConnection.HTTP_CREATED) {
|
||||
throwAcmeException();
|
||||
}
|
||||
return rc;
|
||||
} catch (IOException ex) {
|
||||
throw new AcmeNetworkException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a signed POST request.
|
||||
*
|
||||
* @param url
|
||||
* {@link URL} to send the request to.
|
||||
* @param claims
|
||||
* {@link JSONBuilder} containing claims. {@code null} for POST-as-GET
|
||||
* request.
|
||||
* @param session
|
||||
* {@link Session} instance to be used for signing and tracking
|
||||
* @param keypair
|
||||
* {@link KeyPair} to be used for signing
|
||||
* @param accountLocation
|
||||
* If set, the account location is set as "kid" header. If {@code null},
|
||||
* the public key is set as "jwk" header.
|
||||
* @param accept
|
||||
* Accept header
|
||||
* @return HTTP 200 class status that was returned
|
||||
*/
|
||||
protected int sendSignedRequest(URL url, @Nullable JSONBuilder claims, Session session,
|
||||
KeyPair keypair, @Nullable URL accountLocation, String accept) throws AcmeException {
|
||||
Objects.requireNonNull(url, "url");
|
||||
Objects.requireNonNull(session, "session");
|
||||
Objects.requireNonNull(keypair, "keypair");
|
||||
Objects.requireNonNull(accept, "accept");
|
||||
assertConnectionIsClosed();
|
||||
|
||||
AcmeException lastException = null;
|
||||
|
||||
for (int attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
||||
try {
|
||||
return performRequest(url, claims, session, keypair, accountLocation, accept);
|
||||
} catch (AcmeServerException ex) {
|
||||
if (!BAD_NONCE_ERROR.equals(ex.getType())) {
|
||||
throw ex;
|
||||
}
|
||||
lastException = ex;
|
||||
LOG.info("Bad Replay Nonce, trying again (attempt {}/{})", attempt, MAX_ATTEMPTS);
|
||||
}
|
||||
}
|
||||
|
||||
throw new AcmeException("Too many reattempts", lastException);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the POST request.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* acme4j - Java ACME client
|
||||
*
|
||||
* Copyright (C) 2018 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.net.URL;
|
||||
|
||||
import org.shredzone.acme4j.Login;
|
||||
import org.shredzone.acme4j.exception.AcmeException;
|
||||
|
||||
/**
|
||||
* This {@link Connection} is used for servers that do not implement the POST-as-GET
|
||||
* feature that was introduced in ACME draft-15.
|
||||
*
|
||||
* @since 2.4
|
||||
* @deprecated Only meant for compatibility purposes. If your server needs this
|
||||
* connection, it should be fixed soon.
|
||||
*/
|
||||
@Deprecated
|
||||
public class PreDraft15Connection extends DefaultConnection {
|
||||
|
||||
private static final String MIME_JSON = "application/json";
|
||||
private static final String MIME_CERTIFICATE_CHAIN = "application/pem-certificate-chain";
|
||||
|
||||
public PreDraft15Connection(HttpConnector httpConnector) {
|
||||
super(httpConnector);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sendCertificateRequest(URL url, Login login) throws AcmeException {
|
||||
return sendRequest(url, login.getSession(), MIME_CERTIFICATE_CHAIN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sendSignedPostAsGetRequest(URL url, Login login) throws AcmeException {
|
||||
return sendRequest(url, login.getSession(), MIME_JSON);
|
||||
}
|
||||
|
||||
}
|
|
@ -19,6 +19,7 @@ import java.net.URL;
|
|||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
|
||||
import org.shredzone.acme4j.connector.Connection;
|
||||
import org.shredzone.acme4j.exception.AcmeProtocolException;
|
||||
import org.shredzone.acme4j.provider.AbstractAcmeProvider;
|
||||
import org.shredzone.acme4j.provider.AcmeProvider;
|
||||
|
@ -64,4 +65,10 @@ public class LetsEncryptAcmeProvider extends AbstractAcmeProvider {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public Connection connect() {
|
||||
return new org.shredzone.acme4j.connector.PreDraft15Connection(createHttpConnector());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -648,7 +648,12 @@ public class DefaultConnectionTest {
|
|||
public void testSendRequest() throws Exception {
|
||||
when(mockUrlConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK);
|
||||
|
||||
try (DefaultConnection conn = new DefaultConnection(mockHttpConnection)) {
|
||||
try (DefaultConnection conn = new DefaultConnection(mockHttpConnection) {
|
||||
@Override
|
||||
public String getNonce() {
|
||||
return null;
|
||||
}
|
||||
}) {
|
||||
conn.sendRequest(requestUrl, session);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* acme4j - Java ACME client
|
||||
*
|
||||
* Copyright (C) 2018 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.mockito.Mockito.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.Proxy;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.security.KeyPair;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.jose4j.base64url.Base64Url;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import org.shredzone.acme4j.Login;
|
||||
import org.shredzone.acme4j.Session;
|
||||
import org.shredzone.acme4j.exception.AcmeException;
|
||||
import org.shredzone.acme4j.provider.AcmeProvider;
|
||||
import org.shredzone.acme4j.toolbox.TestUtils;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link PreDraft15Connection}.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class PreDraft15ConnectionTest {
|
||||
|
||||
private URL requestUrl = TestUtils.url("http://example.com/acme/");
|
||||
private URL accountUrl = TestUtils.url(TestUtils.ACCOUNT_URL);
|
||||
private HttpURLConnection mockUrlConnection;
|
||||
private HttpConnector mockHttpConnection;
|
||||
private Session session;
|
||||
private Login login;
|
||||
private KeyPair keyPair;
|
||||
|
||||
@Before
|
||||
public void setup() throws AcmeException, IOException {
|
||||
mockUrlConnection = mock(HttpURLConnection.class);
|
||||
|
||||
mockHttpConnection = mock(HttpConnector.class);
|
||||
when(mockHttpConnection.openConnection(requestUrl, Proxy.NO_PROXY)).thenReturn(mockUrlConnection);
|
||||
|
||||
final AcmeProvider mockProvider = mock(AcmeProvider.class);
|
||||
when(mockProvider.directory(
|
||||
ArgumentMatchers.any(Session.class),
|
||||
ArgumentMatchers.eq(URI.create(TestUtils.ACME_SERVER_URI))))
|
||||
.thenReturn(TestUtils.getJSON("directory"));
|
||||
|
||||
session = TestUtils.session(mockProvider);
|
||||
session.setLocale(Locale.JAPAN);
|
||||
|
||||
keyPair = TestUtils.createKeyPair();
|
||||
|
||||
login = session.login(accountUrl, keyPair);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test signed POST-as-GET requests in compatibility mode.
|
||||
*/
|
||||
@Test
|
||||
public void testSendSignedPostAsGetRequest() throws Exception {
|
||||
when(mockUrlConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK);
|
||||
|
||||
try (PreDraft15Connection conn = new PreDraft15Connection(mockHttpConnection) {
|
||||
@Override
|
||||
public String getNonce() {
|
||||
return Base64Url.encode("foo-nonce-1-foo".getBytes());
|
||||
}
|
||||
}) {
|
||||
conn.sendSignedPostAsGetRequest(requestUrl, login);
|
||||
}
|
||||
|
||||
verify(mockUrlConnection).setRequestMethod("GET");
|
||||
verify(mockUrlConnection).setRequestProperty("Accept", "application/json");
|
||||
verify(mockUrlConnection).setRequestProperty("Accept-Charset", "utf-8");
|
||||
verify(mockUrlConnection).setRequestProperty("Accept-Language", "ja-JP");
|
||||
verify(mockUrlConnection).setDoOutput(false);
|
||||
verify(mockUrlConnection).connect();
|
||||
verify(mockUrlConnection).getResponseCode();
|
||||
verify(mockUrlConnection, atLeast(0)).getHeaderFields();
|
||||
verifyNoMoreInteractions(mockUrlConnection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test certificate POST-as-GET requests in compatibility mode.
|
||||
*/
|
||||
@Test
|
||||
public void testSendCertificateRequest() throws Exception {
|
||||
when(mockUrlConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK);
|
||||
|
||||
try (PreDraft15Connection conn = new PreDraft15Connection(mockHttpConnection) {
|
||||
@Override
|
||||
public String getNonce() {
|
||||
return Base64Url.encode("foo-nonce-1-foo".getBytes());
|
||||
}
|
||||
}) {
|
||||
conn.sendCertificateRequest(requestUrl, login);
|
||||
}
|
||||
|
||||
verify(mockUrlConnection).setRequestMethod("GET");
|
||||
verify(mockUrlConnection).setRequestProperty("Accept", "application/pem-certificate-chain");
|
||||
verify(mockUrlConnection).setRequestProperty("Accept-Charset", "utf-8");
|
||||
verify(mockUrlConnection).setRequestProperty("Accept-Language", "ja-JP");
|
||||
verify(mockUrlConnection).setDoOutput(false);
|
||||
verify(mockUrlConnection).connect();
|
||||
verify(mockUrlConnection).getResponseCode();
|
||||
verify(mockUrlConnection, atLeast(0)).getHeaderFields();
|
||||
verifyNoMoreInteractions(mockUrlConnection);
|
||||
}
|
||||
|
||||
}
|
|
@ -13,6 +13,7 @@
|
|||
*/
|
||||
package org.shredzone.acme4j.provider.letsencrypt;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.shredzone.acme4j.toolbox.TestUtils.url;
|
||||
|
@ -21,6 +22,7 @@ import java.net.URI;
|
|||
import java.net.URISyntaxException;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.shredzone.acme4j.connector.Connection;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link LetsEncryptAcmeProvider}.
|
||||
|
@ -66,4 +68,15 @@ public class LetsEncryptAcmeProviderTest {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that Boulder is still having pre draft-15 connections.
|
||||
*/
|
||||
@Test
|
||||
@SuppressWarnings("deprecation")
|
||||
public void testConnect() {
|
||||
LetsEncryptAcmeProvider provider = new LetsEncryptAcmeProvider();
|
||||
Connection connection = provider.connect();
|
||||
assertThat(connection, is(instanceOf(org.shredzone.acme4j.connector.PreDraft15Connection.class)));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue