From 2ceffa42e38f45fd4c2e179508f5cee4e6753728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20K=C3=B6rber?= Date: Thu, 14 Dec 2017 00:05:46 +0100 Subject: [PATCH] Retry on bad nonce error --- .../java/org/shredzone/acme4j/Account.java | 7 +- .../org/shredzone/acme4j/AccountBuilder.java | 3 +- .../org/shredzone/acme4j/Authorization.java | 3 - .../org/shredzone/acme4j/Certificate.java | 4 - .../main/java/org/shredzone/acme4j/Order.java | 3 - .../org/shredzone/acme4j/OrderBuilder.java | 3 +- .../shredzone/acme4j/challenge/Challenge.java | 4 - .../acme4j/connector/Connection.java | 28 +- .../acme4j/connector/DefaultConnection.java | 269 ++++++++++-------- .../acme4j/connector/ResourceIterator.java | 2 - .../acme4j/provider/AbstractAcmeProvider.java | 2 - .../shredzone/acme4j/AccountBuilderTest.java | 31 +- .../org/shredzone/acme4j/AccountTest.java | 57 +--- .../shredzone/acme4j/AuthorizationTest.java | 26 +- .../org/shredzone/acme4j/CertificateTest.java | 30 +- .../shredzone/acme4j/OrderBuilderTest.java | 7 +- .../java/org/shredzone/acme4j/OrderTest.java | 21 +- .../acme4j/challenge/ChallengeTest.java | 32 +-- .../connector/DefaultConnectionTest.java | 80 ++++-- .../acme4j/connector/DummyConnection.java | 10 +- .../connector/ResourceIteratorTest.java | 10 +- .../provider/AbstractAcmeProviderTest.java | 3 - .../shredzone/acme4j/toolbox/TestUtils.java | 2 + 23 files changed, 261 insertions(+), 376 deletions(-) diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/Account.java b/acme4j-client/src/main/java/org/shredzone/acme4j/Account.java index ba22be74..5f13bbed 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/Account.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/Account.java @@ -131,7 +131,6 @@ public class Account extends AcmeResource { JSONBuilder claims = new JSONBuilder(); conn.sendSignedRequest(getLocation(), claims, getSession()); - conn.accept(HttpURLConnection.HTTP_OK); unmarshal(conn.readJsonResponse()); } @@ -180,8 +179,7 @@ public class Account extends AcmeResource { .put("type", "dns") .put("value", toAce(domain)); - conn.sendSignedRequest(newAuthzUrl, claims, getSession()); - conn.accept(HttpURLConnection.HTTP_CREATED); + conn.sendSignedRequest(newAuthzUrl, claims, getSession(), HttpURLConnection.HTTP_CREATED); JSON json = conn.readJsonResponse(); @@ -231,7 +229,6 @@ public class Account extends AcmeResource { outerClaim.put("payload", innerJws.getEncodedPayload()); conn.sendSignedRequest(keyChangeUrl, outerClaim, getSession()); - conn.accept(HttpURLConnection.HTTP_OK); getSession().setKeyPair(newKeyPair); } catch (JoseException ex) { @@ -252,7 +249,6 @@ public class Account extends AcmeResource { claims.put(KEY_STATUS, "deactivated"); conn.sendSignedRequest(getLocation(), claims, getSession()); - conn.accept(HttpURLConnection.HTTP_OK); unmarshal(conn.readJsonResponse()); } @@ -366,7 +362,6 @@ public class Account extends AcmeResource { } conn.sendSignedRequest(getLocation(), claims, getSession()); - conn.accept(HttpURLConnection.HTTP_OK); JSON json = conn.readJsonResponse(); unmarshal(json); diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/AccountBuilder.java b/acme4j-client/src/main/java/org/shredzone/acme4j/AccountBuilder.java index 29a87306..0a81a02b 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/AccountBuilder.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/AccountBuilder.java @@ -177,8 +177,7 @@ public class AccountBuilder { claims.put("only-return-existing", onlyExisting); } - conn.sendSignedRequest(resourceUrl, claims, session, true); - int resp = conn.accept(HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_CREATED); + int resp = conn.sendSignedRequest(resourceUrl, claims, session, true, HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_CREATED); URL location = conn.getLocation(); diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/Authorization.java b/acme4j-client/src/main/java/org/shredzone/acme4j/Authorization.java index dc850db5..baefec3d 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/Authorization.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/Authorization.java @@ -16,7 +16,6 @@ package org.shredzone.acme4j; import static java.util.stream.Collectors.toList; import static org.shredzone.acme4j.toolbox.AcmeUtils.parseTimestamp; -import java.net.HttpURLConnection; import java.net.URL; import java.time.Instant; import java.util.Collections; @@ -131,7 +130,6 @@ public class Authorization extends AcmeResource { LOG.debug("update"); try (Connection conn = getSession().provider().connect()) { conn.sendRequest(getLocation(), getSession()); - conn.accept(HttpURLConnection.HTTP_OK); unmarshalAuthorization(conn.readJsonResponse()); @@ -149,7 +147,6 @@ public class Authorization extends AcmeResource { claims.put("status", "deactivated"); conn.sendSignedRequest(getLocation(), claims, getSession()); - conn.accept(HttpURLConnection.HTTP_OK); unmarshalAuthorization(conn.readJsonResponse()); } diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/Certificate.java b/acme4j-client/src/main/java/org/shredzone/acme4j/Certificate.java index 72288268..dab4b7e1 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/Certificate.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/Certificate.java @@ -17,7 +17,6 @@ import static java.util.Collections.unmodifiableList; import java.io.IOException; import java.io.Writer; -import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; import java.security.KeyPair; @@ -82,7 +81,6 @@ public class Certificate extends AcmeResource { LOG.debug("download"); try (Connection conn = getSession().provider().connect()) { conn.sendRequest(getLocation(), getSession()); - conn.accept(HttpURLConnection.HTTP_OK); alternates = new ArrayList<>(conn.getLinks("alternate")); certChain = new ArrayList<>(conn.readCertificates()); } @@ -172,7 +170,6 @@ public class Certificate extends AcmeResource { } conn.sendSignedRequest(resUrl, claims, getSession(), true); - conn.accept(HttpURLConnection.HTTP_OK); } catch (CertificateEncodingException ex) { throw new AcmeProtocolException("Invalid certificate", ex); } @@ -211,7 +208,6 @@ public class Certificate extends AcmeResource { } conn.sendSignedRequest(resUrl, claims, session, true); - conn.accept(HttpURLConnection.HTTP_OK); } catch (CertificateEncodingException ex) { throw new AcmeProtocolException("Invalid certificate", ex); } diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/Order.java b/acme4j-client/src/main/java/org/shredzone/acme4j/Order.java index 4170ab4a..18137e4f 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/Order.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/Order.java @@ -15,7 +15,6 @@ package org.shredzone.acme4j; import static java.util.stream.Collectors.toList; -import java.net.HttpURLConnection; import java.net.URL; import java.time.Instant; import java.util.List; @@ -153,7 +152,6 @@ public class Order extends AcmeResource { claims.putBase64("csr", csr); conn.sendSignedRequest(getFinalizeLocation(), claims, getSession()); - conn.accept(HttpURLConnection.HTTP_OK); } loaded = false; // invalidate this object } @@ -173,7 +171,6 @@ public class Order extends AcmeResource { LOG.debug("update"); try (Connection conn = getSession().provider().connect()) { conn.sendRequest(getLocation(), getSession()); - conn.accept(HttpURLConnection.HTTP_OK); JSON json = conn.readJsonResponse(); unmarshal(json); diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/OrderBuilder.java b/acme4j-client/src/main/java/org/shredzone/acme4j/OrderBuilder.java index 2a2eb6dd..1ff98d42 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/OrderBuilder.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/OrderBuilder.java @@ -148,8 +148,7 @@ public class OrderBuilder { claims.put("notAfter", notAfter); } - conn.sendSignedRequest(session.resourceUrl(Resource.NEW_ORDER), claims, session); - conn.accept(HttpURLConnection.HTTP_CREATED); + conn.sendSignedRequest(session.resourceUrl(Resource.NEW_ORDER), claims, session, HttpURLConnection.HTTP_CREATED); JSON json = conn.readJsonResponse(); diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/Challenge.java b/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/Challenge.java index 5c235ed1..a77434f6 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/Challenge.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/Challenge.java @@ -15,7 +15,6 @@ package org.shredzone.acme4j.challenge; import static java.util.stream.Collectors.toList; -import java.net.HttpURLConnection; import java.net.URL; import java.time.Instant; import java.util.Collections; @@ -84,7 +83,6 @@ public class Challenge extends AcmeResource { LOG.debug("bind"); try (Connection conn = session.provider().connect()) { conn.sendRequest(location, session); - conn.accept(HttpURLConnection.HTTP_OK); JSON json = conn.readJsonResponse(); if (!(json.contains(KEY_TYPE))) { @@ -218,7 +216,6 @@ public class Challenge extends AcmeResource { respond(claims); conn.sendSignedRequest(getLocation(), claims, getSession()); - conn.accept(HttpURLConnection.HTTP_OK); unmarshall(conn.readJsonResponse()); } @@ -238,7 +235,6 @@ public class Challenge extends AcmeResource { LOG.debug("update"); try (Connection conn = getSession().provider().connect()) { conn.sendRequest(getLocation(), getSession()); - conn.accept(HttpURLConnection.HTTP_OK); unmarshall(conn.readJsonResponse()); 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 5876a044..1c9b718b 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 @@ -13,6 +13,7 @@ */ package org.shredzone.acme4j.connector; +import java.net.HttpURLConnection; import java.net.URL; import java.security.cert.X509Certificate; import java.util.Collection; @@ -39,6 +40,9 @@ public interface Connection extends AutoCloseable { /** * Sends a simple GET request. + *

+ * If the response code was not {@link HttpURLConnection#HTTP_OK}, an + * {@link AcmeException} matching the error is raised. * * @param url * {@link URL} to send the request to. @@ -57,12 +61,19 @@ public interface Connection extends AutoCloseable { * {@link JSONBuilder} containing claims. Must not be {@code null}. * @param session * {@link Session} instance to be used for signing and tracking + * @param httpStatus + * Acceptable HTTP states. 200 OK if empty. + * @return HTTP 200 class status that was returned */ - void sendSignedRequest(URL url, JSONBuilder claims, Session session) throws AcmeException; + int sendSignedRequest(URL url, JSONBuilder claims, Session session, int... httpStatus) + throws AcmeException; /** * Sends a signed POST request. If the session's KeyIdentifier is set, a "kid" * protected header field is sent. If not, a "jwk" protected header field is sent. + *

+ * If the server does not return a 200 class status code, an {@link AcmeException} is + * raised matching the error. * * @param url * {@link URL} to send the request to. @@ -74,19 +85,12 @@ public interface Connection extends AutoCloseable { * {@code true} to enforce a "jwk" header field even if a KeyIdentifier is * set, {@code false} to choose between "kid" and "jwk" header field * automatically - */ - void sendSignedRequest(URL url, JSONBuilder claims, Session session, boolean enforceJwk) - throws AcmeException; - - /** - * Checks if the HTTP response status is in the given list of acceptable HTTP states, - * otherwise raises an {@link AcmeException} matching the error. - * * @param httpStatus - * Acceptable HTTP states - * @return Actual HTTP status that was accepted + * Acceptable HTTP states. 200 OK if empty. + * @return HTTP 200 class status that was returned */ - int accept(int... httpStatus) throws AcmeException; + int sendSignedRequest(URL url, JSONBuilder claims, Session session, boolean enforceJwk, int... httpStatus) + throws AcmeException; /** * Reads a server response as JSON data. diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/connector/DefaultConnection.java b/acme4j-client/src/main/java/org/shredzone/acme4j/connector/DefaultConnection.java index 5978a61c..f129e1d7 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/connector/DefaultConnection.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/connector/DefaultConnection.java @@ -35,7 +35,6 @@ import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.OptionalInt; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -78,6 +77,9 @@ public class DefaultConnection implements Connection { private static final Pattern BASE64URL_PATTERN = Pattern.compile("[0-9A-Za-z_-]+"); + private static final URI BAD_NONCE_ERROR = URI.create("urn:ietf:params:acme:error:badNonce"); + private static final int MAX_ATTEMPTS = 10; + protected final HttpConnector httpConnector; protected HttpURLConnection conn; @@ -107,8 +109,7 @@ public class DefaultConnection implements Connection { int rc = conn.getResponseCode(); if (rc != HttpURLConnection.HTTP_OK && rc != HttpURLConnection.HTTP_NO_CONTENT) { - throw new AcmeProtocolException("Fetching a nonce returned " + rc + " " - + conn.getResponseMessage()); + throwAcmeException(); } updateSession(session); @@ -141,108 +142,49 @@ public class DefaultConnection implements Connection { conn.connect(); logHeaders(); + + int rc = conn.getResponseCode(); + if (rc != HttpURLConnection.HTTP_OK) { + throwAcmeException(); + } + } catch (IOException ex) { throw new AcmeNetworkException(ex); } } @Override - public void sendSignedRequest(URL url, JSONBuilder claims, Session session) throws AcmeException { + public int sendSignedRequest(URL url, JSONBuilder claims, Session session, int... httpStatus) throws AcmeException { if (session.getKeyIdentifier() == null) { throw new IllegalStateException("session has no KeyIdentifier set"); } - sendSignedRequest(url, claims, session, false); + return sendSignedRequest(url, claims, session, false, httpStatus); } @Override - public void sendSignedRequest(URL url, JSONBuilder claims, Session session, boolean enforceJwk) + public int sendSignedRequest(URL url, JSONBuilder claims, Session session, boolean enforceJwk, int... httpStatus) throws AcmeException { Objects.requireNonNull(url, "url"); Objects.requireNonNull(claims, "claims"); Objects.requireNonNull(session, "session"); assertConnectionIsClosed(); - try { - KeyPair keypair = session.getKeyPair(); + AcmeException lastException = null; - if (session.getNonce() == null) { - resetNonce(session); + for (int attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) { + try { + return performRequest(url, claims, session, enforceJwk, httpStatus); + } 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); } - - conn = httpConnector.openConnection(url); - conn.setRequestMethod("POST"); - conn.setRequestProperty(ACCEPT_HEADER, "application/json"); - conn.setRequestProperty(ACCEPT_CHARSET_HEADER, DEFAULT_CHARSET); - conn.setRequestProperty(ACCEPT_LANGUAGE_HEADER, session.getLocale().toLanguageTag()); - conn.setRequestProperty(CONTENT_TYPE_HEADER, "application/jose+json"); - conn.setDoOutput(true); - - final PublicJsonWebKey jwk = PublicJsonWebKey.Factory.newPublicJwk(keypair.getPublic()); - JsonWebSignature jws = new JsonWebSignature(); - jws.setPayload(claims.toString()); - jws.getHeaders().setObjectHeaderValue("nonce", Base64Url.encode(session.getNonce())); - jws.getHeaders().setObjectHeaderValue("url", url); - if (enforceJwk || session.getKeyIdentifier() == null) { - jws.getHeaders().setJwkHeaderValue("jwk", jwk); - } else { - jws.getHeaders().setObjectHeaderValue("kid", session.getKeyIdentifier()); - } - - jws.setAlgorithmHeaderValue(keyAlgorithm(jwk)); - jws.setKey(keypair.getPrivate()); - jws.sign(); - - if (LOG.isDebugEnabled()) { - LOG.debug("POST {}", url); - LOG.debug(" Payload: {}", claims.toString()); - LOG.debug(" JWS Header: {}", jws.getHeaders().getFullHeaderAsJsonString()); - } - - JSONBuilder jb = new JSONBuilder(); - jb.put("protected", jws.getHeaders().getEncodedHeader()); - jb.put("payload", jws.getEncodedPayload()); - jb.put("signature", jws.getEncodedSignature()); - byte[] outputData = jb.toString().getBytes(DEFAULT_CHARSET); - - conn.setFixedLengthStreamingMode(outputData.length); - conn.connect(); - - try (OutputStream out = conn.getOutputStream()) { - out.write(outputData); - } - - logHeaders(); - - updateSession(session); - } catch (IOException ex) { - throw new AcmeNetworkException(ex); - } catch (JoseException ex) { - throw new AcmeProtocolException("Failed to generate a JSON request", ex); } - } - @Override - public int accept(int... httpStatus) throws AcmeException { - assertConnectionIsOpen(); - - try { - int rc = conn.getResponseCode(); - OptionalInt match = Arrays.stream(httpStatus).filter(s -> s == rc).findFirst(); - if (match.isPresent()) { - return match.getAsInt(); - } - - String contentType = AcmeUtils.getContentType(conn.getHeaderField(CONTENT_TYPE_HEADER)); - if (!"application/problem+json".equals(contentType)) { - throw new AcmeException("HTTP " + rc + ": " + conn.getResponseMessage()); - } - - Problem problem = new Problem(readJsonResponse(), conn.getURL()); - throw createAcmeException(problem); - } catch (IOException ex) { - throw new AcmeNetworkException(ex); - } + throw new AcmeException("Too many reattempts", lastException); } @Override @@ -345,6 +287,91 @@ public class DefaultConnection implements Connection { conn = null; } + /** + * Performs the POST request. + * + * @param url + * {@link URL} to send the request to. + * @param claims + * {@link JSONBuilder} containing claims. Must not be {@code null}. + * @param session + * {@link Session} instance to be used for signing and tracking + * @param enforceJwk + * {@code true} to enforce a "jwk" header field even if a KeyIdentifier is + * set, {@code false} to choose between "kid" and "jwk" header field + * automatically + * @param httpStatus + * Acceptable HTTP states. 200 OK if empty. + * @return HTTP 200 class status that was returned + */ + private int performRequest(URL url, JSONBuilder claims, Session session, boolean enforceJwk, int... httpStatus) + throws AcmeException { + try { + KeyPair keypair = session.getKeyPair(); + + if (session.getNonce() == null) { + resetNonce(session); + } + + conn = httpConnector.openConnection(url); + conn.setRequestMethod("POST"); + conn.setRequestProperty(ACCEPT_HEADER, "application/json"); + conn.setRequestProperty(ACCEPT_CHARSET_HEADER, DEFAULT_CHARSET); + conn.setRequestProperty(ACCEPT_LANGUAGE_HEADER, session.getLocale().toLanguageTag()); + conn.setRequestProperty(CONTENT_TYPE_HEADER, "application/jose+json"); + conn.setDoOutput(true); + + final PublicJsonWebKey jwk = PublicJsonWebKey.Factory.newPublicJwk(keypair.getPublic()); + JsonWebSignature jws = new JsonWebSignature(); + jws.setPayload(claims.toString()); + jws.getHeaders().setObjectHeaderValue("nonce", Base64Url.encode(session.getNonce())); + jws.getHeaders().setObjectHeaderValue("url", url); + if (enforceJwk || session.getKeyIdentifier() == null) { + jws.getHeaders().setJwkHeaderValue("jwk", jwk); + } else { + jws.getHeaders().setObjectHeaderValue("kid", session.getKeyIdentifier()); + } + + jws.setAlgorithmHeaderValue(keyAlgorithm(jwk)); + jws.setKey(keypair.getPrivate()); + jws.sign(); + + if (LOG.isDebugEnabled()) { + LOG.debug("POST {}", url); + LOG.debug(" Payload: {}", claims.toString()); + LOG.debug(" JWS Header: {}", jws.getHeaders().getFullHeaderAsJsonString()); + } + + JSONBuilder jb = new JSONBuilder(); + jb.put("protected", jws.getHeaders().getEncodedHeader()); + jb.put("payload", jws.getEncodedPayload()); + jb.put("signature", jws.getEncodedSignature()); + byte[] outputData = jb.toString().getBytes(DEFAULT_CHARSET); + + conn.setFixedLengthStreamingMode(outputData.length); + conn.connect(); + + try (OutputStream out = conn.getOutputStream()) { + out.write(outputData); + } + + logHeaders(); + + updateSession(session); + + int rc = conn.getResponseCode(); + if ((httpStatus.length == 0 && rc != HttpURLConnection.HTTP_OK) + || (httpStatus.length > 0 && !Arrays.stream(httpStatus).filter(s -> s == rc).findFirst().isPresent())) { + throwAcmeException(); + } + return rc; + } catch (IOException ex) { + throw new AcmeNetworkException(ex); + } catch (JoseException ex) { + throw new AcmeProtocolException("Failed to generate a JSON request", ex); + } + } + /** * Gets the instant sent with the Retry-After header. */ @@ -374,42 +401,52 @@ public class DefaultConnection implements Connection { } /** - * Handles a problem by throwing an exception. If a JSON problem was returned, an - * {@link AcmeServerException} or subtype will be thrown. Otherwise a generic - * {@link AcmeException} is thrown. + * Throws an {@link AcmeException}. This method throws an exception that tries to + * explain the error as precisely as possible. */ - private AcmeException createAcmeException(Problem problem) { - if (problem.getType() == null) { - return new AcmeException(problem.getDetail()); + private void throwAcmeException() throws AcmeException { + try { + String contentType = AcmeUtils.getContentType(conn.getHeaderField(CONTENT_TYPE_HEADER)); + if (!"application/problem+json".equals(contentType)) { + throw new AcmeException("HTTP " + conn.getResponseCode() + ": " + conn.getResponseMessage()); + } + + Problem problem = new Problem(readJsonResponse(), conn.getURL()); + + if (problem.getType() == null) { + throw new AcmeException(problem.getDetail()); + } + + String error = AcmeUtils.stripErrorPrefix(problem.getType().toString()); + + if ("unauthorized".equals(error)) { + throw new AcmeUnauthorizedException(problem); + } + + if ("userActionRequired".equals(error)) { + URI tos = collectLinks("terms-of-service").stream() + .findFirst() + .map(it -> { + try { + return conn.getURL().toURI().resolve(it); + } catch (URISyntaxException ex) { + throw new AcmeProtocolException("Invalid TOS URI", ex); + } + }) + .orElse(null); + throw new AcmeUserActionRequiredException(problem, tos); + } + + if ("rateLimited".equals(error)) { + Optional retryAfter = getRetryAfterHeader(); + Collection rateLimits = getLinks("urn:ietf:params:acme:documentation"); + throw new AcmeRateLimitedException(problem, retryAfter.orElse(null), rateLimits); + } + + throw new AcmeServerException(problem); + } catch (IOException ex) { + throw new AcmeNetworkException(ex); } - - String error = AcmeUtils.stripErrorPrefix(problem.getType().toString()); - - if ("unauthorized".equals(error)) { - return new AcmeUnauthorizedException(problem); - } - - if ("userActionRequired".equals(error)) { - URI tos = collectLinks("terms-of-service").stream() - .findFirst() - .map(it -> { - try { - return conn.getURL().toURI().resolve(it); - } catch (URISyntaxException ex) { - throw new AcmeProtocolException("Invalid TOS URI", ex); - } - }) - .orElse(null); - return new AcmeUserActionRequiredException(problem, tos); - } - - if ("rateLimited".equals(error)) { - Optional retryAfter = getRetryAfterHeader(); - Collection rateLimits = getLinks("urn:ietf:params:acme:documentation"); - return new AcmeRateLimitedException(problem, retryAfter.orElse(null), rateLimits); - } - - return new AcmeServerException(problem); } /** diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/connector/ResourceIterator.java b/acme4j-client/src/main/java/org/shredzone/acme4j/connector/ResourceIterator.java index 2be55688..3b5b1ef9 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/connector/ResourceIterator.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/connector/ResourceIterator.java @@ -13,7 +13,6 @@ */ package org.shredzone.acme4j.connector; -import java.net.HttpURLConnection; import java.net.URL; import java.util.ArrayDeque; import java.util.Deque; @@ -141,7 +140,6 @@ public class ResourceIterator implements Iterator { private void readAndQueue() throws AcmeException { try (Connection conn = session.provider().connect()) { conn.sendRequest(nextUrl, session); - conn.accept(HttpURLConnection.HTTP_OK); JSON json = conn.readJsonResponse(); fillUrlList(json); diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/provider/AbstractAcmeProvider.java b/acme4j-client/src/main/java/org/shredzone/acme4j/provider/AbstractAcmeProvider.java index 38e678f2..66bdd3c6 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/provider/AbstractAcmeProvider.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/provider/AbstractAcmeProvider.java @@ -13,7 +13,6 @@ */ package org.shredzone.acme4j.provider; -import java.net.HttpURLConnection; import java.net.URI; import java.util.Collections; import java.util.HashMap; @@ -52,7 +51,6 @@ public abstract class AbstractAcmeProvider implements AcmeProvider { public JSON directory(Session session, URI serverUri) throws AcmeException { try (Connection conn = connect()) { conn.sendRequest(resolve(serverUri), session); - conn.accept(HttpURLConnection.HTTP_OK); // use nonce header if there is one, saves a HEAD request... conn.updateSession(session); diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/AccountBuilderTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/AccountBuilderTest.java index d14a9d44..ed1fde52 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/AccountBuilderTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/AccountBuilderTest.java @@ -28,7 +28,6 @@ import org.jose4j.jwx.CompactSerializer; import org.jose4j.lang.JoseException; import org.junit.Test; import org.shredzone.acme4j.connector.Resource; -import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.provider.TestableConnectionProvider; import org.shredzone.acme4j.toolbox.AcmeUtils; import org.shredzone.acme4j.toolbox.JSON; @@ -52,31 +51,24 @@ public class AccountBuilderTest { private boolean isUpdate; @Override - public void sendSignedRequest(URL url, JSONBuilder claims, Session session) { + public int sendSignedRequest(URL url, JSONBuilder claims, Session session, int... httpStatus) { assertThat(session, is(notNullValue())); assertThat(url, is(locationUrl)); assertThat(isUpdate, is(false)); isUpdate = true; + assertThat(httpStatus, isIntArrayContainingInAnyOrder()); + return HttpURLConnection.HTTP_OK; } @Override - public void sendSignedRequest(URL url, JSONBuilder claims, Session session, boolean enforceJwk) { + public int sendSignedRequest(URL url, JSONBuilder claims, Session session, boolean enforceJwk, int... httpStatus) { assertThat(session, is(notNullValue())); assertThat(url, is(resourceUrl)); assertThat(claims.toString(), sameJSONAs(getJSON("newAccount").toString())); assertThat(enforceJwk, is(true)); isUpdate = false; - } - - @Override - public int accept(int... httpStatus) throws AcmeException { - if (isUpdate) { - assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_OK)); - return HttpURLConnection.HTTP_OK; - } else { - assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_CREATED)); - return HttpURLConnection.HTTP_CREATED; - } + assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_CREATED)); + return HttpURLConnection.HTTP_CREATED; } @Override @@ -125,7 +117,7 @@ public class AccountBuilderTest { TestableConnectionProvider provider = new TestableConnectionProvider() { @Override - public void sendSignedRequest(URL url, JSONBuilder claims, Session session, boolean enforceJwk) { + public int sendSignedRequest(URL url, JSONBuilder claims, Session session, boolean enforceJwk, int... httpStatus) { try { assertThat(session, is(notNullValue())); assertThat(url, is(resourceUrl)); @@ -162,10 +154,7 @@ public class AccountBuilderTest { ex.printStackTrace(); fail("decoding inner payload failed"); } - } - @Override - public int accept(int... httpStatus) throws AcmeException { assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_CREATED)); return HttpURLConnection.HTTP_CREATED; } @@ -202,15 +191,11 @@ public class AccountBuilderTest { public void testOnlyExistingRegistration() throws Exception { TestableConnectionProvider provider = new TestableConnectionProvider() { @Override - public void sendSignedRequest(URL url, JSONBuilder claims, Session session, boolean enforceJwk) { + public int sendSignedRequest(URL url, JSONBuilder claims, Session session, boolean enforceJwk, int... httpStatus) { assertThat(session, is(notNullValue())); assertThat(url, is(resourceUrl)); assertThat(claims.toString(), sameJSONAs(getJSON("newAccountOnlyExisting").toString())); assertThat(enforceJwk, is(true)); - } - - @Override - public int accept(int... httpStatus) throws AcmeException { assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_CREATED)); return HttpURLConnection.HTTP_OK; } diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/AccountTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/AccountTest.java index c5043146..31f63a48 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/AccountTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/AccountTest.java @@ -63,15 +63,14 @@ public class AccountTest { public void testUpdateAccount() throws AcmeException, IOException, URISyntaxException { TestableConnectionProvider provider = new TestableConnectionProvider() { private JSON jsonResponse; - private Integer response; @Override - public void sendSignedRequest(URL url, JSONBuilder claims, Session session) { + public int sendSignedRequest(URL url, JSONBuilder claims, Session session, int... httpStatus) { assertThat(url, is(locationUrl)); assertThat(claims.toString(), sameJSONAs(getJSON("updateAccount").toString())); assertThat(session, is(notNullValue())); jsonResponse = getJSON("updateAccountResponse"); - response = HttpURLConnection.HTTP_OK; + return HttpURLConnection.HTTP_OK; } @Override @@ -80,17 +79,7 @@ public class AccountTest { jsonResponse = new JSONBuilder() .array("orders", "https://example.com/acme/order/1") .toJSON(); - response = HttpURLConnection.HTTP_OK; - return; } - - response = HttpURLConnection.HTTP_NOT_FOUND; - } - - @Override - public int accept(int... httpStatus) throws AcmeException { - assertThat(response, not(nullValue())); - return response; } @Override @@ -137,14 +126,10 @@ public class AccountTest { TestableConnectionProvider provider = new TestableConnectionProvider() { @Override - public void sendSignedRequest(URL url, JSONBuilder claims, Session session) { + public int sendSignedRequest(URL url, JSONBuilder claims, Session session, int... httpStatus) { requestWasSent.set(true); assertThat(url, is(locationUrl)); - } - - @Override - public int accept(int... httpStatus) throws AcmeException { - assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_OK)); + assertThat(httpStatus, isIntArrayContainingInAnyOrder()); return HttpURLConnection.HTTP_OK; } @@ -190,14 +175,10 @@ public class AccountTest { public void testPreAuthorizeDomain() throws Exception { TestableConnectionProvider provider = new TestableConnectionProvider() { @Override - public void sendSignedRequest(URL url, JSONBuilder claims, Session session) { + public int sendSignedRequest(URL url, JSONBuilder claims, Session session, int... httpStatus) { assertThat(url, is(resourceUrl)); assertThat(claims.toString(), sameJSONAs(getJSON("newAuthorizationRequest").toString())); assertThat(session, is(notNullValue())); - } - - @Override - public int accept(int... httpStatus) throws AcmeException { assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_CREATED)); return HttpURLConnection.HTTP_CREATED; } @@ -248,14 +229,11 @@ public class AccountTest { TestableConnectionProvider provider = new TestableConnectionProvider() { @Override - public void sendSignedRequest(URL url, JSONBuilder claims, Session session) { + public int sendSignedRequest(URL url, JSONBuilder claims, Session session, int... httpStatus) throws AcmeException { assertThat(url, is(resourceUrl)); assertThat(claims.toString(), sameJSONAs(getJSON("newAuthorizationRequest").toString())); assertThat(session, is(notNullValue())); - } - @Override - public int accept(int... httpStatus) throws AcmeException { Problem problem = TestUtils.createProblem(problemType, problemDetail, resourceUrl); throw new AcmeServerException(problem); } @@ -325,7 +303,7 @@ public class AccountTest { final TestableConnectionProvider provider = new TestableConnectionProvider() { @Override - public void sendSignedRequest(URL url, JSONBuilder payload, Session session) { + public int sendSignedRequest(URL url, JSONBuilder payload, Session session, int... httpStatus) { try { assertThat(url, is(locationUrl)); assertThat(session, is(notNullValue())); @@ -356,11 +334,8 @@ public class AccountTest { } catch (JoseException ex) { fail("decoding inner payload failed"); } - } - @Override - public int accept(int... httpStatus) throws AcmeException { - assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_OK)); + assertThat(httpStatus, isIntArrayContainingInAnyOrder()); return HttpURLConnection.HTTP_OK; } @@ -408,16 +383,12 @@ public class AccountTest { public void testDeactivate() throws Exception { TestableConnectionProvider provider = new TestableConnectionProvider() { @Override - public void sendSignedRequest(URL url, JSONBuilder claims, Session session) { + public int sendSignedRequest(URL url, JSONBuilder claims, Session session, int... httpStatus) { JSON json = claims.toJSON(); assertThat(json.get("status").asString(), is("deactivated")); assertThat(url, is(locationUrl)); assertThat(session, is(notNullValue())); - } - - @Override - public int accept(int... httpStatus) throws AcmeException { - assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_OK)); + assertThat(httpStatus, isIntArrayContainingInAnyOrder()); return HttpURLConnection.HTTP_OK; } @@ -442,15 +413,11 @@ public class AccountTest { public void testModify() throws Exception { TestableConnectionProvider provider = new TestableConnectionProvider() { @Override - public void sendSignedRequest(URL url, JSONBuilder claims, Session session) { + public int sendSignedRequest(URL url, JSONBuilder claims, Session session, int... httpStatus) { assertThat(url, is(locationUrl)); assertThat(claims.toString(), sameJSONAs(getJSON("modifyAccount").toString())); assertThat(session, is(notNullValue())); - } - - @Override - public int accept(int... httpStatus) throws AcmeException { - assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_OK)); + assertThat(httpStatus, isIntArrayContainingInAnyOrder()); return HttpURLConnection.HTTP_OK; } diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/AuthorizationTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/AuthorizationTest.java index 0c4d844b..4d55c615 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/AuthorizationTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/AuthorizationTest.java @@ -95,12 +95,6 @@ public class AuthorizationTest { assertThat(url, is(locationUrl)); } - @Override - public int accept(int... httpStatus) throws AcmeException { - assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_OK)); - return HttpURLConnection.HTTP_OK; - } - @Override public JSON readJsonResponse() { return getJSON("updateAuthorizationResponse"); @@ -147,12 +141,6 @@ public class AuthorizationTest { assertThat(url, is(locationUrl)); } - @Override - public int accept(int... httpStatus) throws AcmeException { - assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_OK)); - return HttpURLConnection.HTTP_OK; - } - @Override public JSON readJsonResponse() { return getJSON("updateAuthorizationResponse"); @@ -199,12 +187,6 @@ public class AuthorizationTest { assertThat(url, is(locationUrl)); } - @Override - public int accept(int... httpStatus) throws AcmeException { - assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_OK)); - return HttpURLConnection.HTTP_OK; - } - @Override public JSON readJsonResponse() { return getJSON("updateAuthorizationResponse"); @@ -250,16 +232,12 @@ public class AuthorizationTest { public void testDeactivate() throws Exception { TestableConnectionProvider provider = new TestableConnectionProvider() { @Override - public void sendSignedRequest(URL url, JSONBuilder claims, Session session) { + public int sendSignedRequest(URL url, JSONBuilder claims, Session session, int... httpStatus) { JSON json = claims.toJSON(); assertThat(json.get("status").asString(), is("deactivated")); assertThat(url, is(locationUrl)); assertThat(session, is(notNullValue())); - } - - @Override - public int accept(int... httpStatus) throws AcmeException { - assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_OK)); + assertThat(httpStatus, isIntArrayContainingInAnyOrder()); return HttpURLConnection.HTTP_OK; } diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/CertificateTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/CertificateTest.java index eaed7cef..b13e552f 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/CertificateTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/CertificateTest.java @@ -61,12 +61,6 @@ public class CertificateTest { assertThat(session, is(notNullValue())); } - @Override - public int accept(int... httpStatus) throws AcmeException { - assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_OK)); - return HttpURLConnection.HTTP_OK; - } - @Override public List readCertificates() throws AcmeException { return originalCert; @@ -138,18 +132,14 @@ public class CertificateTest { } @Override - public void sendSignedRequest(URL url, JSONBuilder claims, Session session, boolean enforceJwk) { + public int sendSignedRequest(URL url, JSONBuilder claims, Session session, boolean enforceJwk, int... httpStatus) { assertThat(url, is(resourceUrl)); assertThat(claims.toString(), sameJSONAs(getJSON("revokeCertificateRequest").toString())); assertThat(session, is(notNullValue())); assertThat(session.getKeyIdentifier(), is(nullValue())); assertThat(enforceJwk, is(true)); certRequested = false; - } - - @Override - public int accept(int... httpStatus) throws AcmeException { - assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_OK)); + assertThat(httpStatus, isIntArrayContainingInAnyOrder()); return HttpURLConnection.HTTP_OK; } @@ -192,17 +182,13 @@ public class CertificateTest { } @Override - public void sendSignedRequest(URL url, JSONBuilder claims, Session session, boolean enforceJwk) { + public int sendSignedRequest(URL url, JSONBuilder claims, Session session, boolean enforceJwk, int... httpStatus) { assertThat(url, is(resourceUrl)); assertThat(claims.toString(), sameJSONAs(getJSON("revokeCertificateWithReasonRequest").toString())); assertThat(session, is(notNullValue())); assertThat(enforceJwk, is(true)); certRequested = false; - } - - @Override - public int accept(int... httpStatus) throws AcmeException { - assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_OK)); + assertThat(httpStatus, isIntArrayContainingInAnyOrder()); return HttpURLConnection.HTTP_OK; } @@ -246,18 +232,14 @@ public class CertificateTest { TestableConnectionProvider provider = new TestableConnectionProvider() { @Override - public void sendSignedRequest(URL url, JSONBuilder claims, Session session, boolean enforceJwk) + public int sendSignedRequest(URL url, JSONBuilder claims, Session session, boolean enforceJwk, int... httpStatus) throws AcmeException { assertThat(url, is(resourceUrl)); assertThat(claims.toString(), sameJSONAs(getJSON("revokeCertificateWithReasonRequest").toString())); assertThat(session, is(notNullValue())); assertThat(session.getKeyPair(), is(certKeyPair)); assertThat(enforceJwk, is(true)); - } - - @Override - public int accept(int... httpStatus) throws AcmeException { - assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_OK)); + assertThat(httpStatus, isIntArrayContainingInAnyOrder()); return HttpURLConnection.HTTP_OK; } }; diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/OrderBuilderTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/OrderBuilderTest.java index 7d9941e7..3503e1cb 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/OrderBuilderTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/OrderBuilderTest.java @@ -26,7 +26,6 @@ import java.util.Arrays; import org.junit.Test; import org.shredzone.acme4j.connector.Resource; -import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.provider.TestableConnectionProvider; import org.shredzone.acme4j.toolbox.JSON; import org.shredzone.acme4j.toolbox.JSONBuilder; @@ -49,14 +48,10 @@ public class OrderBuilderTest { TestableConnectionProvider provider = new TestableConnectionProvider() { @Override - public void sendSignedRequest(URL url, JSONBuilder claims, Session session) { + public int sendSignedRequest(URL url, JSONBuilder claims, Session session, int... httpStatus) { assertThat(url, is(resourceUrl)); assertThat(claims.toString(), sameJSONAs(getJSON("requestOrderRequest").toString())); assertThat(session, is(notNullValue())); - } - - @Override - public int accept(int... httpStatus) throws AcmeException { assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_CREATED)); return HttpURLConnection.HTTP_CREATED; } diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/OrderTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/OrderTest.java index e3364f86..96d29559 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/OrderTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/OrderTest.java @@ -26,7 +26,6 @@ import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import org.junit.Test; -import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.provider.TestableConnectionProvider; import org.shredzone.acme4j.toolbox.JSON; import org.shredzone.acme4j.toolbox.JSONBuilder; @@ -51,12 +50,6 @@ public class OrderTest { assertThat(url, is(locationUrl)); } - @Override - public int accept(int... httpStatus) throws AcmeException { - assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_OK)); - return HttpURLConnection.HTTP_OK; - } - @Override public JSON readJsonResponse() { return getJSON("updateOrderResponse"); @@ -106,12 +99,6 @@ public class OrderTest { assertThat(url, is(locationUrl)); } - @Override - public int accept(int... httpStatus) throws AcmeException { - assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_OK)); - return HttpURLConnection.HTTP_OK; - } - @Override public JSON readJsonResponse() { return getJSON("updateOrderResponse"); @@ -153,16 +140,12 @@ public class OrderTest { } @Override - public void sendSignedRequest(URL url, JSONBuilder claims, Session session) { + public int sendSignedRequest(URL url, JSONBuilder claims, Session session, int... httpStatus) { assertThat(url, is(finalizeUrl)); assertThat(claims.toString(), sameJSONAs(getJSON("finalizeRequest").toString())); assertThat(session, is(notNullValue())); isFinalized = true; - } - - @Override - public int accept(int... httpStatus) throws AcmeException { - assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_OK)); + assertThat(httpStatus, isIntArrayContainingInAnyOrder()); return HttpURLConnection.HTTP_OK; } diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/ChallengeTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/ChallengeTest.java index 07f1ecfb..86a2307b 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/ChallengeTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/ChallengeTest.java @@ -66,12 +66,6 @@ public class ChallengeTest { assertThat(url, is(locationUrl)); } - @Override - public int accept(int... httpStatus) throws AcmeException { - assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_OK)); - return HttpURLConnection.HTTP_OK; - } - @Override public JSON readJsonResponse() { return getJSON("updateHttpChallengeResponse"); @@ -165,15 +159,11 @@ public class ChallengeTest { public void testTrigger() throws Exception { TestableConnectionProvider provider = new TestableConnectionProvider() { @Override - public void sendSignedRequest(URL url, JSONBuilder claims, Session session) { + public int sendSignedRequest(URL url, JSONBuilder claims, Session session, int... httpStatus) { assertThat(url, is(resourceUrl)); assertThat(claims.toString(), sameJSONAs(getJSON("triggerHttpChallengeRequest").toString())); assertThat(session, is(notNullValue())); - } - - @Override - public int accept(int... httpStatus) throws AcmeException { - assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_OK)); + assertThat(httpStatus, isIntArrayContainingInAnyOrder()); return HttpURLConnection.HTTP_OK; } @@ -207,12 +197,6 @@ public class ChallengeTest { assertThat(url, is(locationUrl)); } - @Override - public int accept(int... httpStatus) throws AcmeException { - assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_OK)); - return HttpURLConnection.HTTP_OK; - } - @Override public JSON readJsonResponse() { return getJSON("updateHttpChallengeResponse"); @@ -250,12 +234,6 @@ public class ChallengeTest { assertThat(url, is(locationUrl)); } - @Override - public int accept(int... httpStatus) throws AcmeException { - assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_OK)); - return HttpURLConnection.HTTP_OK; - } - @Override public JSON readJsonResponse() { return getJSON("updateHttpChallengeResponse"); @@ -317,12 +295,6 @@ public class ChallengeTest { assertThat(url, is(locationUrl)); } - @Override - public int accept(int... httpStatus) throws AcmeException { - assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_OK)); - return HttpURLConnection.HTTP_OK; - } - @Override public JSON readJsonResponse() { return getJSON("updateAccountResponse"); diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/connector/DefaultConnectionTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/connector/DefaultConnectionTest.java index 70a454e9..3faebe75 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/connector/DefaultConnectionTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/connector/DefaultConnectionTest.java @@ -111,16 +111,14 @@ public class DefaultConnectionTest { */ @Test public void testGetNonceFromHeader() { - byte[] nonce = "foo-nonce-foo".getBytes(); - when(mockUrlConnection.getHeaderField("Replay-Nonce")) - .thenReturn(Base64Url.encode(nonce)); + .thenReturn(Base64Url.encode(TestUtils.DUMMY_NONCE)); try (DefaultConnection conn = new DefaultConnection(mockHttpConnection)) { conn.conn = mockUrlConnection; conn.updateSession(session); } - assertThat(session.getNonce(), is(nonce)); + assertThat(session.getNonce(), is(TestUtils.DUMMY_NONCE)); verify(mockUrlConnection).getHeaderField("Replay-Nonce"); verifyNoMoreInteractions(mockUrlConnection); @@ -154,8 +152,6 @@ public class DefaultConnectionTest { */ @Test public void testResetNonce() throws AcmeException, IOException { - byte[] nonce = "foo-nonce-foo".getBytes(); - when(mockHttpConnection.openConnection(new URL("https://example.com/acme/new-nonce"))) .thenReturn(mockUrlConnection); when(mockUrlConnection.getResponseCode()) @@ -173,13 +169,13 @@ public class DefaultConnectionTest { assertThat(session.getNonce(), is(nullValue())); when(mockUrlConnection.getHeaderField("Replay-Nonce")) - .thenReturn(Base64Url.encode(nonce)); + .thenReturn(Base64Url.encode(TestUtils.DUMMY_NONCE)); try (DefaultConnection conn = new DefaultConnection(mockHttpConnection)) { conn.resetNonce(session); } - assertThat(session.getNonce(), is(nonce)); + assertThat(session.getNonce(), is(TestUtils.DUMMY_NONCE)); verify(mockUrlConnection, atLeastOnce()).setRequestMethod("HEAD"); verify(mockUrlConnection, atLeastOnce()).setRequestProperty("Accept-Language", "ja-JP"); @@ -404,15 +400,17 @@ public class DefaultConnectionTest { @Test public void testAccept() throws Exception { when(mockUrlConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK); + when(mockUrlConnection.getOutputStream()).thenReturn(new ByteArrayOutputStream()); + + session.setKeyIdentifier(keyIdentifier); + session.setNonce(TestUtils.DUMMY_NONCE); try (DefaultConnection conn = new DefaultConnection(mockHttpConnection)) { - conn.conn = mockUrlConnection; - int rc = conn.accept(HttpURLConnection.HTTP_OK); + int rc = conn.sendSignedRequest(requestUrl, new JSONBuilder(), session); assertThat(rc, is(HttpURLConnection.HTTP_OK)); } verify(mockUrlConnection).getResponseCode(); - verifyNoMoreInteractions(mockUrlConnection); } /** @@ -424,12 +422,15 @@ public class DefaultConnectionTest { when(mockUrlConnection.getHeaderField("Content-Type")).thenReturn("application/problem+json"); when(mockUrlConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_FORBIDDEN); + when(mockUrlConnection.getOutputStream()).thenReturn(new ByteArrayOutputStream()); when(mockUrlConnection.getErrorStream()).thenReturn(new ByteArrayInputStream(jsonData.getBytes("utf-8"))); when(mockUrlConnection.getURL()).thenReturn(url("https://example.com/acme/1")); + session.setKeyIdentifier(keyIdentifier); + session.setNonce(TestUtils.DUMMY_NONCE); + try (DefaultConnection conn = new DefaultConnection(mockHttpConnection)) { - conn.conn = mockUrlConnection; - conn.accept(HttpURLConnection.HTTP_OK); + conn.sendSignedRequest(requestUrl, new JSONBuilder(), session); fail("Expected to fail"); } catch (AcmeUnauthorizedException ex) { assertThat(ex.getType(), is(URI.create("urn:ietf:params:acme:error:unauthorized"))); @@ -442,7 +443,6 @@ public class DefaultConnectionTest { verify(mockUrlConnection, atLeastOnce()).getResponseCode(); verify(mockUrlConnection).getErrorStream(); verify(mockUrlConnection).getURL(); - verifyNoMoreInteractions(mockUrlConnection); } /** @@ -458,12 +458,15 @@ public class DefaultConnectionTest { when(mockUrlConnection.getHeaderField("Content-Type")).thenReturn("application/problem+json"); when(mockUrlConnection.getHeaderFields()).thenReturn(linkHeader); when(mockUrlConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_FORBIDDEN); + when(mockUrlConnection.getOutputStream()).thenReturn(new ByteArrayOutputStream()); when(mockUrlConnection.getErrorStream()).thenReturn(new ByteArrayInputStream(jsonData.getBytes("utf-8"))); when(mockUrlConnection.getURL()).thenReturn(url("https://example.com/acme/1")); + session.setKeyIdentifier(keyIdentifier); + session.setNonce(TestUtils.DUMMY_NONCE); + try (DefaultConnection conn = new DefaultConnection(mockHttpConnection)) { - conn.conn = mockUrlConnection; - conn.accept(HttpURLConnection.HTTP_OK); + conn.sendSignedRequest(requestUrl, new JSONBuilder(), session); fail("Expected to fail"); } catch (AcmeUserActionRequiredException ex) { assertThat(ex.getType(), is(URI.create("urn:ietf:params:acme:error:userActionRequired"))); @@ -478,7 +481,6 @@ public class DefaultConnectionTest { verify(mockUrlConnection, atLeastOnce()).getResponseCode(); verify(mockUrlConnection).getErrorStream(); verify(mockUrlConnection, atLeastOnce()).getURL(); - verifyNoMoreInteractions(mockUrlConnection); } /** @@ -498,12 +500,15 @@ public class DefaultConnectionTest { when(mockUrlConnection.getHeaderFieldDate("Retry-After", 0L)).thenReturn(retryAfter.toEpochMilli()); when(mockUrlConnection.getHeaderFields()).thenReturn(linkHeader); when(mockUrlConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_FORBIDDEN); + when(mockUrlConnection.getOutputStream()).thenReturn(new ByteArrayOutputStream()); when(mockUrlConnection.getErrorStream()).thenReturn(new ByteArrayInputStream(jsonData.getBytes("utf-8"))); when(mockUrlConnection.getURL()).thenReturn(url("https://example.com/acme/1")); + session.setKeyIdentifier(keyIdentifier); + session.setNonce(TestUtils.DUMMY_NONCE); + try (DefaultConnection conn = new DefaultConnection(mockHttpConnection)) { - conn.conn = mockUrlConnection; - conn.accept(HttpURLConnection.HTTP_OK); + conn.sendSignedRequest(requestUrl, new JSONBuilder(), session); fail("Expected to fail"); } catch (AcmeRateLimitedException ex) { assertThat(ex.getType(), is(URI.create("urn:ietf:params:acme:error:rateLimited"))); @@ -523,7 +528,6 @@ public class DefaultConnectionTest { verify(mockUrlConnection, atLeastOnce()).getResponseCode(); verify(mockUrlConnection).getErrorStream(); verify(mockUrlConnection, atLeastOnce()).getURL(); - verifyNoMoreInteractions(mockUrlConnection); } /** @@ -537,6 +541,11 @@ public class DefaultConnectionTest { .thenReturn(HttpURLConnection.HTTP_INTERNAL_ERROR); when(mockUrlConnection.getURL()) .thenReturn(url("https://example.com/acme/1")); + when(mockUrlConnection.getOutputStream()) + .thenReturn(new ByteArrayOutputStream()); + + session.setKeyIdentifier(keyIdentifier); + session.setNonce(TestUtils.DUMMY_NONCE); try (DefaultConnection conn = new DefaultConnection(mockHttpConnection) { @Override @@ -547,8 +556,7 @@ public class DefaultConnectionTest { return result.toJSON(); }; }) { - conn.conn = mockUrlConnection; - conn.accept(HttpURLConnection.HTTP_OK); + conn.sendSignedRequest(requestUrl, new JSONBuilder(), session); fail("Expected to fail"); } catch (AcmeServerException ex) { assertThat(ex.getType(), is(URI.create("urn:zombie:error:apocalypse"))); @@ -560,7 +568,6 @@ public class DefaultConnectionTest { verify(mockUrlConnection).getHeaderField("Content-Type"); verify(mockUrlConnection, atLeastOnce()).getResponseCode(); verify(mockUrlConnection).getURL(); - verifyNoMoreInteractions(mockUrlConnection); } /** @@ -574,6 +581,11 @@ public class DefaultConnectionTest { .thenReturn(HttpURLConnection.HTTP_INTERNAL_ERROR); when(mockUrlConnection.getURL()) .thenReturn(url("https://example.com/acme/1")); + when(mockUrlConnection.getOutputStream()) + .thenReturn(new ByteArrayOutputStream()); + + session.setKeyIdentifier(keyIdentifier); + session.setNonce(TestUtils.DUMMY_NONCE); try (DefaultConnection conn = new DefaultConnection(mockHttpConnection) { @Override @@ -581,8 +593,7 @@ public class DefaultConnectionTest { return JSON.empty(); }; }) { - conn.conn = mockUrlConnection; - conn.accept(HttpURLConnection.HTTP_OK); + conn.sendSignedRequest(requestUrl, new JSONBuilder(), session); fail("Expected to fail"); } catch (AcmeNetworkException ex) { fail("Did not expect an AcmeNetworkException"); @@ -593,7 +604,6 @@ public class DefaultConnectionTest { verify(mockUrlConnection).getHeaderField("Content-Type"); verify(mockUrlConnection, atLeastOnce()).getResponseCode(); verify(mockUrlConnection).getURL(); - verifyNoMoreInteractions(mockUrlConnection); } /** @@ -607,10 +617,14 @@ public class DefaultConnectionTest { .thenReturn(HttpURLConnection.HTTP_INTERNAL_ERROR); when(mockUrlConnection.getResponseMessage()) .thenReturn("Infernal Server Error"); + when(mockUrlConnection.getOutputStream()) + .thenReturn(new ByteArrayOutputStream()); + + session.setKeyIdentifier(keyIdentifier); + session.setNonce(TestUtils.DUMMY_NONCE); try (DefaultConnection conn = new DefaultConnection(mockHttpConnection)) { - conn.conn = mockUrlConnection; - conn.accept(HttpURLConnection.HTTP_OK); + conn.sendSignedRequest(requestUrl, new JSONBuilder(), session); fail("Expected to fail"); } catch (AcmeException ex) { assertThat(ex.getMessage(), is("HTTP 500: Infernal Server Error")); @@ -619,7 +633,6 @@ public class DefaultConnectionTest { verify(mockUrlConnection).getHeaderField("Content-Type"); verify(mockUrlConnection, atLeastOnce()).getResponseCode(); verify(mockUrlConnection, atLeastOnce()).getResponseMessage(); - verifyNoMoreInteractions(mockUrlConnection); } /** @@ -627,6 +640,8 @@ public class DefaultConnectionTest { */ @Test public void testSendRequest() throws Exception { + when(mockUrlConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK); + try (DefaultConnection conn = new DefaultConnection(mockHttpConnection)) { conn.sendRequest(requestUrl, session); } @@ -636,6 +651,7 @@ public class DefaultConnectionTest { verify(mockUrlConnection).setRequestProperty("Accept-Language", "ja-JP"); verify(mockUrlConnection).setDoOutput(false); verify(mockUrlConnection).connect(); + verify(mockUrlConnection).getResponseCode(); verify(mockUrlConnection, atLeast(0)).getHeaderFields(); verifyNoMoreInteractions(mockUrlConnection); } @@ -649,6 +665,7 @@ public class DefaultConnectionTest { final byte[] nonce2 = "foo-nonce-2-foo".getBytes(); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + when(mockUrlConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK); when(mockUrlConnection.getOutputStream()).thenReturn(outputStream); try (DefaultConnection conn = new DefaultConnection(mockHttpConnection) { @@ -686,6 +703,7 @@ public class DefaultConnectionTest { verify(mockUrlConnection).connect(); verify(mockUrlConnection).setDoOutput(true); verify(mockUrlConnection).setFixedLengthStreamingMode(outputStream.toByteArray().length); + verify(mockUrlConnection).getResponseCode(); verify(mockUrlConnection).getOutputStream(); verify(mockUrlConnection, atLeast(0)).getHeaderFields(); verifyNoMoreInteractions(mockUrlConnection); @@ -722,6 +740,7 @@ public class DefaultConnectionTest { final byte[] nonce2 = "foo-nonce-2-foo".getBytes(); final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + when(mockUrlConnection.getResponseCode()).thenReturn(HttpURLConnection.HTTP_OK); when(mockUrlConnection.getOutputStream()).thenReturn(outputStream); try (DefaultConnection conn = new DefaultConnection(mockHttpConnection) { @@ -758,6 +777,7 @@ public class DefaultConnectionTest { verify(mockUrlConnection).connect(); verify(mockUrlConnection).setDoOutput(true); verify(mockUrlConnection).setFixedLengthStreamingMode(outputStream.toByteArray().length); + verify(mockUrlConnection).getResponseCode(); verify(mockUrlConnection).getOutputStream(); verify(mockUrlConnection, atLeast(0)).getHeaderFields(); verifyNoMoreInteractions(mockUrlConnection); @@ -802,7 +822,7 @@ public class DefaultConnectionTest { /** * Test signed POST requests if there is no nonce. */ - @Test(expected = AcmeProtocolException.class) + @Test(expected = AcmeException.class) public void testSendSignedRequestNoNonce() throws Exception { when(mockHttpConnection.openConnection(new URL("https://example.com/acme/new-nonce"))) .thenReturn(mockUrlConnection); diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/connector/DummyConnection.java b/acme4j-client/src/test/java/org/shredzone/acme4j/connector/DummyConnection.java index 4bef9178..d9ec1fa0 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/connector/DummyConnection.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/connector/DummyConnection.java @@ -40,18 +40,14 @@ public class DummyConnection implements Connection { } @Override - public void sendSignedRequest(URL url, JSONBuilder claims, Session session) { - throw new UnsupportedOperationException(); - } - - @Override - public void sendSignedRequest(URL url, JSONBuilder claims, Session session, boolean enforceJwk) + public int sendSignedRequest(URL url, JSONBuilder claims, Session session, int... httpStatus) throws AcmeException { throw new UnsupportedOperationException(); } @Override - public int accept(int... httpStatus) throws AcmeException { + public int sendSignedRequest(URL url, JSONBuilder claims, Session session, boolean enforceJwk, int... httpStatus) + throws AcmeException { throw new UnsupportedOperationException(); } diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/connector/ResourceIteratorTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/connector/ResourceIteratorTest.java index 259034c2..2dbe45b8 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/connector/ResourceIteratorTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/connector/ResourceIteratorTest.java @@ -15,10 +15,9 @@ package org.shredzone.acme4j.connector; import static org.hamcrest.Matchers.*; import static org.junit.Assert.assertThat; -import static org.shredzone.acme4j.toolbox.TestUtils.*; +import static org.shredzone.acme4j.toolbox.TestUtils.url; import java.io.IOException; -import java.net.HttpURLConnection; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; @@ -32,7 +31,6 @@ import org.junit.Before; import org.junit.Test; import org.shredzone.acme4j.Authorization; import org.shredzone.acme4j.Session; -import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.provider.TestableConnectionProvider; import org.shredzone.acme4j.toolbox.JSON; import org.shredzone.acme4j.toolbox.JSONBuilder; @@ -140,12 +138,6 @@ public class ResourceIteratorTest { assertThat(ix, is(greaterThanOrEqualTo(0))); } - @Override - public int accept(int... httpStatus) throws AcmeException { - assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_OK)); - return HttpURLConnection.HTTP_OK; - } - @Override public JSON readJsonResponse() { int start = ix * RESOURCES_PER_PAGE; diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/provider/AbstractAcmeProviderTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/provider/AbstractAcmeProviderTest.java index 6ed1dfcd..4f9ef8c4 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/provider/AbstractAcmeProviderTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/provider/AbstractAcmeProviderTest.java @@ -20,7 +20,6 @@ import static org.mockito.Mockito.*; import static org.shredzone.acme4j.toolbox.TestUtils.getJSON; import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs; -import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; import java.util.concurrent.atomic.AtomicBoolean; @@ -84,7 +83,6 @@ public class AbstractAcmeProviderTest { final Connection connection = mock(Connection.class); final Session session = mock(Session.class); - when(connection.accept(any(Integer.class))).thenReturn(HttpURLConnection.HTTP_OK); when(connection.readJsonResponse()).thenReturn(getJSON("directory")); AbstractAcmeProvider provider = new AbstractAcmeProvider() { @@ -110,7 +108,6 @@ public class AbstractAcmeProviderTest { assertThat(map.toString(), sameJSONAs(TestUtils.getJSON("directory").toString())); verify(connection).sendRequest(testResolvedUrl, session); - verify(connection).accept(any(Integer.class)); verify(connection).updateSession(any(Session.class)); verify(connection).readJsonResponse(); verify(connection).close(); diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/toolbox/TestUtils.java b/acme4j-client/src/test/java/org/shredzone/acme4j/toolbox/TestUtils.java index 128e6dd8..4fa20890 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/toolbox/TestUtils.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/toolbox/TestUtils.java @@ -75,6 +75,8 @@ public final class TestUtils { public static final String ACME_SERVER_URI = "https://example.com/acme"; + public static final byte[] DUMMY_NONCE = "foo-nonce-foo".getBytes(); + private TestUtils() { // utility class without constructor }