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 * @param registration
* {@link Registration}, with the new key pair and the account location URI * {@link Registration}, with the new key pair and the account location URI
* set * set
* @throws AcmeException
*/ */
void recoverRegistration(Registration registration) 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.List;
import java.util.ServiceLoader; import java.util.ServiceLoader;
import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.provider.AcmeClientProvider; 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 * 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 * server's directory service, or a special acme URI for specific
* implementations. * implementations.
* @return {@link AcmeClient} for communication with the server
*/ */
public static AcmeClient connect(String serverUri) throws AcmeException { public static AcmeClient connect(String serverUri) {
try { try {
return connect(new URI(serverUri)); return connect(new URI(serverUri));
} catch (URISyntaxException ex) { } catch (URISyntaxException ex) {
throw new IllegalArgumentException(ex); throw new IllegalArgumentException("Invalid server URI", ex);
} }
} }
@ -63,7 +61,7 @@ public final class AcmeClientFactory {
* implementations. * implementations.
* @return {@link AcmeClient} for communication with the server * @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) { if (serverUri == null) {
throw new NullPointerException("serverUri must not be null"); throw new NullPointerException("serverUri must not be null");
} }
@ -76,9 +74,9 @@ public final class AcmeClientFactory {
} }
if (candidates.isEmpty()) { 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) { } else if (candidates.size() > 1) {
throw new AcmeException("There are " + candidates.size() + " " throw new IllegalStateException("There are " + candidates.size() + " "
+ AcmeClientProvider.class.getSimpleName() + " accepting " + serverUri + AcmeClientProvider.class.getSimpleName() + " accepting " + serverUri
+ ". Please check your classpath."); + ". Please check your classpath.");
} else { } else {

View File

@ -18,6 +18,7 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import org.jose4j.base64url.Base64Url; import org.jose4j.base64url.Base64Url;
import org.shredzone.acme4j.exception.AcmeProtocolException;
/** /**
* Implements the {@value TYPE} challenge. * Implements the {@value TYPE} challenge.
@ -45,8 +46,7 @@ public class Dns01Challenge extends GenericTokenChallenge {
byte[] digest = md.digest(); byte[] digest = md.digest();
return Base64Url.encode(digest); return Base64Url.encode(digest);
} catch (NoSuchAlgorithmException | UnsupportedEncodingException ex) { } catch (NoSuchAlgorithmException | UnsupportedEncodingException ex) {
// both should be standard in JDK... throw new AcmeProtocolException("Failed to compute digest", ex);
throw new RuntimeException(ex);
} }
} }

View File

@ -31,6 +31,7 @@ import org.jose4j.jwk.JsonWebKey;
import org.jose4j.jwk.JsonWebKey.OutputControlLevel; import org.jose4j.jwk.JsonWebKey.OutputControlLevel;
import org.jose4j.lang.JoseException; import org.jose4j.lang.JoseException;
import org.shredzone.acme4j.Status; import org.shredzone.acme4j.Status;
import org.shredzone.acme4j.exception.AcmeProtocolException;
import org.shredzone.acme4j.util.ClaimBuilder; import org.shredzone.acme4j.util.ClaimBuilder;
import org.shredzone.acme4j.util.TimestampParser; import org.shredzone.acme4j.util.TimestampParser;
@ -75,7 +76,7 @@ public class GenericChallenge implements Challenge {
try { try {
return new URI(uri); return new URI(uri);
} catch (URISyntaxException ex) { } 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"); throw new IllegalArgumentException("map does not contain a type");
} }
if (!acceptable(type)) { if (!acceptable(type)) {
throw new IllegalArgumentException("wrong type: " + type); throw new AcmeProtocolException("wrong type: " + type);
} }
data.clear(); data.clear();
@ -155,7 +156,7 @@ public class GenericChallenge implements Challenge {
md.update(cb.toString().getBytes("UTF-8")); md.update(cb.toString().getBytes("UTF-8"));
return md.digest(); return md.digest();
} catch (JoseException | NoSuchAlgorithmException | UnsupportedEncodingException ex) { } 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())); data = new HashMap<>(JsonUtil.parseJson(in.readUTF()));
in.defaultReadObject(); in.defaultReadObject();
} catch (JoseException ex) { } 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.json.JsonUtil;
import org.jose4j.lang.JoseException; import org.jose4j.lang.JoseException;
import org.shredzone.acme4j.Registration; import org.shredzone.acme4j.Registration;
import org.shredzone.acme4j.exception.AcmeProtocolException;
import org.shredzone.acme4j.util.ClaimBuilder; import org.shredzone.acme4j.util.ClaimBuilder;
import org.shredzone.acme4j.util.ValidationBuilder; import org.shredzone.acme4j.util.ValidationBuilder;
@ -112,7 +113,7 @@ public class ProofOfPossession01Challenge extends GenericChallenge {
} }
} }
} catch (CertificateException | IOException ex) { } 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)); cb.put(KEY_AUTHORIZATION, JsonUtil.parseJson(validation));
} catch (JoseException ex) { } catch (JoseException ex) {
// should not happen, as the JSON is prevalidated in the setter // 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 java.security.NoSuchAlgorithmException;
import org.shredzone.acme4j.Registration; import org.shredzone.acme4j.Registration;
import org.shredzone.acme4j.exception.AcmeProtocolException;
/** /**
* Implements the {@value TYPE} challenge. * Implements the {@value TYPE} challenge.
@ -75,8 +76,7 @@ public class TlsSni01Challenge extends GenericTokenChallenge {
} }
return new String(result); return new String(result);
} catch (NoSuchAlgorithmException | UnsupportedEncodingException ex) { } catch (NoSuchAlgorithmException | UnsupportedEncodingException ex) {
// Algorithm and Encoding are standard on Java throw new AcmeProtocolException("Could not compute hash", ex);
throw new RuntimeException(ex);
} }
} }

View File

@ -13,6 +13,7 @@
*/ */
package org.shredzone.acme4j.connector; package org.shredzone.acme4j.connector;
import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.Map; import java.util.Map;
@ -36,7 +37,7 @@ public interface Connection extends AutoCloseable {
* {@link URI} to send the request to. * {@link URI} to send the request to.
* @return HTTP response code * @return HTTP response code
*/ */
int sendRequest(URI uri) throws AcmeException; int sendRequest(URI uri) throws IOException;
/** /**
* Sends a signed POST request. * Sends a signed POST request.
@ -52,28 +53,28 @@ public interface Connection extends AutoCloseable {
* @return HTTP response code * @return HTTP response code
*/ */
int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Registration registration) int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Registration registration)
throws AcmeException; throws IOException;
/** /**
* Reads a server response as JSON data. * Reads a server response as JSON data.
* *
* @return Map containing the parsed JSON data * @return Map containing the parsed JSON data
*/ */
Map<String, Object> readJsonResponse() throws AcmeException; Map<String, Object> readJsonResponse() throws IOException;
/** /**
* Reads a certificate. * Reads a certificate.
* *
* @return {@link X509Certificate} that was read. * @return {@link X509Certificate} that was read.
*/ */
X509Certificate readCertificate() throws AcmeException; X509Certificate readCertificate() throws IOException;
/** /**
* Reads a resource directory. * Reads a resource directory.
* *
* @return Map of {@link Resource} and the respective {@link URI} to invoke * @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. * Updates a {@link Session} by evaluating the HTTP response header.
@ -81,14 +82,14 @@ public interface Connection extends AutoCloseable {
* @param session * @param session
* {@link Session} instance to be updated * {@link Session} instance to be updated
*/ */
void updateSession(Session session) throws AcmeException; void updateSession(Session session);
/** /**
* Gets a location from the {@code Location} header. * Gets a location from the {@code Location} header.
* *
* @return Location {@link URI}, or {@code null} if no Location header was set * @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. * Gets a link relation from the header.
@ -97,14 +98,14 @@ public interface Connection extends AutoCloseable {
* Link relation * Link relation
* @return Link, or {@code null} if there was no such 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 * Handles a problem by throwing an exception. If a JSON problem was returned, an
* {@link AcmeServerException} will be thrown. Otherwise a generic * {@link AcmeServerException} will be thrown. Otherwise a generic
* {@link AcmeException} is thrown. * {@link AcmeException} is thrown.
*/ */
void throwAcmeException() throws AcmeException; void throwAcmeException() throws AcmeException, IOException;
/** /**
* Closes the {@link Connection}, releasing all resources. * 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; package org.shredzone.acme4j.impl;
import java.io.IOException;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URI; import java.net.URI;
import java.security.KeyPair; import java.security.KeyPair;
@ -37,6 +38,8 @@ import org.shredzone.acme4j.connector.Resource;
import org.shredzone.acme4j.connector.Session; import org.shredzone.acme4j.connector.Session;
import org.shredzone.acme4j.exception.AcmeConflictException; import org.shredzone.acme4j.exception.AcmeConflictException;
import org.shredzone.acme4j.exception.AcmeException; 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.ClaimBuilder;
import org.shredzone.acme4j.util.SignatureUtils; import org.shredzone.acme4j.util.SignatureUtils;
import org.shredzone.acme4j.util.TimestampParser; import org.shredzone.acme4j.util.TimestampParser;
@ -125,6 +128,8 @@ public abstract class AbstractAcmeClient implements AcmeClient {
if (rc == HttpURLConnection.HTTP_CONFLICT) { if (rc == HttpURLConnection.HTTP_CONFLICT) {
throw new AcmeConflictException("Account is already registered", location); 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) { if (tos != null) {
registration.setAgreement(tos); registration.setAgreement(tos);
} }
} catch (IOException ex) {
throw new AcmeNetworkException(ex);
} }
} }
@ -198,7 +205,7 @@ public abstract class AbstractAcmeClient implements AcmeClient {
newKey = jws.getCompactSerialization(); newKey = jws.getCompactSerialization();
} catch (JoseException ex) { } catch (JoseException ex) {
throw new IllegalArgumentException("Bad newKeyPair", ex); throw new AcmeProtocolException("Bad newKeyPair", ex);
} }
LOG.debug("changeRegistrationKey"); LOG.debug("changeRegistrationKey");
@ -211,6 +218,8 @@ public abstract class AbstractAcmeClient implements AcmeClient {
if (rc != HttpURLConnection.HTTP_ACCEPTED) { if (rc != HttpURLConnection.HTTP_ACCEPTED) {
conn.throwAcmeException(); conn.throwAcmeException();
} }
} catch (IOException ex) {
throw new AcmeNetworkException(ex);
} }
} }
@ -244,6 +253,8 @@ public abstract class AbstractAcmeClient implements AcmeClient {
if (tos != null) { if (tos != null) {
registration.setAgreement(tos); 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(); Map<String, Object> result = conn.readJsonResponse();
unmarshalAuthorization(result, auth); 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(); Map<String, Object> result = conn.readJsonResponse();
unmarshalAuthorization(result, auth); unmarshalAuthorization(result, auth);
} catch (IOException ex) {
throw new AcmeNetworkException(ex);
} }
} }
@ -326,6 +341,8 @@ public abstract class AbstractAcmeClient implements AcmeClient {
} }
challenge.unmarshall(conn.readJsonResponse()); 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()); 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(); Map<String, Object> json = conn.readJsonResponse();
if (!(json.containsKey("type"))) { 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); 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(); // X509Certificate cert = conn.readCertificate();
return conn.getLocation(); return conn.getLocation();
} catch (IOException ex) {
throw new AcmeNetworkException(ex);
} }
} }
@ -415,6 +438,8 @@ public abstract class AbstractAcmeClient implements AcmeClient {
} }
return conn.readCertificate(); return conn.readCertificate();
} catch (IOException ex) {
throw new AcmeNetworkException(ex);
} }
} }
@ -430,7 +455,7 @@ public abstract class AbstractAcmeClient implements AcmeClient {
LOG.debug("revokeCertificate"); LOG.debug("revokeCertificate");
URI resUri = resourceUri(Resource.REVOKE_CERT); URI resUri = resourceUri(Resource.REVOKE_CERT);
if (resUri == null) { 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()) { try (Connection conn = createConnection()) {
@ -443,7 +468,9 @@ public abstract class AbstractAcmeClient implements AcmeClient {
conn.throwAcmeException(); conn.throwAcmeException();
} }
} catch (CertificateEncodingException ex) { } 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.connector.Session;
import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.exception.AcmeRateLimitExceededException; import org.shredzone.acme4j.exception.AcmeRateLimitExceededException;
import org.shredzone.acme4j.exception.AcmeProtocolException;
import org.shredzone.acme4j.exception.AcmeServerException; import org.shredzone.acme4j.exception.AcmeServerException;
import org.shredzone.acme4j.exception.AcmeUnauthorizedException; import org.shredzone.acme4j.exception.AcmeUnauthorizedException;
import org.shredzone.acme4j.util.ClaimBuilder; import org.shredzone.acme4j.util.ClaimBuilder;
@ -72,35 +73,29 @@ public class DefaultConnection implements Connection {
} }
@Override @Override
public int sendRequest(URI uri) throws AcmeException { public int sendRequest(URI uri) throws IOException {
if (uri == null) { if (uri == null) {
throw new NullPointerException("uri must not be null"); throw new NullPointerException("uri must not be null");
} }
if (conn != null) { assertConnectionIsClosed();
throw new IllegalStateException("Connection was not closed. Race condition?");
}
try { LOG.debug("GET {}", uri);
LOG.debug("GET {}", uri);
conn = httpConnector.openConnection(uri); conn = httpConnector.openConnection(uri);
conn.setRequestMethod("GET"); conn.setRequestMethod("GET");
conn.setRequestProperty("Accept-Charset", "utf-8"); conn.setRequestProperty("Accept-Charset", "utf-8");
conn.setDoOutput(false); conn.setDoOutput(false);
conn.connect(); conn.connect();
logHeaders(); logHeaders();
return conn.getResponseCode(); return conn.getResponseCode();
} catch (IOException ex) {
throw new AcmeException("Request failed: " + uri, ex);
}
} }
@Override @Override
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Registration registration) public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Registration registration)
throws AcmeException { throws IOException {
if (uri == null) { if (uri == null) {
throw new NullPointerException("uri must not be null"); throw new NullPointerException("uri must not be null");
} }
@ -113,9 +108,7 @@ public class DefaultConnection implements Connection {
if (registration == null) { if (registration == null) {
throw new NullPointerException("registration must not be null"); throw new NullPointerException("registration must not be null");
} }
if (conn != null) { assertConnectionIsClosed();
throw new IllegalStateException("Connection was not closed. Race condition?");
}
try { try {
KeyPair keypair = registration.getKeyPair(); KeyPair keypair = registration.getKeyPair();
@ -130,7 +123,7 @@ public class DefaultConnection implements Connection {
} }
if (session.getNonce() == null) { 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); LOG.debug("POST {} with claims: {}", uri, claims);
@ -164,21 +157,19 @@ public class DefaultConnection implements Connection {
updateSession(session); updateSession(session);
return conn.getResponseCode(); return conn.getResponseCode();
} catch (JoseException | IOException ex) { } catch (JoseException ex) {
throw new AcmeException("Request failed: " + uri, ex); throw new AcmeProtocolException("Failed to generate a JSON request", ex);
} }
} }
@Override @Override
public Map<String, Object> readJsonResponse() throws AcmeException { public Map<String, Object> readJsonResponse() throws IOException {
if (conn == null) { assertConnectionIsOpen();
throw new IllegalStateException("Not connected");
}
String contentType = conn.getHeaderField("Content-Type"); String contentType = conn.getHeaderField("Content-Type");
if (!("application/json".equals(contentType) if (!("application/json".equals(contentType)
|| "application/problem+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(); StringBuilder sb = new StringBuilder();
@ -195,41 +186,37 @@ public class DefaultConnection implements Connection {
LOG.debug("Result JSON: {}", sb); LOG.debug("Result JSON: {}", sb);
} }
} catch (JoseException | IOException ex) { } catch (JoseException ex) {
throw new AcmeException("Failed to parse response: " + sb, ex); throw new AcmeProtocolException("Failed to parse response: " + sb, ex);
} }
return result; return result;
} }
@Override @Override
public X509Certificate readCertificate() throws AcmeException { public X509Certificate readCertificate() throws IOException {
if (conn == null) { assertConnectionIsOpen();
throw new IllegalStateException("Not connected");
}
String contentType = conn.getHeaderField("Content-Type"); String contentType = conn.getHeaderField("Content-Type");
if (!("application/pkix-cert".equals(contentType))) { 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()) { try (InputStream in = conn.getInputStream()) {
CertificateFactory cf = CertificateFactory.getInstance("X.509"); CertificateFactory cf = CertificateFactory.getInstance("X.509");
return (X509Certificate) cf.generateCertificate(in); return (X509Certificate) cf.generateCertificate(in);
} catch (CertificateException | IOException ex) { } catch (CertificateException ex) {
throw new AcmeException("Failed to read certificate", ex); throw new AcmeProtocolException("Failed to read certificate", ex);
} }
} }
@Override @Override
public Map<Resource, URI> readDirectory() throws AcmeException { public Map<Resource, URI> readDirectory() throws IOException {
if (conn == null) { assertConnectionIsOpen();
throw new IllegalStateException("Not connected");
}
String contentType = conn.getHeaderField("Content-Type"); String contentType = conn.getHeaderField("Content-Type");
if (!("application/json".equals(contentType))) { 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); EnumMap<Resource, URI> resourceMap = new EnumMap<>(Resource.class);
@ -250,18 +237,16 @@ public class DefaultConnection implements Connection {
} }
LOG.debug("Resource directory: {}", resourceMap); LOG.debug("Resource directory: {}", resourceMap);
} catch (JoseException | URISyntaxException | IOException ex) { } catch (JoseException | URISyntaxException ex) {
throw new AcmeException("Failed to read directory: " + sb, ex); throw new AcmeProtocolException("Failed to read directory: " + sb, ex);
} }
return resourceMap; return resourceMap;
} }
@Override @Override
public void updateSession(Session session) throws AcmeException { public void updateSession(Session session) {
if (conn == null) { assertConnectionIsOpen();
throw new IllegalStateException("Not connected");
}
String nonceHeader = conn.getHeaderField("Replay-Nonce"); String nonceHeader = conn.getHeaderField("Replay-Nonce");
if (nonceHeader == null || nonceHeader.trim().isEmpty()) { if (nonceHeader == null || nonceHeader.trim().isEmpty()) {
@ -269,7 +254,7 @@ public class DefaultConnection implements Connection {
} }
if (!BASE64URL_PATTERN.matcher(nonceHeader).matches()) { 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); LOG.debug("Replay Nonce: {}", nonceHeader);
@ -278,10 +263,8 @@ public class DefaultConnection implements Connection {
} }
@Override @Override
public URI getLocation() throws AcmeException { public URI getLocation() {
if (conn == null) { assertConnectionIsOpen();
throw new IllegalStateException("Not connected");
}
String location = conn.getHeaderField("Location"); String location = conn.getHeaderField("Location");
if (location == null) { if (location == null) {
@ -292,15 +275,13 @@ public class DefaultConnection implements Connection {
LOG.debug("Location: {}", location); LOG.debug("Location: {}", location);
return new URI(location); return new URI(location);
} catch (URISyntaxException ex) { } catch (URISyntaxException ex) {
throw new AcmeException("Bad Location header: " + location); throw new AcmeProtocolException("Bad Location header: " + location);
} }
} }
@Override @Override
public URI getLink(String relation) throws AcmeException { public URI getLink(String relation) {
if (conn == null) { assertConnectionIsOpen();
throw new IllegalStateException("Not connected");
}
List<String> links = conn.getHeaderFields().get("Link"); List<String> links = conn.getHeaderFields().get("Link");
if (links != null) { if (links != null) {
@ -313,19 +294,18 @@ public class DefaultConnection implements Connection {
LOG.debug("Link: {} -> {}", relation, location); LOG.debug("Link: {} -> {}", relation, location);
return new URI(location); return new URI(location);
} catch (URISyntaxException ex) { } catch (URISyntaxException ex) {
throw new AcmeException("Bad '" + relation + "' Link header: " + link); throw new AcmeProtocolException("Bad '" + relation + "' Link header: " + link);
} }
} }
} }
} }
return null; return null;
} }
@Override @Override
public void throwAcmeException() throws AcmeException { public void throwAcmeException() throws AcmeException, IOException {
if (conn == null) { assertConnectionIsOpen();
throw new IllegalStateException("Not connected");
}
if ("application/problem+json".equals(conn.getHeaderField("Content-Type"))) { if ("application/problem+json".equals(conn.getHeaderField("Content-Type"))) {
Map<String, Object> map = readJsonResponse(); Map<String, Object> map = readJsonResponse();
@ -351,12 +331,8 @@ public class DefaultConnection implements Connection {
throw new AcmeServerException(type, detail); throw new AcmeServerException(type, detail);
} }
} else { } else {
try { throw new AcmeException("HTTP " + conn.getResponseCode() + ": "
throw new AcmeException("HTTP " + conn.getResponseCode() + ": " + conn.getResponseMessage());
+ conn.getResponseMessage());
} catch (IOException ex) {
throw new AcmeException("Network error");
}
} }
} }
@ -365,6 +341,24 @@ public class DefaultConnection implements Connection {
conn = null; 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. * Log all HTTP headers in debug mode.
*/ */

View File

@ -13,6 +13,7 @@
*/ */
package org.shredzone.acme4j.impl; package org.shredzone.acme4j.impl;
import java.io.IOException;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URI; import java.net.URI;
import java.util.Date; 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.Connection;
import org.shredzone.acme4j.connector.Resource; import org.shredzone.acme4j.connector.Resource;
import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.exception.AcmeNetworkException;
import org.shredzone.acme4j.exception.AcmeProtocolException;
import org.shredzone.acme4j.provider.AcmeClientProvider; import org.shredzone.acme4j.provider.AcmeClientProvider;
/** /**
@ -92,7 +95,7 @@ public class GenericAcmeClient extends AbstractAcmeClient {
if (directoryMap.isEmpty() || !directoryCacheExpiry.after(now)) { if (directoryMap.isEmpty() || !directoryCacheExpiry.after(now)) {
if (directoryUri == null) { 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()) { try (Connection conn = createConnection()) {
@ -110,6 +113,8 @@ public class GenericAcmeClient extends AbstractAcmeClient {
directoryMap.clear(); directoryMap.clear();
directoryMap.putAll(newMap); directoryMap.putAll(newMap);
directoryCacheExpiry = new Date(now.getTime() + 60 * 60 * 1000L); directoryCacheExpiry = new Date(now.getTime() + 60 * 60 * 1000L);
} catch (IOException ex) {
throw new AcmeNetworkException(ex);
} }
} }
return directoryMap.get(resource); return directoryMap.get(resource);

View File

@ -17,6 +17,7 @@ import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import org.shredzone.acme4j.connector.HttpConnector; import org.shredzone.acme4j.connector.HttpConnector;
import org.shredzone.acme4j.exception.AcmeProtocolException;
import org.shredzone.acme4j.provider.AbstractAcmeClientProvider; import org.shredzone.acme4j.provider.AbstractAcmeClientProvider;
import org.shredzone.acme4j.provider.AcmeClientProvider; import org.shredzone.acme4j.provider.AcmeClientProvider;
@ -58,7 +59,7 @@ public class LetsEncryptAcmeClientProvider extends AbstractAcmeClientProvider {
try { try {
return new URI(directoryUri); return new URI(directoryUri);
} catch (URISyntaxException ex) { } 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.jwk.PublicJsonWebKey;
import org.jose4j.lang.JoseException; import org.jose4j.lang.JoseException;
import org.shredzone.acme4j.connector.Resource; import org.shredzone.acme4j.connector.Resource;
import org.shredzone.acme4j.exception.AcmeProtocolException;
/** /**
* Builder for claim structures. * Builder for claim structures.
@ -128,7 +129,7 @@ public class ClaimBuilder {
object(key).putAll(jwkParams); object(key).putAll(jwkParams);
return this; return this;
} catch (JoseException ex) { } 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.jose4j.lang.JoseException;
import org.shredzone.acme4j.Registration; import org.shredzone.acme4j.Registration;
import org.shredzone.acme4j.challenge.ProofOfPossession01Challenge; import org.shredzone.acme4j.challenge.ProofOfPossession01Challenge;
import org.shredzone.acme4j.exception.AcmeProtocolException;
/** /**
* Generates a validation string for {@link ProofOfPossession01Challenge}. * Generates a validation string for {@link ProofOfPossession01Challenge}.
@ -121,7 +122,7 @@ public class ValidationBuilder {
auth.put("signature", jws.getEncodedSignature()); auth.put("signature", jws.getEncodedSignature());
return auth.toString(); return auth.toString();
} catch (JoseException ex) { } 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.junit.Test;
import org.shredzone.acme4j.challenge.Challenge; import org.shredzone.acme4j.challenge.Challenge;
import org.shredzone.acme4j.connector.Connection; import org.shredzone.acme4j.connector.Connection;
import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.provider.AcmeClientProvider; import org.shredzone.acme4j.provider.AcmeClientProvider;
/** /**
@ -43,26 +42,26 @@ public class AcmeClientFactoryTest {
* the correct {@link AcmeClientProvider}. * the correct {@link AcmeClientProvider}.
*/ */
@Test @Test
public void testConnectURI() throws URISyntaxException, AcmeException { public void testConnectURI() throws URISyntaxException {
AcmeClient client = AcmeClientFactory.connect(new URI("acme://example.com")); AcmeClient client = AcmeClientFactory.connect(new URI("acme://example.com"));
assertThat(client, is(sameInstance(DUMMY_CLIENT))); assertThat(client, is(sameInstance(DUMMY_CLIENT)));
} }
/** /**
* There are no testing providers accepting {@code acme://example.org}. Test that * 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) @Test(expected = IllegalArgumentException.class)
public void testNone() throws URISyntaxException, AcmeException { public void testNone() throws URISyntaxException {
AcmeClientFactory.connect(new URI("acme://example.org")); AcmeClientFactory.connect(new URI("acme://example.org"));
} }
/** /**
* There are two testing providers accepting {@code acme://example.net}. Test that * 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) @Test(expected = IllegalStateException.class)
public void testDuplicate() throws URISyntaxException, AcmeException { public void testDuplicate() throws URISyntaxException {
AcmeClientFactory.connect(new URI("acme://example.net")); 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.jose4j.lang.JoseException;
import org.junit.Test; import org.junit.Test;
import org.shredzone.acme4j.Status; import org.shredzone.acme4j.Status;
import org.shredzone.acme4j.exception.AcmeProtocolException;
import org.shredzone.acme4j.util.ClaimBuilder; import org.shredzone.acme4j.util.ClaimBuilder;
import org.shredzone.acme4j.util.TestUtils; import org.shredzone.acme4j.util.TestUtils;
import org.shredzone.acme4j.util.TimestampParser; import org.shredzone.acme4j.util.TimestampParser;
@ -86,7 +87,7 @@ public class GenericChallengeTest {
/** /**
* Test that an exception is thrown on challenge type mismatch. * Test that an exception is thrown on challenge type mismatch.
*/ */
@Test(expected = IllegalArgumentException.class) @Test(expected = AcmeProtocolException.class)
public void testNotAcceptable() throws URISyntaxException { public void testNotAcceptable() throws URISyntaxException {
Http01Challenge challenge = new Http01Challenge(); Http01Challenge challenge = new Http01Challenge();
challenge.unmarshall(TestUtils.getJsonAsMap("dnsChallenge")); 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.Resource;
import org.shredzone.acme4j.connector.Session; import org.shredzone.acme4j.connector.Session;
import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.exception.AcmeProtocolException;
import org.shredzone.acme4j.util.ClaimBuilder; import org.shredzone.acme4j.util.ClaimBuilder;
import org.shredzone.acme4j.util.TestUtils; import org.shredzone.acme4j.util.TestUtils;
import org.shredzone.acme4j.util.TimestampParser; import org.shredzone.acme4j.util.TimestampParser;
@ -80,7 +81,7 @@ public class AbstractAcmeClientTest {
Connection connection = new DummyConnection() { Connection connection = new DummyConnection() {
@Override @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(uri, is(resourceUri));
assertThat(claims.toString(), sameJSONAs(getJson("newRegistration"))); assertThat(claims.toString(), sameJSONAs(getJson("newRegistration")));
assertThat(session, is(notNullValue())); assertThat(session, is(notNullValue()));
@ -89,12 +90,12 @@ public class AbstractAcmeClientTest {
} }
@Override @Override
public URI getLocation() throws AcmeException { public URI getLocation() {
return locationUri; return locationUri;
} }
@Override @Override
public URI getLink(String relation) throws AcmeException { public URI getLink(String relation) {
switch(relation) { switch(relation) {
case "terms-of-service": return agreementUri; case "terms-of-service": return agreementUri;
default: return null; default: return null;
@ -123,7 +124,7 @@ public class AbstractAcmeClientTest {
Connection connection = new DummyConnection() { Connection connection = new DummyConnection() {
@Override @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(uri, is(locationUri));
assertThat(claims.toString(), sameJSONAs(getJson("modifyRegistration"))); assertThat(claims.toString(), sameJSONAs(getJson("modifyRegistration")));
assertThat(session, is(notNullValue())); assertThat(session, is(notNullValue()));
@ -132,12 +133,12 @@ public class AbstractAcmeClientTest {
} }
@Override @Override
public URI getLocation() throws AcmeException { public URI getLocation() {
return locationUri; return locationUri;
} }
@Override @Override
public URI getLink(String relation) throws AcmeException { public URI getLink(String relation) {
switch(relation) { switch(relation) {
case "terms-of-service": return agreementUri; case "terms-of-service": return agreementUri;
default: return null; default: return null;
@ -165,7 +166,7 @@ public class AbstractAcmeClientTest {
Connection connection = new DummyConnection() { Connection connection = new DummyConnection() {
@Override @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(); Map<String, Object> claimMap = claims.toMap();
assertThat(claimMap.get("resource"), is((Object) "reg")); assertThat(claimMap.get("resource"), is((Object) "reg"));
assertThat(claimMap.get("newKey"), not(nullValue())); assertThat(claimMap.get("newKey"), not(nullValue()));
@ -185,7 +186,7 @@ public class AbstractAcmeClientTest {
jws.setKey(newKeyPair.getPublic()); jws.setKey(newKeyPair.getPublic());
assertThat(jws.getPayload(), sameJSONAs(expectedPayload.toString())); assertThat(jws.getPayload(), sameJSONAs(expectedPayload.toString()));
} catch (JoseException ex) { } catch (JoseException ex) {
throw new AcmeException("Bad newKey", ex); throw new AcmeProtocolException("Bad newKey", ex);
} }
assertThat(uri, is(locationUri)); assertThat(uri, is(locationUri));
@ -195,7 +196,7 @@ public class AbstractAcmeClientTest {
} }
@Override @Override
public URI getLocation() throws AcmeException { public URI getLocation() {
return locationUri; return locationUri;
} }
}; };
@ -209,7 +210,7 @@ public class AbstractAcmeClientTest {
* Test that the same account key is not accepted for change * Test that the same account key is not accepted for change
*/ */
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void testChangeRegistrationSameKey() throws AcmeException, IOException { public void testChangeRegistrationSameKey() throws AcmeException {
Registration registration = new Registration(accountKeyPair); Registration registration = new Registration(accountKeyPair);
registration.setLocation(locationUri); registration.setLocation(locationUri);
@ -230,21 +231,19 @@ public class AbstractAcmeClientTest {
Connection connection = new DummyConnection() { Connection connection = new DummyConnection() {
@Override @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("recoverRegistration")));
assertThat(session, is(notNullValue())); assertThat(session, is(notNullValue()));
assertThat(registration.getKeyPair(), is(sameInstance(accountKeyPair))); assertThat(registration.getKeyPair(), is(sameInstance(accountKeyPair)));
return HttpURLConnection.HTTP_CREATED; return HttpURLConnection.HTTP_CREATED;
} }
@Override @Override
public URI getLocation() throws AcmeException { public URI getLocation() {
return anotherLocationUri; return anotherLocationUri;
} }
@Override @Override
public URI getLink(String relation) throws AcmeException { public URI getLink(String relation) {
switch(relation) { switch(relation) {
case "terms-of-service": return agreementUri; case "terms-of-service": return agreementUri;
default: return null; default: return null;
@ -271,7 +270,7 @@ public class AbstractAcmeClientTest {
Connection connection = new DummyConnection() { Connection connection = new DummyConnection() {
@Override @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(uri, is(resourceUri));
assertThat(claims.toString(), sameJSONAs(getJson("newAuthorizationRequest"))); assertThat(claims.toString(), sameJSONAs(getJson("newAuthorizationRequest")));
assertThat(session, is(notNullValue())); assertThat(session, is(notNullValue()));
@ -280,12 +279,12 @@ public class AbstractAcmeClientTest {
} }
@Override @Override
public Map<String, Object> readJsonResponse() throws AcmeException { public Map<String, Object> readJsonResponse() {
return getJsonAsMap("newAuthorizationResponse"); return getJsonAsMap("newAuthorizationResponse");
} }
@Override @Override
public URI getLocation() throws AcmeException { public URI getLocation() {
return locationUri; return locationUri;
} }
}; };
@ -324,13 +323,13 @@ public class AbstractAcmeClientTest {
Connection connection = new DummyConnection() { Connection connection = new DummyConnection() {
@Override @Override
public int sendRequest(URI uri) throws AcmeException { public int sendRequest(URI uri) {
assertThat(uri, is(locationUri)); assertThat(uri, is(locationUri));
return HttpURLConnection.HTTP_OK; return HttpURLConnection.HTTP_OK;
} }
@Override @Override
public Map<String, Object> readJsonResponse() throws AcmeException { public Map<String, Object> readJsonResponse() {
return getJsonAsMap("updateAuthorizationResponse"); return getJsonAsMap("updateAuthorizationResponse");
} }
}; };
@ -366,7 +365,7 @@ public class AbstractAcmeClientTest {
public void testTriggerChallenge() throws AcmeException { public void testTriggerChallenge() throws AcmeException {
Connection connection = new DummyConnection() { Connection connection = new DummyConnection() {
@Override @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(uri, is(resourceUri));
assertThat(claims.toString(), sameJSONAs(getJson("triggerHttpChallengeRequest"))); assertThat(claims.toString(), sameJSONAs(getJson("triggerHttpChallengeRequest")));
assertThat(session, is(notNullValue())); assertThat(session, is(notNullValue()));
@ -375,7 +374,7 @@ public class AbstractAcmeClientTest {
} }
@Override @Override
public Map<String, Object> readJsonResponse() throws AcmeException { public Map<String, Object> readJsonResponse() {
return getJsonAsMap("triggerHttpChallengeResponse"); return getJsonAsMap("triggerHttpChallengeResponse");
} }
}; };
@ -399,13 +398,13 @@ public class AbstractAcmeClientTest {
public void testUpdateChallenge() throws AcmeException { public void testUpdateChallenge() throws AcmeException {
Connection connection = new DummyConnection() { Connection connection = new DummyConnection() {
@Override @Override
public int sendRequest(URI uri) throws AcmeException { public int sendRequest(URI uri) {
assertThat(uri, is(locationUri)); assertThat(uri, is(locationUri));
return HttpURLConnection.HTTP_ACCEPTED; return HttpURLConnection.HTTP_ACCEPTED;
} }
@Override @Override
public Map<String, Object> readJsonResponse() throws AcmeException { public Map<String, Object> readJsonResponse() {
return getJsonAsMap("updateHttpChallengeResponse"); return getJsonAsMap("updateHttpChallengeResponse");
} }
}; };
@ -425,13 +424,13 @@ public class AbstractAcmeClientTest {
public void testRestoreChallenge() throws AcmeException { public void testRestoreChallenge() throws AcmeException {
Connection connection = new DummyConnection() { Connection connection = new DummyConnection() {
@Override @Override
public int sendRequest(URI uri) throws AcmeException { public int sendRequest(URI uri) {
assertThat(uri, is(locationUri)); assertThat(uri, is(locationUri));
return HttpURLConnection.HTTP_ACCEPTED; return HttpURLConnection.HTTP_ACCEPTED;
} }
@Override @Override
public Map<String, Object> readJsonResponse() throws AcmeException { public Map<String, Object> readJsonResponse() {
return getJsonAsMap("updateHttpChallengeResponse"); return getJsonAsMap("updateHttpChallengeResponse");
} }
}; };
@ -452,7 +451,7 @@ public class AbstractAcmeClientTest {
public void testRequestCertificate() throws AcmeException, IOException { public void testRequestCertificate() throws AcmeException, IOException {
Connection connection = new DummyConnection() { Connection connection = new DummyConnection() {
@Override @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(uri, is(resourceUri));
assertThat(claims.toString(), sameJSONAs(getJson("requestCertificateRequest"))); assertThat(claims.toString(), sameJSONAs(getJson("requestCertificateRequest")));
assertThat(session, is(notNullValue())); assertThat(session, is(notNullValue()));
@ -461,7 +460,7 @@ public class AbstractAcmeClientTest {
} }
@Override @Override
public URI getLocation() throws AcmeException { public URI getLocation() {
return locationUri; return locationUri;
} }
}; };
@ -484,13 +483,13 @@ public class AbstractAcmeClientTest {
Connection connection = new DummyConnection() { Connection connection = new DummyConnection() {
@Override @Override
public int sendRequest(URI uri) throws AcmeException { public int sendRequest(URI uri) {
assertThat(uri, is(locationUri)); assertThat(uri, is(locationUri));
return HttpURLConnection.HTTP_OK; return HttpURLConnection.HTTP_OK;
} }
@Override @Override
public X509Certificate readCertificate() throws AcmeException { public X509Certificate readCertificate() {
return originalCert; return originalCert;
} }
}; };
@ -510,7 +509,7 @@ public class AbstractAcmeClientTest {
Connection connection = new DummyConnection() { Connection connection = new DummyConnection() {
@Override @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(uri, is(resourceUri));
assertThat(claims.toString(), sameJSONAs(getJson("revokeCertificateRequest"))); assertThat(claims.toString(), sameJSONAs(getJson("revokeCertificateRequest")));
assertThat(session, is(notNullValue())); assertThat(session, is(notNullValue()));
@ -566,7 +565,7 @@ public class AbstractAcmeClientTest {
} }
@Override @Override
protected URI resourceUri(Resource resource) throws AcmeException { protected URI resourceUri(Resource resource) {
if (resourceMap.isEmpty()) { if (resourceMap.isEmpty()) {
fail("Unexpected invocation of resourceUri()"); 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.Resource;
import org.shredzone.acme4j.connector.Session; import org.shredzone.acme4j.connector.Session;
import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.exception.AcmeProtocolException;
import org.shredzone.acme4j.exception.AcmeServerException; import org.shredzone.acme4j.exception.AcmeServerException;
import org.shredzone.acme4j.util.ClaimBuilder; import org.shredzone.acme4j.util.ClaimBuilder;
import org.shredzone.acme4j.util.TestUtils; import org.shredzone.acme4j.util.TestUtils;
@ -91,7 +92,7 @@ public class DefaultConnectionTest {
* {@code Replay-Nonce} header correctly. * {@code Replay-Nonce} header correctly.
*/ */
@Test @Test
public void testGetNonceFromHeader() throws AcmeException { public void testGetNonceFromHeader() {
byte[] nonce = "foo-nonce-foo".getBytes(); byte[] nonce = "foo-nonce-foo".getBytes();
when(mockUrlConnection.getHeaderField("Replay-Nonce")) when(mockUrlConnection.getHeaderField("Replay-Nonce"))
@ -113,7 +114,7 @@ public class DefaultConnectionTest {
* {@code Replay-Nonce} header. * {@code Replay-Nonce} header.
*/ */
@Test @Test
public void testInvalidNonceFromHeader() throws AcmeException { public void testInvalidNonceFromHeader() {
String badNonce = "#$%&/*+*#'"; String badNonce = "#$%&/*+*#'";
when(mockUrlConnection.getHeaderField("Replay-Nonce")).thenReturn(badNonce); when(mockUrlConnection.getHeaderField("Replay-Nonce")).thenReturn(badNonce);
@ -123,7 +124,7 @@ public class DefaultConnectionTest {
conn.conn = mockUrlConnection; conn.conn = mockUrlConnection;
conn.updateSession(session); conn.updateSession(session);
fail("Expected to fail"); fail("Expected to fail");
} catch (AcmeException ex) { } catch (AcmeProtocolException ex) {
assertThat(ex.getMessage(), org.hamcrest.Matchers.startsWith("Invalid replay nonce")); assertThat(ex.getMessage(), org.hamcrest.Matchers.startsWith("Invalid replay nonce"));
} }
@ -227,7 +228,7 @@ public class DefaultConnectionTest {
try (DefaultConnection conn = new DefaultConnection(mockHttpConnection) { try (DefaultConnection conn = new DefaultConnection(mockHttpConnection) {
@Override @Override
public Map<String,Object> readJsonResponse() throws AcmeException { public Map<String,Object> readJsonResponse() {
Map<String, Object> result = new HashMap<String, Object>(); Map<String, Object> result = new HashMap<String, Object>();
result.put("type", "urn:zombie:error:apocalypse"); result.put("type", "urn:zombie:error:apocalypse");
result.put("detail", "Zombie apocalypse in progress"); result.put("detail", "Zombie apocalypse in progress");
@ -241,7 +242,7 @@ public class DefaultConnectionTest {
assertThat(ex.getType(), is("urn:zombie:error:apocalypse")); assertThat(ex.getType(), is("urn:zombie:error:apocalypse"));
assertThat(ex.getMessage(), is("Zombie apocalypse in progress")); assertThat(ex.getMessage(), is("Zombie apocalypse in progress"));
assertThat(ex.getAcmeErrorType(), is(nullValue())); assertThat(ex.getAcmeErrorType(), is(nullValue()));
} catch (AcmeException ex) { } catch (AcmeException | IOException ex) {
fail("Expected an AcmeServerException"); fail("Expected an AcmeServerException");
} }
@ -259,7 +260,7 @@ public class DefaultConnectionTest {
try (DefaultConnection conn = new DefaultConnection(mockHttpConnection) { try (DefaultConnection conn = new DefaultConnection(mockHttpConnection) {
@Override @Override
public Map<String,Object> readJsonResponse() throws AcmeException { public Map<String,Object> readJsonResponse() {
return new HashMap<String, Object>(); return new HashMap<String, Object>();
}; };
}) { }) {
@ -268,6 +269,8 @@ public class DefaultConnectionTest {
fail("Expected to fail"); fail("Expected to fail");
} catch (AcmeException ex) { } catch (AcmeException ex) {
assertThat(ex.getMessage(), not(isEmptyOrNullString())); assertThat(ex.getMessage(), not(isEmptyOrNullString()));
} catch (IOException ex) {
fail("Expected an AcmeException");
} }
verify(mockUrlConnection).getHeaderField("Content-Type"); verify(mockUrlConnection).getHeaderField("Content-Type");
@ -305,7 +308,7 @@ public class DefaultConnectionTest {
try (DefaultConnection conn = new DefaultConnection(mockHttpConnection) { try (DefaultConnection conn = new DefaultConnection(mockHttpConnection) {
@Override @Override
public void updateSession(Session session) throws AcmeException { public void updateSession(Session session) {
assertThat(session, is(sameInstance(testSession))); assertThat(session, is(sameInstance(testSession)));
if (session.getNonce() == null) { if (session.getNonce() == null) {
session.setNonce(nonce1); session.setNonce(nonce1);

View File

@ -33,42 +33,42 @@ import org.shredzone.acme4j.util.ClaimBuilder;
public class DummyConnection implements Connection { public class DummyConnection implements Connection {
@Override @Override
public int sendRequest(URI uri) throws AcmeException { public int sendRequest(URI uri) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override @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(); throw new UnsupportedOperationException();
} }
@Override @Override
public Map<String, Object> readJsonResponse() throws AcmeException { public Map<String, Object> readJsonResponse() {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override @Override
public X509Certificate readCertificate() throws AcmeException { public X509Certificate readCertificate() {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override @Override
public Map<Resource, URI> readDirectory() throws AcmeException { public Map<Resource, URI> readDirectory() {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override @Override
public void updateSession(Session session) throws AcmeException { public void updateSession(Session session) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override @Override
public URI getLocation() throws AcmeException { public URI getLocation() {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override @Override
public URI getLink(String relation) throws AcmeException { public URI getLink(String relation) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }

View File

@ -17,6 +17,7 @@ import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.Date; import java.util.Date;
@ -88,7 +89,7 @@ public class GenericAcmeClientTest {
* Test that the directory is properly read and cached. * Test that the directory is properly read and cached.
*/ */
@Test @Test
public void testResourceUri() throws AcmeException, URISyntaxException { public void testResourceUri() throws AcmeException, IOException, URISyntaxException {
Map<Resource, URI> directoryMap = new HashMap<Resource, URI>(); 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_AUTHZ, new URI("http://example.com/acme/new-authz"));
directoryMap.put(Resource.NEW_CERT, new URI("http://example.com/acme/new-cert")); directoryMap.put(Resource.NEW_CERT, new URI("http://example.com/acme/new-cert"));