Review exceptions, closes issue #10

pull/17/merge
Richard Körber 2016-03-17 00:58:32 +01:00
parent bfa3f92a93
commit 158c3c46d1
21 changed files with 277 additions and 161 deletions

View File

@ -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;

View File

@ -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 {

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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<String, Object> readJsonResponse() throws AcmeException;
Map<String, Object> 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<Resource, URI> readDirectory() throws AcmeException;
Map<Resource, URI> 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.

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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<String, Object> result = conn.readJsonResponse();
unmarshalAuthorization(result, auth);
} catch (IOException ex) {
throw new AcmeNetworkException(ex);
}
}
@ -299,6 +312,8 @@ public abstract class AbstractAcmeClient implements AcmeClient {
Map<String, Object> 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<String, Object> 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);
}
}

View File

@ -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<String, Object> readJsonResponse() throws AcmeException {
if (conn == null) {
throw new IllegalStateException("Not connected");
}
public Map<String, Object> 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<Resource, URI> readDirectory() throws AcmeException {
if (conn == null) {
throw new IllegalStateException("Not connected");
}
public Map<Resource, URI> 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<Resource, URI> 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<String> 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<String, Object> 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.
*/

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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"));
}

View File

@ -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"));

View File

@ -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<String, Object> 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<String, Object> readJsonResponse() throws AcmeException {
public Map<String, Object> 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<String, Object> readJsonResponse() throws AcmeException {
public Map<String, Object> 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<String, Object> readJsonResponse() throws AcmeException {
public Map<String, Object> 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<String, Object> readJsonResponse() throws AcmeException {
public Map<String, Object> 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<String, Object> readJsonResponse() throws AcmeException {
public Map<String, Object> 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()");
}

View File

@ -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<String,Object> readJsonResponse() throws AcmeException {
public Map<String,Object> readJsonResponse() {
Map<String, Object> result = new HashMap<String, Object>();
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<String,Object> readJsonResponse() throws AcmeException {
public Map<String,Object> readJsonResponse() {
return new HashMap<String, Object>();
};
}) {
@ -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);

View File

@ -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<String, Object> readJsonResponse() throws AcmeException {
public Map<String, Object> readJsonResponse() {
throw new UnsupportedOperationException();
}
@Override
public X509Certificate readCertificate() throws AcmeException {
public X509Certificate readCertificate() {
throw new UnsupportedOperationException();
}
@Override
public Map<Resource, URI> readDirectory() throws AcmeException {
public Map<Resource, URI> 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();
}

View File

@ -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<Resource, URI> directoryMap = new HashMap<Resource, URI>();
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"));