From 158c3c46d1b51a23befeda65c396ebbe7f9eb27d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20K=C3=B6rber?= Date: Thu, 17 Mar 2016 00:58:32 +0100 Subject: [PATCH] Review exceptions, closes issue #10 --- .../java/org/shredzone/acme4j/AcmeClient.java | 1 - .../shredzone/acme4j/AcmeClientFactory.java | 12 +- .../acme4j/challenge/Dns01Challenge.java | 4 +- .../acme4j/challenge/GenericChallenge.java | 9 +- .../ProofOfPossession01Challenge.java | 5 +- .../acme4j/challenge/TlsSni01Challenge.java | 4 +- .../acme4j/connector/Connection.java | 19 +-- .../exception/AcmeNetworkException.java | 37 +++++ .../exception/AcmeProtocolException.java | 47 +++++++ .../acme4j/impl/AbstractAcmeClient.java | 35 ++++- .../acme4j/impl/DefaultConnection.java | 132 +++++++++--------- .../acme4j/impl/GenericAcmeClient.java | 7 +- .../LetsEncryptAcmeClientProvider.java | 3 +- .../shredzone/acme4j/util/ClaimBuilder.java | 3 +- .../acme4j/util/ValidationBuilder.java | 3 +- .../acme4j/AcmeClientFactoryTest.java | 15 +- .../challenge/GenericChallengeTest.java | 3 +- .../acme4j/impl/AbstractAcmeClientTest.java | 63 ++++----- .../acme4j/impl/DefaultConnectionTest.java | 17 ++- .../acme4j/impl/DummyConnection.java | 16 +-- .../acme4j/impl/GenericAcmeClientTest.java | 3 +- 21 files changed, 277 insertions(+), 161 deletions(-) create mode 100644 acme4j-client/src/main/java/org/shredzone/acme4j/exception/AcmeNetworkException.java create mode 100644 acme4j-client/src/main/java/org/shredzone/acme4j/exception/AcmeProtocolException.java diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/AcmeClient.java b/acme4j-client/src/main/java/org/shredzone/acme4j/AcmeClient.java index b6c56fa9..80942414 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/AcmeClient.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/AcmeClient.java @@ -68,7 +68,6 @@ public interface AcmeClient { * @param registration * {@link Registration}, with the new key pair and the account location URI * set - * @throws AcmeException */ void recoverRegistration(Registration registration) throws AcmeException; diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/AcmeClientFactory.java b/acme4j-client/src/main/java/org/shredzone/acme4j/AcmeClientFactory.java index 605895f6..6b777bf3 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/AcmeClientFactory.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/AcmeClientFactory.java @@ -19,7 +19,6 @@ import java.util.ArrayList; import java.util.List; import java.util.ServiceLoader; -import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.provider.AcmeClientProvider; /** @@ -44,13 +43,12 @@ public final class AcmeClientFactory { * URI of the ACME server. This can either be a http/https URI to the * server's directory service, or a special acme URI for specific * implementations. - * @return {@link AcmeClient} for communication with the server */ - public static AcmeClient connect(String serverUri) throws AcmeException { + public static AcmeClient connect(String serverUri) { try { return connect(new URI(serverUri)); } catch (URISyntaxException ex) { - throw new IllegalArgumentException(ex); + throw new IllegalArgumentException("Invalid server URI", ex); } } @@ -63,7 +61,7 @@ public final class AcmeClientFactory { * implementations. * @return {@link AcmeClient} for communication with the server */ - public static AcmeClient connect(URI serverUri) throws AcmeException { + public static AcmeClient connect(URI serverUri) { if (serverUri == null) { throw new NullPointerException("serverUri must not be null"); } @@ -76,9 +74,9 @@ public final class AcmeClientFactory { } if (candidates.isEmpty()) { - throw new AcmeException("No ACME provider found for " + serverUri); + throw new IllegalArgumentException("No ACME provider found for " + serverUri); } else if (candidates.size() > 1) { - throw new AcmeException("There are " + candidates.size() + " " + throw new IllegalStateException("There are " + candidates.size() + " " + AcmeClientProvider.class.getSimpleName() + " accepting " + serverUri + ". Please check your classpath."); } else { diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/Dns01Challenge.java b/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/Dns01Challenge.java index ced87abb..670c25df 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/Dns01Challenge.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/Dns01Challenge.java @@ -18,6 +18,7 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import org.jose4j.base64url.Base64Url; +import org.shredzone.acme4j.exception.AcmeProtocolException; /** * Implements the {@value TYPE} challenge. @@ -45,8 +46,7 @@ public class Dns01Challenge extends GenericTokenChallenge { byte[] digest = md.digest(); return Base64Url.encode(digest); } catch (NoSuchAlgorithmException | UnsupportedEncodingException ex) { - // both should be standard in JDK... - throw new RuntimeException(ex); + throw new AcmeProtocolException("Failed to compute digest", ex); } } diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/GenericChallenge.java b/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/GenericChallenge.java index dd8b4871..70872ac0 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/GenericChallenge.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/GenericChallenge.java @@ -31,6 +31,7 @@ import org.jose4j.jwk.JsonWebKey; import org.jose4j.jwk.JsonWebKey.OutputControlLevel; import org.jose4j.lang.JoseException; import org.shredzone.acme4j.Status; +import org.shredzone.acme4j.exception.AcmeProtocolException; import org.shredzone.acme4j.util.ClaimBuilder; import org.shredzone.acme4j.util.TimestampParser; @@ -75,7 +76,7 @@ public class GenericChallenge implements Challenge { try { return new URI(uri); } catch (URISyntaxException ex) { - throw new IllegalStateException("Invalid URI", ex); + throw new AcmeProtocolException("Invalid URI", ex); } } @@ -96,7 +97,7 @@ public class GenericChallenge implements Challenge { throw new IllegalArgumentException("map does not contain a type"); } if (!acceptable(type)) { - throw new IllegalArgumentException("wrong type: " + type); + throw new AcmeProtocolException("wrong type: " + type); } data.clear(); @@ -155,7 +156,7 @@ public class GenericChallenge implements Challenge { md.update(cb.toString().getBytes("UTF-8")); return md.digest(); } catch (JoseException | NoSuchAlgorithmException | UnsupportedEncodingException ex) { - throw new IllegalArgumentException("Cannot compute key thumbprint", ex); + throw new AcmeProtocolException("Cannot compute key thumbprint", ex); } } @@ -175,7 +176,7 @@ public class GenericChallenge implements Challenge { data = new HashMap<>(JsonUtil.parseJson(in.readUTF())); in.defaultReadObject(); } catch (JoseException ex) { - throw new IOException("Cannot deserialize", ex); + throw new AcmeProtocolException("Cannot deserialize", ex); } } diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/ProofOfPossession01Challenge.java b/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/ProofOfPossession01Challenge.java index 45b933fc..c24b6c1e 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/ProofOfPossession01Challenge.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/ProofOfPossession01Challenge.java @@ -29,6 +29,7 @@ import org.jose4j.base64url.Base64Url; import org.jose4j.json.JsonUtil; import org.jose4j.lang.JoseException; import org.shredzone.acme4j.Registration; +import org.shredzone.acme4j.exception.AcmeProtocolException; import org.shredzone.acme4j.util.ClaimBuilder; import org.shredzone.acme4j.util.ValidationBuilder; @@ -112,7 +113,7 @@ public class ProofOfPossession01Challenge extends GenericChallenge { } } } catch (CertificateException | IOException ex) { - throw new IllegalArgumentException("Invalid certs", ex); + throw new AcmeProtocolException("Invalid certificates", ex); } } } @@ -129,7 +130,7 @@ public class ProofOfPossession01Challenge extends GenericChallenge { cb.put(KEY_AUTHORIZATION, JsonUtil.parseJson(validation)); } catch (JoseException ex) { // should not happen, as the JSON is prevalidated in the setter - throw new IllegalStateException("validation: invalid JSON", ex); + throw new AcmeProtocolException("validation: invalid JSON", ex); } } diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/TlsSni01Challenge.java b/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/TlsSni01Challenge.java index 5e2bc760..afa41988 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/TlsSni01Challenge.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/TlsSni01Challenge.java @@ -18,6 +18,7 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import org.shredzone.acme4j.Registration; +import org.shredzone.acme4j.exception.AcmeProtocolException; /** * Implements the {@value TYPE} challenge. @@ -75,8 +76,7 @@ public class TlsSni01Challenge extends GenericTokenChallenge { } return new String(result); } catch (NoSuchAlgorithmException | UnsupportedEncodingException ex) { - // Algorithm and Encoding are standard on Java - throw new RuntimeException(ex); + throw new AcmeProtocolException("Could not compute hash", ex); } } 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 7a459d0f..d3e8e536 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.io.IOException; import java.net.URI; import java.security.cert.X509Certificate; import java.util.Map; @@ -36,7 +37,7 @@ public interface Connection extends AutoCloseable { * {@link URI} to send the request to. * @return HTTP response code */ - int sendRequest(URI uri) throws AcmeException; + int sendRequest(URI uri) throws IOException; /** * Sends a signed POST request. @@ -52,28 +53,28 @@ public interface Connection extends AutoCloseable { * @return HTTP response code */ int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Registration registration) - throws AcmeException; + throws IOException; /** * Reads a server response as JSON data. * * @return Map containing the parsed JSON data */ - Map readJsonResponse() throws AcmeException; + Map readJsonResponse() throws IOException; /** * Reads a certificate. * * @return {@link X509Certificate} that was read. */ - X509Certificate readCertificate() throws AcmeException; + X509Certificate readCertificate() throws IOException; /** * Reads a resource directory. * * @return Map of {@link Resource} and the respective {@link URI} to invoke */ - Map readDirectory() throws AcmeException; + Map readDirectory() throws IOException; /** * Updates a {@link Session} by evaluating the HTTP response header. @@ -81,14 +82,14 @@ public interface Connection extends AutoCloseable { * @param session * {@link Session} instance to be updated */ - void updateSession(Session session) throws AcmeException; + void updateSession(Session session); /** * Gets a location from the {@code Location} header. * * @return Location {@link URI}, or {@code null} if no Location header was set */ - URI getLocation() throws AcmeException; + URI getLocation(); /** * Gets a link relation from the header. @@ -97,14 +98,14 @@ public interface Connection extends AutoCloseable { * Link relation * @return Link, or {@code null} if there was no such link relation */ - URI getLink(String relation) throws AcmeException; + URI getLink(String relation); /** * Handles a problem by throwing an exception. If a JSON problem was returned, an * {@link AcmeServerException} will be thrown. Otherwise a generic * {@link AcmeException} is thrown. */ - void throwAcmeException() throws AcmeException; + void throwAcmeException() throws AcmeException, IOException; /** * Closes the {@link Connection}, releasing all resources. diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/exception/AcmeNetworkException.java b/acme4j-client/src/main/java/org/shredzone/acme4j/exception/AcmeNetworkException.java new file mode 100644 index 00000000..6a1ff8b3 --- /dev/null +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/exception/AcmeNetworkException.java @@ -0,0 +1,37 @@ +/* + * acme4j - Java ACME client + * + * Copyright (C) 2016 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.exception; + +import java.io.IOException; + +/** + * This exception is thrown when a network error occured while communicating with the + * server. + * + * @author Richard "Shred" Körber + */ +public class AcmeNetworkException extends AcmeException { + private static final long serialVersionUID = 2054398693543329179L; + + /** + * Create a new {@link AcmeNetworkException}. + * + * @param cause + * {@link IOException} that caused the network error + */ + public AcmeNetworkException(IOException cause) { + super("Network error", cause); + } + +} diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/exception/AcmeProtocolException.java b/acme4j-client/src/main/java/org/shredzone/acme4j/exception/AcmeProtocolException.java new file mode 100644 index 00000000..0921f88e --- /dev/null +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/exception/AcmeProtocolException.java @@ -0,0 +1,47 @@ +/* + * acme4j - Java ACME client + * + * Copyright (C) 2016 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.exception; + +/** + * This runtime exception is thrown on ACME protocol errors that should not occur. For + * example, this exception is thrown when a server response could not be parsed. + * + * @author Richard "Shred" Körber + */ +public class AcmeProtocolException extends RuntimeException { + private static final long serialVersionUID = 2031203835755725193L; + + /** + * Creates a new {@link AcmeProtocolException}. + * + * @param msg + * Reason of the exception + */ + public AcmeProtocolException(String msg) { + super(msg); + } + + /** + * Creates a new {@link AcmeProtocolException}. + * + * @param msg + * Reason of the exception + * @param cause + * Cause + */ + public AcmeProtocolException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/impl/AbstractAcmeClient.java b/acme4j-client/src/main/java/org/shredzone/acme4j/impl/AbstractAcmeClient.java index 8741b669..1c34c016 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/impl/AbstractAcmeClient.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/impl/AbstractAcmeClient.java @@ -13,6 +13,7 @@ */ package org.shredzone.acme4j.impl; +import java.io.IOException; import java.net.HttpURLConnection; import java.net.URI; import java.security.KeyPair; @@ -37,6 +38,8 @@ import org.shredzone.acme4j.connector.Resource; import org.shredzone.acme4j.connector.Session; import org.shredzone.acme4j.exception.AcmeConflictException; import org.shredzone.acme4j.exception.AcmeException; +import org.shredzone.acme4j.exception.AcmeNetworkException; +import org.shredzone.acme4j.exception.AcmeProtocolException; import org.shredzone.acme4j.util.ClaimBuilder; import org.shredzone.acme4j.util.SignatureUtils; import org.shredzone.acme4j.util.TimestampParser; @@ -125,6 +128,8 @@ public abstract class AbstractAcmeClient implements AcmeClient { if (rc == HttpURLConnection.HTTP_CONFLICT) { throw new AcmeConflictException("Account is already registered", location); } + } catch (IOException ex) { + throw new AcmeNetworkException(ex); } } @@ -162,6 +167,8 @@ public abstract class AbstractAcmeClient implements AcmeClient { if (tos != null) { registration.setAgreement(tos); } + } catch (IOException ex) { + throw new AcmeNetworkException(ex); } } @@ -198,7 +205,7 @@ public abstract class AbstractAcmeClient implements AcmeClient { newKey = jws.getCompactSerialization(); } catch (JoseException ex) { - throw new IllegalArgumentException("Bad newKeyPair", ex); + throw new AcmeProtocolException("Bad newKeyPair", ex); } LOG.debug("changeRegistrationKey"); @@ -211,6 +218,8 @@ public abstract class AbstractAcmeClient implements AcmeClient { if (rc != HttpURLConnection.HTTP_ACCEPTED) { conn.throwAcmeException(); } + } catch (IOException ex) { + throw new AcmeNetworkException(ex); } } @@ -244,6 +253,8 @@ public abstract class AbstractAcmeClient implements AcmeClient { if (tos != null) { registration.setAgreement(tos); } + } catch (IOException ex) { + throw new AcmeNetworkException(ex); } } @@ -276,6 +287,8 @@ public abstract class AbstractAcmeClient implements AcmeClient { Map result = conn.readJsonResponse(); unmarshalAuthorization(result, auth); + } catch (IOException ex) { + throw new AcmeNetworkException(ex); } } @@ -299,6 +312,8 @@ public abstract class AbstractAcmeClient implements AcmeClient { Map result = conn.readJsonResponse(); unmarshalAuthorization(result, auth); + } catch (IOException ex) { + throw new AcmeNetworkException(ex); } } @@ -326,6 +341,8 @@ public abstract class AbstractAcmeClient implements AcmeClient { } challenge.unmarshall(conn.readJsonResponse()); + } catch (IOException ex) { + throw new AcmeNetworkException(ex); } } @@ -346,6 +363,8 @@ public abstract class AbstractAcmeClient implements AcmeClient { } challenge.unmarshall(conn.readJsonResponse()); + } catch (IOException ex) { + throw new AcmeNetworkException(ex); } } @@ -365,10 +384,12 @@ public abstract class AbstractAcmeClient implements AcmeClient { Map json = conn.readJsonResponse(); if (!(json.containsKey("type"))) { - throw new AcmeException("Provided URI is not a challenge URI"); + throw new IllegalArgumentException("Provided URI is not a challenge URI"); } return (T) createChallenge(json); + } catch (IOException ex) { + throw new AcmeNetworkException(ex); } } @@ -398,6 +419,8 @@ public abstract class AbstractAcmeClient implements AcmeClient { // X509Certificate cert = conn.readCertificate(); return conn.getLocation(); + } catch (IOException ex) { + throw new AcmeNetworkException(ex); } } @@ -415,6 +438,8 @@ public abstract class AbstractAcmeClient implements AcmeClient { } return conn.readCertificate(); + } catch (IOException ex) { + throw new AcmeNetworkException(ex); } } @@ -430,7 +455,7 @@ public abstract class AbstractAcmeClient implements AcmeClient { LOG.debug("revokeCertificate"); URI resUri = resourceUri(Resource.REVOKE_CERT); if (resUri == null) { - throw new AcmeException("CA does not support certificate revocation"); + throw new AcmeProtocolException("CA does not support certificate revocation"); } try (Connection conn = createConnection()) { @@ -443,7 +468,9 @@ public abstract class AbstractAcmeClient implements AcmeClient { conn.throwAcmeException(); } } catch (CertificateEncodingException ex) { - throw new IllegalArgumentException("Invalid certificate", ex); + throw new AcmeProtocolException("Invalid certificate", ex); + } catch (IOException ex) { + throw new AcmeNetworkException(ex); } } diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/impl/DefaultConnection.java b/acme4j-client/src/main/java/org/shredzone/acme4j/impl/DefaultConnection.java index b3198cb3..a1fdcc9d 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/impl/DefaultConnection.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/impl/DefaultConnection.java @@ -43,6 +43,7 @@ import org.shredzone.acme4j.connector.Resource; import org.shredzone.acme4j.connector.Session; import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.exception.AcmeRateLimitExceededException; +import org.shredzone.acme4j.exception.AcmeProtocolException; import org.shredzone.acme4j.exception.AcmeServerException; import org.shredzone.acme4j.exception.AcmeUnauthorizedException; import org.shredzone.acme4j.util.ClaimBuilder; @@ -72,35 +73,29 @@ public class DefaultConnection implements Connection { } @Override - public int sendRequest(URI uri) throws AcmeException { + public int sendRequest(URI uri) throws IOException { if (uri == null) { throw new NullPointerException("uri must not be null"); } - if (conn != null) { - throw new IllegalStateException("Connection was not closed. Race condition?"); - } + assertConnectionIsClosed(); - try { - LOG.debug("GET {}", uri); + LOG.debug("GET {}", uri); - conn = httpConnector.openConnection(uri); - conn.setRequestMethod("GET"); - conn.setRequestProperty("Accept-Charset", "utf-8"); - conn.setDoOutput(false); + conn = httpConnector.openConnection(uri); + conn.setRequestMethod("GET"); + conn.setRequestProperty("Accept-Charset", "utf-8"); + conn.setDoOutput(false); - conn.connect(); + conn.connect(); - logHeaders(); + logHeaders(); - return conn.getResponseCode(); - } catch (IOException ex) { - throw new AcmeException("Request failed: " + uri, ex); - } + return conn.getResponseCode(); } @Override public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Registration registration) - throws AcmeException { + throws IOException { if (uri == null) { throw new NullPointerException("uri must not be null"); } @@ -113,9 +108,7 @@ public class DefaultConnection implements Connection { if (registration == null) { throw new NullPointerException("registration must not be null"); } - if (conn != null) { - throw new IllegalStateException("Connection was not closed. Race condition?"); - } + assertConnectionIsClosed(); try { KeyPair keypair = registration.getKeyPair(); @@ -130,7 +123,7 @@ public class DefaultConnection implements Connection { } if (session.getNonce() == null) { - throw new AcmeException("No nonce available"); + throw new AcmeProtocolException("Server did not provide a nonce"); } LOG.debug("POST {} with claims: {}", uri, claims); @@ -164,21 +157,19 @@ public class DefaultConnection implements Connection { updateSession(session); return conn.getResponseCode(); - } catch (JoseException | IOException ex) { - throw new AcmeException("Request failed: " + uri, ex); + } catch (JoseException ex) { + throw new AcmeProtocolException("Failed to generate a JSON request", ex); } } @Override - public Map readJsonResponse() throws AcmeException { - if (conn == null) { - throw new IllegalStateException("Not connected"); - } + public Map readJsonResponse() throws IOException { + assertConnectionIsOpen(); String contentType = conn.getHeaderField("Content-Type"); if (!("application/json".equals(contentType) || "application/problem+json".equals(contentType))) { - throw new AcmeException("Unexpected content type: " + contentType); + throw new AcmeProtocolException("Unexpected content type: " + contentType); } StringBuilder sb = new StringBuilder(); @@ -195,41 +186,37 @@ public class DefaultConnection implements Connection { LOG.debug("Result JSON: {}", sb); } - } catch (JoseException | IOException ex) { - throw new AcmeException("Failed to parse response: " + sb, ex); + } catch (JoseException ex) { + throw new AcmeProtocolException("Failed to parse response: " + sb, ex); } return result; } @Override - public X509Certificate readCertificate() throws AcmeException { - if (conn == null) { - throw new IllegalStateException("Not connected"); - } + public X509Certificate readCertificate() throws IOException { + assertConnectionIsOpen(); String contentType = conn.getHeaderField("Content-Type"); if (!("application/pkix-cert".equals(contentType))) { - throw new AcmeException("Unexpected content type: " + contentType); + throw new AcmeProtocolException("Unexpected content type: " + contentType); } try (InputStream in = conn.getInputStream()) { CertificateFactory cf = CertificateFactory.getInstance("X.509"); return (X509Certificate) cf.generateCertificate(in); - } catch (CertificateException | IOException ex) { - throw new AcmeException("Failed to read certificate", ex); + } catch (CertificateException ex) { + throw new AcmeProtocolException("Failed to read certificate", ex); } } @Override - public Map readDirectory() throws AcmeException { - if (conn == null) { - throw new IllegalStateException("Not connected"); - } + public Map readDirectory() throws IOException { + assertConnectionIsOpen(); String contentType = conn.getHeaderField("Content-Type"); if (!("application/json".equals(contentType))) { - throw new AcmeException("Unexpected content type: " + contentType); + throw new AcmeProtocolException("Unexpected content type: " + contentType); } EnumMap resourceMap = new EnumMap<>(Resource.class); @@ -250,18 +237,16 @@ public class DefaultConnection implements Connection { } LOG.debug("Resource directory: {}", resourceMap); - } catch (JoseException | URISyntaxException | IOException ex) { - throw new AcmeException("Failed to read directory: " + sb, ex); + } catch (JoseException | URISyntaxException ex) { + throw new AcmeProtocolException("Failed to read directory: " + sb, ex); } return resourceMap; } @Override - public void updateSession(Session session) throws AcmeException { - if (conn == null) { - throw new IllegalStateException("Not connected"); - } + public void updateSession(Session session) { + assertConnectionIsOpen(); String nonceHeader = conn.getHeaderField("Replay-Nonce"); if (nonceHeader == null || nonceHeader.trim().isEmpty()) { @@ -269,7 +254,7 @@ public class DefaultConnection implements Connection { } if (!BASE64URL_PATTERN.matcher(nonceHeader).matches()) { - throw new AcmeException("Invalid replay nonce: " + nonceHeader); + throw new AcmeProtocolException("Invalid replay nonce: " + nonceHeader); } LOG.debug("Replay Nonce: {}", nonceHeader); @@ -278,10 +263,8 @@ public class DefaultConnection implements Connection { } @Override - public URI getLocation() throws AcmeException { - if (conn == null) { - throw new IllegalStateException("Not connected"); - } + public URI getLocation() { + assertConnectionIsOpen(); String location = conn.getHeaderField("Location"); if (location == null) { @@ -292,15 +275,13 @@ public class DefaultConnection implements Connection { LOG.debug("Location: {}", location); return new URI(location); } catch (URISyntaxException ex) { - throw new AcmeException("Bad Location header: " + location); + throw new AcmeProtocolException("Bad Location header: " + location); } } @Override - public URI getLink(String relation) throws AcmeException { - if (conn == null) { - throw new IllegalStateException("Not connected"); - } + public URI getLink(String relation) { + assertConnectionIsOpen(); List links = conn.getHeaderFields().get("Link"); if (links != null) { @@ -313,19 +294,18 @@ public class DefaultConnection implements Connection { LOG.debug("Link: {} -> {}", relation, location); return new URI(location); } catch (URISyntaxException ex) { - throw new AcmeException("Bad '" + relation + "' Link header: " + link); + throw new AcmeProtocolException("Bad '" + relation + "' Link header: " + link); } } } } + return null; } @Override - public void throwAcmeException() throws AcmeException { - if (conn == null) { - throw new IllegalStateException("Not connected"); - } + public void throwAcmeException() throws AcmeException, IOException { + assertConnectionIsOpen(); if ("application/problem+json".equals(conn.getHeaderField("Content-Type"))) { Map map = readJsonResponse(); @@ -351,12 +331,8 @@ public class DefaultConnection implements Connection { throw new AcmeServerException(type, detail); } } else { - try { - throw new AcmeException("HTTP " + conn.getResponseCode() + ": " - + conn.getResponseMessage()); - } catch (IOException ex) { - throw new AcmeException("Network error"); - } + throw new AcmeException("HTTP " + conn.getResponseCode() + ": " + + conn.getResponseMessage()); } } @@ -365,6 +341,24 @@ public class DefaultConnection implements Connection { conn = null; } + /** + * Asserts that the connection is currently open. Throws an exception if not. + */ + private void assertConnectionIsOpen() { + if (conn == null) { + throw new IllegalStateException("Not connected."); + } + } + + /** + * Asserts that the connection is currently closed. Throws an exception if not. + */ + private void assertConnectionIsClosed() { + if (conn != null) { + throw new IllegalStateException("Previous connection is not closed."); + } + } + /** * Log all HTTP headers in debug mode. */ diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/impl/GenericAcmeClient.java b/acme4j-client/src/main/java/org/shredzone/acme4j/impl/GenericAcmeClient.java index 9245608c..fb52b984 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/impl/GenericAcmeClient.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/impl/GenericAcmeClient.java @@ -13,6 +13,7 @@ */ package org.shredzone.acme4j.impl; +import java.io.IOException; import java.net.HttpURLConnection; import java.net.URI; import java.util.Date; @@ -26,6 +27,8 @@ import org.shredzone.acme4j.challenge.GenericTokenChallenge; import org.shredzone.acme4j.connector.Connection; import org.shredzone.acme4j.connector.Resource; import org.shredzone.acme4j.exception.AcmeException; +import org.shredzone.acme4j.exception.AcmeNetworkException; +import org.shredzone.acme4j.exception.AcmeProtocolException; import org.shredzone.acme4j.provider.AcmeClientProvider; /** @@ -92,7 +95,7 @@ public class GenericAcmeClient extends AbstractAcmeClient { if (directoryMap.isEmpty() || !directoryCacheExpiry.after(now)) { if (directoryUri == null) { - throw new IllegalStateException("directoryUri was null on construction time"); + throw new AcmeProtocolException("directoryUri was null on construction time"); } try (Connection conn = createConnection()) { @@ -110,6 +113,8 @@ public class GenericAcmeClient extends AbstractAcmeClient { directoryMap.clear(); directoryMap.putAll(newMap); directoryCacheExpiry = new Date(now.getTime() + 60 * 60 * 1000L); + } catch (IOException ex) { + throw new AcmeNetworkException(ex); } } return directoryMap.get(resource); diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/provider/letsencrypt/LetsEncryptAcmeClientProvider.java b/acme4j-client/src/main/java/org/shredzone/acme4j/provider/letsencrypt/LetsEncryptAcmeClientProvider.java index 2a24ce5d..8a9c74fe 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/provider/letsencrypt/LetsEncryptAcmeClientProvider.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/provider/letsencrypt/LetsEncryptAcmeClientProvider.java @@ -17,6 +17,7 @@ import java.net.URI; import java.net.URISyntaxException; import org.shredzone.acme4j.connector.HttpConnector; +import org.shredzone.acme4j.exception.AcmeProtocolException; import org.shredzone.acme4j.provider.AbstractAcmeClientProvider; import org.shredzone.acme4j.provider.AcmeClientProvider; @@ -58,7 +59,7 @@ public class LetsEncryptAcmeClientProvider extends AbstractAcmeClientProvider { try { return new URI(directoryUri); } catch (URISyntaxException ex) { - throw new IllegalArgumentException(directoryUri, ex); + throw new AcmeProtocolException(directoryUri, ex); } } diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/util/ClaimBuilder.java b/acme4j-client/src/main/java/org/shredzone/acme4j/util/ClaimBuilder.java index d20a7d47..65398dfe 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/util/ClaimBuilder.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/util/ClaimBuilder.java @@ -25,6 +25,7 @@ import org.jose4j.jwk.JsonWebKey; import org.jose4j.jwk.PublicJsonWebKey; import org.jose4j.lang.JoseException; import org.shredzone.acme4j.connector.Resource; +import org.shredzone.acme4j.exception.AcmeProtocolException; /** * Builder for claim structures. @@ -128,7 +129,7 @@ public class ClaimBuilder { object(key).putAll(jwkParams); return this; } catch (JoseException ex) { - throw new IllegalArgumentException("Invalid key", ex); + throw new AcmeProtocolException("Invalid key", ex); } } diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/util/ValidationBuilder.java b/acme4j-client/src/main/java/org/shredzone/acme4j/util/ValidationBuilder.java index 35d5735a..7d1dfb6c 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/util/ValidationBuilder.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/util/ValidationBuilder.java @@ -25,6 +25,7 @@ import org.jose4j.jws.JsonWebSignature; import org.jose4j.lang.JoseException; import org.shredzone.acme4j.Registration; import org.shredzone.acme4j.challenge.ProofOfPossession01Challenge; +import org.shredzone.acme4j.exception.AcmeProtocolException; /** * Generates a validation string for {@link ProofOfPossession01Challenge}. @@ -121,7 +122,7 @@ public class ValidationBuilder { auth.put("signature", jws.getEncodedSignature()); return auth.toString(); } catch (JoseException ex) { - throw new IllegalArgumentException("Failed to sign", ex); + throw new AcmeProtocolException("Failed to sign", ex); } } diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/AcmeClientFactoryTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/AcmeClientFactoryTest.java index 95e381fb..b50543ce 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/AcmeClientFactoryTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/AcmeClientFactoryTest.java @@ -24,7 +24,6 @@ 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; /** @@ -43,26 +42,26 @@ public class AcmeClientFactoryTest { * the correct {@link AcmeClientProvider}. */ @Test - public void testConnectURI() throws URISyntaxException, AcmeException { + public void testConnectURI() throws URISyntaxException { AcmeClient client = AcmeClientFactory.connect(new URI("acme://example.com")); assertThat(client, is(sameInstance(DUMMY_CLIENT))); } /** * There are no testing providers accepting {@code acme://example.org}. Test that - * connecting to this URI will result in an {@link AcmeException}. + * connecting to this URI will result in an {@link IllegalArgumentException}. */ - @Test(expected = AcmeException.class) - public void testNone() throws URISyntaxException, AcmeException { + @Test(expected = IllegalArgumentException.class) + public void testNone() throws URISyntaxException { AcmeClientFactory.connect(new URI("acme://example.org")); } /** * There are two testing providers accepting {@code acme://example.net}. Test that - * connecting to this URI will result in an {@link AcmeException}. + * connecting to this URI will result in an {@link IllegalStateException}. */ - @Test(expected = AcmeException.class) - public void testDuplicate() throws URISyntaxException, AcmeException { + @Test(expected = IllegalStateException.class) + public void testDuplicate() throws URISyntaxException { AcmeClientFactory.connect(new URI("acme://example.net")); } diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/GenericChallengeTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/GenericChallengeTest.java index 354411d9..1c4dcc87 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/GenericChallengeTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/GenericChallengeTest.java @@ -33,6 +33,7 @@ import org.jose4j.jwk.JsonWebKey.OutputControlLevel; import org.jose4j.lang.JoseException; import org.junit.Test; import org.shredzone.acme4j.Status; +import org.shredzone.acme4j.exception.AcmeProtocolException; import org.shredzone.acme4j.util.ClaimBuilder; import org.shredzone.acme4j.util.TestUtils; import org.shredzone.acme4j.util.TimestampParser; @@ -86,7 +87,7 @@ public class GenericChallengeTest { /** * Test that an exception is thrown on challenge type mismatch. */ - @Test(expected = IllegalArgumentException.class) + @Test(expected = AcmeProtocolException.class) public void testNotAcceptable() throws URISyntaxException { Http01Challenge challenge = new Http01Challenge(); challenge.unmarshall(TestUtils.getJsonAsMap("dnsChallenge")); diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/impl/AbstractAcmeClientTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/impl/AbstractAcmeClientTest.java index b1476921..8885ad60 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/impl/AbstractAcmeClientTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/impl/AbstractAcmeClientTest.java @@ -42,6 +42,7 @@ import org.shredzone.acme4j.connector.Connection; import org.shredzone.acme4j.connector.Resource; import org.shredzone.acme4j.connector.Session; import org.shredzone.acme4j.exception.AcmeException; +import org.shredzone.acme4j.exception.AcmeProtocolException; import org.shredzone.acme4j.util.ClaimBuilder; import org.shredzone.acme4j.util.TestUtils; import org.shredzone.acme4j.util.TimestampParser; @@ -80,7 +81,7 @@ public class AbstractAcmeClientTest { Connection connection = new DummyConnection() { @Override - public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Registration registration) throws AcmeException { + public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Registration registration) { assertThat(uri, is(resourceUri)); assertThat(claims.toString(), sameJSONAs(getJson("newRegistration"))); assertThat(session, is(notNullValue())); @@ -89,12 +90,12 @@ public class AbstractAcmeClientTest { } @Override - public URI getLocation() throws AcmeException { + public URI getLocation() { return locationUri; } @Override - public URI getLink(String relation) throws AcmeException { + public URI getLink(String relation) { switch(relation) { case "terms-of-service": return agreementUri; default: return null; @@ -123,7 +124,7 @@ public class AbstractAcmeClientTest { Connection connection = new DummyConnection() { @Override - public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Registration registration) throws AcmeException { + public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Registration registration) { assertThat(uri, is(locationUri)); assertThat(claims.toString(), sameJSONAs(getJson("modifyRegistration"))); assertThat(session, is(notNullValue())); @@ -132,12 +133,12 @@ public class AbstractAcmeClientTest { } @Override - public URI getLocation() throws AcmeException { + public URI getLocation() { return locationUri; } @Override - public URI getLink(String relation) throws AcmeException { + public URI getLink(String relation) { switch(relation) { case "terms-of-service": return agreementUri; default: return null; @@ -165,7 +166,7 @@ public class AbstractAcmeClientTest { Connection connection = new DummyConnection() { @Override - public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Registration registration) throws AcmeException { + public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Registration registration) { Map claimMap = claims.toMap(); assertThat(claimMap.get("resource"), is((Object) "reg")); assertThat(claimMap.get("newKey"), not(nullValue())); @@ -185,7 +186,7 @@ public class AbstractAcmeClientTest { jws.setKey(newKeyPair.getPublic()); assertThat(jws.getPayload(), sameJSONAs(expectedPayload.toString())); } catch (JoseException ex) { - throw new AcmeException("Bad newKey", ex); + throw new AcmeProtocolException("Bad newKey", ex); } assertThat(uri, is(locationUri)); @@ -195,7 +196,7 @@ public class AbstractAcmeClientTest { } @Override - public URI getLocation() throws AcmeException { + public URI getLocation() { return locationUri; } }; @@ -209,7 +210,7 @@ public class AbstractAcmeClientTest { * Test that the same account key is not accepted for change */ @Test(expected = IllegalArgumentException.class) - public void testChangeRegistrationSameKey() throws AcmeException, IOException { + public void testChangeRegistrationSameKey() throws AcmeException { Registration registration = new Registration(accountKeyPair); registration.setLocation(locationUri); @@ -230,21 +231,19 @@ public class AbstractAcmeClientTest { Connection connection = new DummyConnection() { @Override - public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Registration registration) throws AcmeException { - assertThat(uri, is(resourceUri)); - assertThat(claims.toString(), sameJSONAs(getJson("recoverRegistration"))); + public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Registration registration) { assertThat(session, is(notNullValue())); assertThat(registration.getKeyPair(), is(sameInstance(accountKeyPair))); return HttpURLConnection.HTTP_CREATED; } @Override - public URI getLocation() throws AcmeException { + public URI getLocation() { return anotherLocationUri; } @Override - public URI getLink(String relation) throws AcmeException { + public URI getLink(String relation) { switch(relation) { case "terms-of-service": return agreementUri; default: return null; @@ -271,7 +270,7 @@ public class AbstractAcmeClientTest { Connection connection = new DummyConnection() { @Override - public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Registration registration) throws AcmeException { + public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Registration registration) { assertThat(uri, is(resourceUri)); assertThat(claims.toString(), sameJSONAs(getJson("newAuthorizationRequest"))); assertThat(session, is(notNullValue())); @@ -280,12 +279,12 @@ public class AbstractAcmeClientTest { } @Override - public Map readJsonResponse() throws AcmeException { + public Map readJsonResponse() { return getJsonAsMap("newAuthorizationResponse"); } @Override - public URI getLocation() throws AcmeException { + public URI getLocation() { return locationUri; } }; @@ -324,13 +323,13 @@ public class AbstractAcmeClientTest { Connection connection = new DummyConnection() { @Override - public int sendRequest(URI uri) throws AcmeException { + public int sendRequest(URI uri) { assertThat(uri, is(locationUri)); return HttpURLConnection.HTTP_OK; } @Override - public Map readJsonResponse() throws AcmeException { + public Map readJsonResponse() { return getJsonAsMap("updateAuthorizationResponse"); } }; @@ -366,7 +365,7 @@ public class AbstractAcmeClientTest { public void testTriggerChallenge() throws AcmeException { Connection connection = new DummyConnection() { @Override - public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Registration registration) throws AcmeException { + public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Registration registration) { assertThat(uri, is(resourceUri)); assertThat(claims.toString(), sameJSONAs(getJson("triggerHttpChallengeRequest"))); assertThat(session, is(notNullValue())); @@ -375,7 +374,7 @@ public class AbstractAcmeClientTest { } @Override - public Map readJsonResponse() throws AcmeException { + public Map readJsonResponse() { return getJsonAsMap("triggerHttpChallengeResponse"); } }; @@ -399,13 +398,13 @@ public class AbstractAcmeClientTest { public void testUpdateChallenge() throws AcmeException { Connection connection = new DummyConnection() { @Override - public int sendRequest(URI uri) throws AcmeException { + public int sendRequest(URI uri) { assertThat(uri, is(locationUri)); return HttpURLConnection.HTTP_ACCEPTED; } @Override - public Map readJsonResponse() throws AcmeException { + public Map readJsonResponse() { return getJsonAsMap("updateHttpChallengeResponse"); } }; @@ -425,13 +424,13 @@ public class AbstractAcmeClientTest { public void testRestoreChallenge() throws AcmeException { Connection connection = new DummyConnection() { @Override - public int sendRequest(URI uri) throws AcmeException { + public int sendRequest(URI uri) { assertThat(uri, is(locationUri)); return HttpURLConnection.HTTP_ACCEPTED; } @Override - public Map readJsonResponse() throws AcmeException { + public Map readJsonResponse() { return getJsonAsMap("updateHttpChallengeResponse"); } }; @@ -452,7 +451,7 @@ public class AbstractAcmeClientTest { public void testRequestCertificate() throws AcmeException, IOException { Connection connection = new DummyConnection() { @Override - public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Registration registration) throws AcmeException { + public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Registration registration) { assertThat(uri, is(resourceUri)); assertThat(claims.toString(), sameJSONAs(getJson("requestCertificateRequest"))); assertThat(session, is(notNullValue())); @@ -461,7 +460,7 @@ public class AbstractAcmeClientTest { } @Override - public URI getLocation() throws AcmeException { + public URI getLocation() { return locationUri; } }; @@ -484,13 +483,13 @@ public class AbstractAcmeClientTest { Connection connection = new DummyConnection() { @Override - public int sendRequest(URI uri) throws AcmeException { + public int sendRequest(URI uri) { assertThat(uri, is(locationUri)); return HttpURLConnection.HTTP_OK; } @Override - public X509Certificate readCertificate() throws AcmeException { + public X509Certificate readCertificate() { return originalCert; } }; @@ -510,7 +509,7 @@ public class AbstractAcmeClientTest { Connection connection = new DummyConnection() { @Override - public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Registration registration) throws AcmeException { + public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Registration registration) { assertThat(uri, is(resourceUri)); assertThat(claims.toString(), sameJSONAs(getJson("revokeCertificateRequest"))); assertThat(session, is(notNullValue())); @@ -566,7 +565,7 @@ public class AbstractAcmeClientTest { } @Override - protected URI resourceUri(Resource resource) throws AcmeException { + protected URI resourceUri(Resource resource) { if (resourceMap.isEmpty()) { fail("Unexpected invocation of resourceUri()"); } diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/impl/DefaultConnectionTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/impl/DefaultConnectionTest.java index 80912ba6..bc0feeae 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/impl/DefaultConnectionTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/impl/DefaultConnectionTest.java @@ -41,6 +41,7 @@ import org.shredzone.acme4j.connector.HttpConnector; import org.shredzone.acme4j.connector.Resource; import org.shredzone.acme4j.connector.Session; import org.shredzone.acme4j.exception.AcmeException; +import org.shredzone.acme4j.exception.AcmeProtocolException; import org.shredzone.acme4j.exception.AcmeServerException; import org.shredzone.acme4j.util.ClaimBuilder; import org.shredzone.acme4j.util.TestUtils; @@ -91,7 +92,7 @@ public class DefaultConnectionTest { * {@code Replay-Nonce} header correctly. */ @Test - public void testGetNonceFromHeader() throws AcmeException { + public void testGetNonceFromHeader() { byte[] nonce = "foo-nonce-foo".getBytes(); when(mockUrlConnection.getHeaderField("Replay-Nonce")) @@ -113,7 +114,7 @@ public class DefaultConnectionTest { * {@code Replay-Nonce} header. */ @Test - public void testInvalidNonceFromHeader() throws AcmeException { + public void testInvalidNonceFromHeader() { String badNonce = "#$%&/*+*#'"; when(mockUrlConnection.getHeaderField("Replay-Nonce")).thenReturn(badNonce); @@ -123,7 +124,7 @@ public class DefaultConnectionTest { conn.conn = mockUrlConnection; conn.updateSession(session); fail("Expected to fail"); - } catch (AcmeException ex) { + } catch (AcmeProtocolException ex) { assertThat(ex.getMessage(), org.hamcrest.Matchers.startsWith("Invalid replay nonce")); } @@ -227,7 +228,7 @@ public class DefaultConnectionTest { try (DefaultConnection conn = new DefaultConnection(mockHttpConnection) { @Override - public Map readJsonResponse() throws AcmeException { + public Map readJsonResponse() { Map result = new HashMap(); result.put("type", "urn:zombie:error:apocalypse"); result.put("detail", "Zombie apocalypse in progress"); @@ -241,7 +242,7 @@ public class DefaultConnectionTest { assertThat(ex.getType(), is("urn:zombie:error:apocalypse")); assertThat(ex.getMessage(), is("Zombie apocalypse in progress")); assertThat(ex.getAcmeErrorType(), is(nullValue())); - } catch (AcmeException ex) { + } catch (AcmeException | IOException ex) { fail("Expected an AcmeServerException"); } @@ -259,7 +260,7 @@ public class DefaultConnectionTest { try (DefaultConnection conn = new DefaultConnection(mockHttpConnection) { @Override - public Map readJsonResponse() throws AcmeException { + public Map readJsonResponse() { return new HashMap(); }; }) { @@ -268,6 +269,8 @@ public class DefaultConnectionTest { fail("Expected to fail"); } catch (AcmeException ex) { assertThat(ex.getMessage(), not(isEmptyOrNullString())); + } catch (IOException ex) { + fail("Expected an AcmeException"); } verify(mockUrlConnection).getHeaderField("Content-Type"); @@ -305,7 +308,7 @@ public class DefaultConnectionTest { try (DefaultConnection conn = new DefaultConnection(mockHttpConnection) { @Override - public void updateSession(Session session) throws AcmeException { + public void updateSession(Session session) { assertThat(session, is(sameInstance(testSession))); if (session.getNonce() == null) { session.setNonce(nonce1); diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/impl/DummyConnection.java b/acme4j-client/src/test/java/org/shredzone/acme4j/impl/DummyConnection.java index 82ad478b..97e718a0 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/impl/DummyConnection.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/impl/DummyConnection.java @@ -33,42 +33,42 @@ import org.shredzone.acme4j.util.ClaimBuilder; public class DummyConnection implements Connection { @Override - public int sendRequest(URI uri) throws AcmeException { + public int sendRequest(URI uri) { throw new UnsupportedOperationException(); } @Override - public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Registration registration) throws AcmeException { + public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Registration registration) { throw new UnsupportedOperationException(); } @Override - public Map readJsonResponse() throws AcmeException { + public Map readJsonResponse() { throw new UnsupportedOperationException(); } @Override - public X509Certificate readCertificate() throws AcmeException { + public X509Certificate readCertificate() { throw new UnsupportedOperationException(); } @Override - public Map readDirectory() throws AcmeException { + public Map readDirectory() { throw new UnsupportedOperationException(); } @Override - public void updateSession(Session session) throws AcmeException { + public void updateSession(Session session) { throw new UnsupportedOperationException(); } @Override - public URI getLocation() throws AcmeException { + public URI getLocation() { throw new UnsupportedOperationException(); } @Override - public URI getLink(String relation) throws AcmeException { + public URI getLink(String relation) { throw new UnsupportedOperationException(); } diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/impl/GenericAcmeClientTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/impl/GenericAcmeClientTest.java index 05595634..71da2ef3 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/impl/GenericAcmeClientTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/impl/GenericAcmeClientTest.java @@ -17,6 +17,7 @@ import static org.hamcrest.Matchers.*; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.*; +import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.Date; @@ -88,7 +89,7 @@ public class GenericAcmeClientTest { * Test that the directory is properly read and cached. */ @Test - public void testResourceUri() throws AcmeException, URISyntaxException { + public void testResourceUri() throws AcmeException, IOException, URISyntaxException { Map directoryMap = new HashMap(); directoryMap.put(Resource.NEW_AUTHZ, new URI("http://example.com/acme/new-authz")); directoryMap.put(Resource.NEW_CERT, new URI("http://example.com/acme/new-cert"));