Add pre-authorization support

pull/55/head
Richard Körber 2017-05-02 18:09:01 +02:00
parent 4e1ad652b0
commit 7d83ef0e80
3 changed files with 77 additions and 12 deletions

View File

@ -37,6 +37,7 @@ import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.exception.AcmeLazyLoadingException; import org.shredzone.acme4j.exception.AcmeLazyLoadingException;
import org.shredzone.acme4j.exception.AcmeProtocolException; import org.shredzone.acme4j.exception.AcmeProtocolException;
import org.shredzone.acme4j.exception.AcmeRetryAfterException; import org.shredzone.acme4j.exception.AcmeRetryAfterException;
import org.shredzone.acme4j.exception.AcmeServerException;
import org.shredzone.acme4j.provider.pebble.Pebble; import org.shredzone.acme4j.provider.pebble.Pebble;
import org.shredzone.acme4j.util.JSON; import org.shredzone.acme4j.util.JSON;
import org.shredzone.acme4j.util.JSONBuilder; 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.
* <p> * <p>
* IDN domain names will be ACE encoded automatically. * Some servers may not allow pre-authorization.
* *
* @param domain * @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 * @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"); Objects.requireNonNull(domain, "domain");
if (domain.isEmpty()) { if (domain.isEmpty()) {
throw new IllegalArgumentException("domain must not be empty"); 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()) { try (Connection conn = getSession().provider().connect()) {
JSONBuilder claims = new JSONBuilder(); JSONBuilder claims = new JSONBuilder();
claims.object("identifier") claims.object("identifier")
.put("type", "dns") .put("type", "dns")
.put("value", toAce(domain)); .put("value", toAce(domain));
conn.sendSignedRequest(getSession().resourceUrl(Resource.NEW_AUTHZ), claims, getSession()); conn.sendSignedRequest(newAuthzUrl, claims, getSession());
conn.accept(HttpURLConnection.HTTP_CREATED); conn.accept(HttpURLConnection.HTTP_CREATED);
JSON json = conn.readJsonResponse(); JSON json = conn.readJsonResponse();

View File

@ -41,6 +41,7 @@ import org.shredzone.acme4j.challenge.Dns01Challenge;
import org.shredzone.acme4j.challenge.Http01Challenge; import org.shredzone.acme4j.challenge.Http01Challenge;
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.AcmeServerException;
import org.shredzone.acme4j.provider.AcmeProvider; import org.shredzone.acme4j.provider.AcmeProvider;
import org.shredzone.acme4j.provider.TestableConnectionProvider; import org.shredzone.acme4j.provider.TestableConnectionProvider;
import org.shredzone.acme4j.util.JSON; 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 @Test
public void testAuthorizeDomain() throws Exception { public void testPreAuthorizeDomain() throws Exception {
TestableConnectionProvider provider = new TestableConnectionProvider() { TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override @Override
public void sendSignedRequest(URL url, JSONBuilder claims, Session session) { public void sendSignedRequest(URL url, JSONBuilder claims, Session session) {
@ -292,7 +293,7 @@ public class RegistrationTest {
String domainName = "example.org"; String domainName = "example.org";
Registration registration = new Registration(session, locationUrl); Registration registration = new Registration(session, locationUrl);
Authorization auth = registration.authorizeDomain(domainName); Authorization auth = registration.preAuthorizeDomain(domainName);
assertThat(auth.getDomain(), is(domainName)); assertThat(auth.getDomain(), is(domainName));
assertThat(auth.getStatus(), is(Status.PENDING)); assertThat(auth.getStatus(), is(Status.PENDING));
@ -305,29 +306,80 @@ public class RegistrationTest {
provider.close(); 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 that a bad domain parameter is not accepted.
*/ */
@Test @Test
public void testAuthorizeBadDomain() throws Exception { public void testAuthorizeBadDomain() throws Exception {
TestableConnectionProvider provider = new TestableConnectionProvider(); 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(); Session session = provider.createSession();
Registration registration = Registration.bind(session, locationUrl); Registration registration = Registration.bind(session, locationUrl);
try { try {
registration.authorizeDomain(null); registration.preAuthorizeDomain(null);
fail("null domain was accepted"); fail("null domain was accepted");
} catch (NullPointerException ex) { } catch (NullPointerException ex) {
// expected // expected
} }
try { try {
registration.authorizeDomain(""); registration.preAuthorizeDomain("");
fail("empty domain string was accepted"); fail("empty domain string was accepted");
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
// expected // 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(); provider.close();
} }

View File

@ -209,7 +209,7 @@ public class ClientTest {
*/ */
private void authorize(Registration reg, String domain) throws AcmeException { private void authorize(Registration reg, String domain) throws AcmeException {
// Authorize the domain. // Authorize the domain.
Authorization auth = reg.authorizeDomain(domain); Authorization auth = reg.preAuthorizeDomain(domain);
LOG.info("Authorization for domain " + domain); LOG.info("Authorization for domain " + domain);
// Find the desired challenge and prepare it. // Find the desired challenge and prepare it.