diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/Registration.java b/acme4j-client/src/main/java/org/shredzone/acme4j/Registration.java index 67ef8c2a..4f214407 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/Registration.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/Registration.java @@ -37,6 +37,7 @@ import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.exception.AcmeLazyLoadingException; import org.shredzone.acme4j.exception.AcmeProtocolException; import org.shredzone.acme4j.exception.AcmeRetryAfterException; +import org.shredzone.acme4j.exception.AcmeServerException; import org.shredzone.acme4j.provider.pebble.Pebble; import org.shredzone.acme4j.util.JSON; import org.shredzone.acme4j.util.JSONBuilder; @@ -176,28 +177,40 @@ public class Registration extends AcmeResource { } /** - * Authorizes a domain. The domain is associated with this registration. + * Pre-authorizes a domain. The CA will check if it accepts the domain for + * certification, and returns the necessary challenges. *

- * IDN domain names will be ACE encoded automatically. + * Some servers may not allow pre-authorization. * * @param domain - * Domain name to be authorized + * Domain name to be pre-authorized. IDN names are accepted and will be ACE + * encoded automatically. * @return {@link Authorization} object for this domain + * @throws AcmeException + * if the server does not allow pre-authorization + * @throws AcmeServerException + * if the server allows pre-authorization, but will refuse to issue a + * certificate for this domain */ - public Authorization authorizeDomain(String domain) throws AcmeException { + public Authorization preAuthorizeDomain(String domain) throws AcmeException { Objects.requireNonNull(domain, "domain"); if (domain.isEmpty()) { throw new IllegalArgumentException("domain must not be empty"); } - LOG.debug("authorizeDomain {}", domain); + URL newAuthzUrl = getSession().resourceUrl(Resource.NEW_AUTHZ); + if (newAuthzUrl == null) { + throw new AcmeException("Server does not allow pre-authorization"); + } + + LOG.debug("preAuthorizeDomain {}", domain); try (Connection conn = getSession().provider().connect()) { JSONBuilder claims = new JSONBuilder(); claims.object("identifier") .put("type", "dns") .put("value", toAce(domain)); - conn.sendSignedRequest(getSession().resourceUrl(Resource.NEW_AUTHZ), claims, getSession()); + conn.sendSignedRequest(newAuthzUrl, claims, getSession()); conn.accept(HttpURLConnection.HTTP_CREATED); JSON json = conn.readJsonResponse(); diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/RegistrationTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/RegistrationTest.java index 1bcdf26e..f36fabc8 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/RegistrationTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/RegistrationTest.java @@ -41,6 +41,7 @@ import org.shredzone.acme4j.challenge.Dns01Challenge; import org.shredzone.acme4j.challenge.Http01Challenge; import org.shredzone.acme4j.connector.Resource; import org.shredzone.acme4j.exception.AcmeException; +import org.shredzone.acme4j.exception.AcmeServerException; import org.shredzone.acme4j.provider.AcmeProvider; import org.shredzone.acme4j.provider.TestableConnectionProvider; import org.shredzone.acme4j.util.JSON; @@ -251,10 +252,10 @@ public class RegistrationTest { } /** - * Test that a new {@link Authorization} can be created. + * Test that a domain can be pre-authorized. */ @Test - public void testAuthorizeDomain() throws Exception { + public void testPreAuthorizeDomain() throws Exception { TestableConnectionProvider provider = new TestableConnectionProvider() { @Override public void sendSignedRequest(URL url, JSONBuilder claims, Session session) { @@ -292,7 +293,7 @@ public class RegistrationTest { String domainName = "example.org"; Registration registration = new Registration(session, locationUrl); - Authorization auth = registration.authorizeDomain(domainName); + Authorization auth = registration.preAuthorizeDomain(domainName); assertThat(auth.getDomain(), is(domainName)); assertThat(auth.getStatus(), is(Status.PENDING)); @@ -305,29 +306,80 @@ public class RegistrationTest { provider.close(); } + /** + * Test that a domain pre-authorization can fail. + */ + @Test + public void testNoPreAuthorizeDomain() throws Exception { + URI problemType = URI.create("urn:ietf:params:acme:error:rejectedIdentifier"); + String problemDetail = "example.org is blacklisted"; + + TestableConnectionProvider provider = new TestableConnectionProvider() { + @Override + public void sendSignedRequest(URL url, JSONBuilder claims, Session session) { + assertThat(url, is(resourceUrl)); + assertThat(claims.toString(), sameJSONAs(getJSON("newAuthorizationRequest").toString())); + assertThat(session, is(notNullValue())); + } + + @Override + public int accept(int... httpStatus) throws AcmeException { + Problem problem = TestUtils.createProblem(problemType, problemDetail, resourceUrl); + throw new AcmeServerException(problem); + } + }; + + Session session = provider.createSession(); + + provider.putTestResource(Resource.NEW_AUTHZ, resourceUrl); + + Registration registration = new Registration(session, locationUrl); + + try { + registration.preAuthorizeDomain("example.org"); + fail("preauthorization was accepted"); + } catch (AcmeServerException ex) { + assertThat(ex.getType(), is(problemType)); + assertThat(ex.getMessage(), is(problemDetail)); + } + + provider.close(); + } + /** * Test that a bad domain parameter is not accepted. */ @Test public void testAuthorizeBadDomain() throws Exception { TestableConnectionProvider provider = new TestableConnectionProvider(); + // just provide a resource record so the provider returns a directory + provider.putTestResource(Resource.NEW_NONCE, resourceUrl); + Session session = provider.createSession(); Registration registration = Registration.bind(session, locationUrl); try { - registration.authorizeDomain(null); + registration.preAuthorizeDomain(null); fail("null domain was accepted"); } catch (NullPointerException ex) { // expected } try { - registration.authorizeDomain(""); + registration.preAuthorizeDomain(""); fail("empty domain string was accepted"); } catch (IllegalArgumentException ex) { // expected } + try { + registration.preAuthorizeDomain("example.com"); + fail("preauthorization was accepted"); + } catch (AcmeException ex) { + // expected + assertThat(ex.getMessage(), is("Server does not allow pre-authorization")); + } + provider.close(); } diff --git a/acme4j-example/src/main/java/org/shredzone/acme4j/ClientTest.java b/acme4j-example/src/main/java/org/shredzone/acme4j/ClientTest.java index c2055ea4..18f02699 100644 --- a/acme4j-example/src/main/java/org/shredzone/acme4j/ClientTest.java +++ b/acme4j-example/src/main/java/org/shredzone/acme4j/ClientTest.java @@ -209,7 +209,7 @@ public class ClientTest { */ private void authorize(Registration reg, String domain) throws AcmeException { // Authorize the domain. - Authorization auth = reg.authorizeDomain(domain); + Authorization auth = reg.preAuthorizeDomain(domain); LOG.info("Authorization for domain " + domain); // Find the desired challenge and prepare it.