diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/Account.java b/acme4j-client/src/main/java/org/shredzone/acme4j/Account.java index 1c22e67f..7e89c8f3 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/Account.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/Account.java @@ -14,7 +14,7 @@ package org.shredzone.acme4j; import static java.util.stream.Collectors.toList; -import static org.shredzone.acme4j.toolbox.AcmeUtils.*; +import static org.shredzone.acme4j.toolbox.AcmeUtils.keyAlgorithm; import java.net.URI; import java.net.URL; @@ -160,9 +160,7 @@ public class Account extends AcmeJsonResource { LOG.debug("preAuthorizeDomain {}", domain); try (Connection conn = connect()) { JSONBuilder claims = new JSONBuilder(); - claims.object("identifier") - .put("type", "dns") - .put("value", toAce(domain)); + claims.put("identifier", Identifier.dns(domain).toMap()); conn.sendSignedRequest(newAuthzUrl, claims, getLogin()); diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/Authorization.java b/acme4j-client/src/main/java/org/shredzone/acme4j/Authorization.java index 2d864726..f29383d6 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/Authorization.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/Authorization.java @@ -52,14 +52,25 @@ public class Authorization extends AcmeJsonResource { * For wildcard domain orders, the domain itself (without wildcard prefix) is returned * here. To find out if this {@link Authorization} is related to a wildcard domain * order, check the {@link #isWildcard()} method. + * + * @deprecated Use {@link #getIdentifier()}. */ + @Deprecated public String getDomain() { - JSON jsonIdentifier = getJSON().get("identifier").asObject(); - String type = jsonIdentifier.get("type").asString(); - if (!"dns".equals(type)) { - throw new AcmeProtocolException("Unknown authorization type: " + type); - } - return jsonIdentifier.get("value").asString(); + return getIdentifier().getDomain(); + } + + /** + * Gets the {@link Identifier} to be authorized. + *
+ * For wildcard domain orders, the domain itself (without wildcard prefix) is returned + * here. To find out if this {@link Authorization} is related to a wildcard domain + * order, check the {@link #isWildcard()} method. + * + * @since 2.3 + */ + public Identifier getIdentifier() { + return getJSON().get("identifier").asIdentifier(); } /** diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/Identifier.java b/acme4j-client/src/main/java/org/shredzone/acme4j/Identifier.java new file mode 100644 index 00000000..3a0b387a --- /dev/null +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/Identifier.java @@ -0,0 +1,149 @@ +/* + * acme4j - Java ACME client + * + * Copyright (C) 2018 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; + +import static java.util.Objects.requireNonNull; +import static org.shredzone.acme4j.toolbox.AcmeUtils.toAce; + +import java.io.Serializable; +import java.util.Map; + +import javax.annotation.ParametersAreNonnullByDefault; +import javax.annotation.concurrent.Immutable; + +import org.shredzone.acme4j.exception.AcmeProtocolException; +import org.shredzone.acme4j.toolbox.JSON; +import org.shredzone.acme4j.toolbox.JSONBuilder; + +/** + * Represents an identifier. + *
+ * The ACME protocol only defines the DNS identifier, which identifies a domain name. + *
+ * CAs may define further, proprietary identifier types. + * + * @since 2.3 + */ +@ParametersAreNonnullByDefault +@Immutable +public class Identifier implements Serializable { + private static final long serialVersionUID = -7777851842076362412L; + + /** + * Type constant for DNS identifiers. + */ + public static final String DNS = "dns"; + + private static final String KEY_TYPE = "type"; + private static final String KEY_VALUE = "value"; + + private final String type; + private final String value; + + /** + * Creates a new DNS identifier for the given domain name. + * + * @param domain + * Domain name. Unicode domains are automatically ASCII encoded. + * @return New {@link Identifier} + */ + public static Identifier dns(String domain) { + return new Identifier(DNS, toAce(domain)); + } + + /** + * Creates a new {@link Identifier}. + *
+ * This is a generic constructor for identifiers. Refer to the documentation of your + * CA to find out about the accepted identifier types and values. + *
+ * Note that for DNS identifiers, no ASCII encoding of unicode domain takes place
+ * here. Use {@link #dns(String)} instead.
+ *
+ * @param type
+ * Identifier type
+ * @param value
+ * Identifier value
+ */
+ public Identifier(String type, String value) {
+ this.type = requireNonNull(type, KEY_TYPE);
+ this.value = requireNonNull(value, KEY_VALUE);
+ }
+
+ /**
+ * Creates a new {@link Identifier} from the given {@link JSON} structure.
+ *
+ * @param json
+ * {@link JSON} containing the identifier data
+ */
+ public Identifier(JSON json) {
+ this(json.get(KEY_TYPE).asString(), json.get(KEY_VALUE).asString());
+ }
+
+ /**
+ * Returns the identifier type.
+ */
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * Returns the identifier value.
+ */
+ public String getValue() {
+ return value;
+ }
+
+ /**
+ * Returns the domain name if this is a DNS identifier.
+ *
+ * @return Domain name. Unicode domains are ASCII encoded.
+ * @throws AcmeProtocolException
+ * if this is not a DNS identifier.
+ */
+ public String getDomain() {
+ if (!DNS.equals(type)) {
+ throw new AcmeProtocolException("expected 'dns' identifier, but found '" + type + "'");
+ }
+ return value;
+ }
+
+ /**
+ * Returns the identifier as JSON map.
+ */
+ public Map
diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/toolbox/JSONBuilder.java b/acme4j-client/src/main/java/org/shredzone/acme4j/toolbox/JSONBuilder.java
index 0a470e52..0c323300 100644
--- a/acme4j-client/src/main/java/org/shredzone/acme4j/toolbox/JSONBuilder.java
+++ b/acme4j-client/src/main/java/org/shredzone/acme4j/toolbox/JSONBuilder.java
@@ -19,6 +19,7 @@ import java.security.Key;
import java.security.PublicKey;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
+import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -137,10 +138,10 @@ public class JSONBuilder {
* @param key
* Property key
* @param values
- * Array of property values
+ * Collection of property values
* @return {@code this}
*/
- public JSONBuilder array(String key, Object... values) {
+ public JSONBuilder array(String key, Collection> values) {
data.put(key, values);
return this;
}
diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/AccountTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/AccountTest.java
index 566df158..b9e549e5 100644
--- a/acme4j-client/src/test/java/org/shredzone/acme4j/AccountTest.java
+++ b/acme4j-client/src/test/java/org/shredzone/acme4j/AccountTest.java
@@ -75,7 +75,7 @@ public class AccountTest {
public void sendRequest(URL url, Session session) {
if (url("https://example.com/acme/acct/1/orders").equals(url)) {
jsonResponse = new JSONBuilder()
- .array("orders", "https://example.com/acme/order/1")
+ .array("orders", Arrays.asList("https://example.com/acme/order/1"))
.toJSON();
}
}
@@ -169,6 +169,7 @@ public class AccountTest {
* Test that a domain can be pre-authorized.
*/
@Test
+ @SuppressWarnings("deprecation")
public void testPreAuthorizeDomain() throws Exception {
TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override
@@ -202,6 +203,7 @@ public class AccountTest {
Authorization auth = account.preAuthorizeDomain(domainName);
assertThat(auth.getDomain(), is(domainName));
+ assertThat(auth.getIdentifier().getDomain(), is(domainName));
assertThat(auth.getStatus(), is(Status.PENDING));
assertThat(auth.getExpires(), is(nullValue()));
assertThat(auth.getLocation(), is(locationUrl));
diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/AuthorizationTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/AuthorizationTest.java
index 34114622..57182390 100644
--- a/acme4j-client/src/test/java/org/shredzone/acme4j/AuthorizationTest.java
+++ b/acme4j-client/src/test/java/org/shredzone/acme4j/AuthorizationTest.java
@@ -88,6 +88,7 @@ public class AuthorizationTest {
* Test that authorization is properly updated.
*/
@Test
+ @SuppressWarnings("deprecation")
public void testUpdate() throws Exception {
TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override
@@ -116,6 +117,7 @@ public class AuthorizationTest {
auth.update();
assertThat(auth.getDomain(), is("example.org"));
+ assertThat(auth.getIdentifier().getDomain(), is("example.org"));
assertThat(auth.getStatus(), is(Status.VALID));
assertThat(auth.isWildcard(), is(false));
assertThat(auth.getExpires(), is(parseTimestamp("2016-01-02T17:12:40Z")));
@@ -158,7 +160,7 @@ public class AuthorizationTest {
Authorization auth = new Authorization(login, locationUrl);
auth.update();
- assertThat(auth.getDomain(), is("example.org"));
+ assertThat(auth.getIdentifier().getDomain(), is("example.org"));
assertThat(auth.getStatus(), is(Status.VALID));
assertThat(auth.isWildcard(), is(true));
assertThat(auth.getExpires(), is(parseTimestamp("2016-01-02T17:12:40Z")));
@@ -205,12 +207,12 @@ public class AuthorizationTest {
// Lazy loading
assertThat(requestWasSent.get(), is(false));
- assertThat(auth.getDomain(), is("example.org"));
+ assertThat(auth.getIdentifier().getDomain(), is("example.org"));
assertThat(requestWasSent.get(), is(true));
// Subsequent queries do not trigger another load
requestWasSent.set(false);
- assertThat(auth.getDomain(), is("example.org"));
+ assertThat(auth.getIdentifier().getDomain(), is("example.org"));
assertThat(auth.getStatus(), is(Status.VALID));
assertThat(auth.isWildcard(), is(false));
assertThat(auth.getExpires(), is(parseTimestamp("2016-01-02T17:12:40Z")));
@@ -258,7 +260,7 @@ public class AuthorizationTest {
assertThat(ex.getRetryAfter(), is(retryAfter));
}
- assertThat(auth.getDomain(), is("example.org"));
+ assertThat(auth.getIdentifier().getDomain(), is("example.org"));
assertThat(auth.getStatus(), is(Status.VALID));
assertThat(auth.isWildcard(), is(false));
assertThat(auth.getExpires(), is(parseTimestamp("2016-01-02T17:12:40Z")));
diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/IdentifierTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/IdentifierTest.java
new file mode 100644
index 00000000..753eb538
--- /dev/null
+++ b/acme4j-client/src/test/java/org/shredzone/acme4j/IdentifierTest.java
@@ -0,0 +1,97 @@
+/*
+ * acme4j - Java ACME client
+ *
+ * Copyright (C) 2018 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;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+import java.util.Map;
+
+import org.junit.Test;
+import org.shredzone.acme4j.exception.AcmeProtocolException;
+import org.shredzone.acme4j.toolbox.JSONBuilder;
+
+/**
+ * Unit tests for {@link Identifier}.
+ */
+public class IdentifierTest {
+
+ @Test
+ public void testConstants() {
+ assertThat(Identifier.DNS, is("dns"));
+ }
+
+ @Test
+ public void testGetters() {
+ Identifier id1 = new Identifier("foo", "123.456");
+ assertThat(id1.getType(), is("foo"));
+ assertThat(id1.getValue(), is("123.456"));
+ assertThat(id1.toString(), is("foo=123.456"));
+ Map