diff --git a/README.md b/README.md index 6617578b..f9769a75 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ It is an independent open source implementation that is not affiliated with or e * Fully supports the ACME v2 protocol up to [draft 13](https://tools.ietf.org/html/draft-ietf-acme-acme-13) * Supports all ACME challenges and the `tls-alpn-01` challenge +* Supports the [acme-ip draft](https://tools.ietf.org/html/draft-ietf-acme-ip) * Easy to use Java API * Requires JRE 8 (update 101) or higher * Built with maven, packages available at [Maven Central](http://search.maven.org/#search|ga|1|g%3A%22org.shredzone.acme4j%22) diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/Identifier.java b/acme4j-client/src/main/java/org/shredzone/acme4j/Identifier.java index 3a0b387a..16e7d479 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/Identifier.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/Identifier.java @@ -17,6 +17,8 @@ import static java.util.Objects.requireNonNull; import static org.shredzone.acme4j.toolbox.AcmeUtils.toAce; import java.io.Serializable; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.util.Map; import javax.annotation.ParametersAreNonnullByDefault; @@ -30,6 +32,7 @@ import org.shredzone.acme4j.toolbox.JSONBuilder; * Represents an identifier. *

* The ACME protocol only defines the DNS identifier, which identifies a domain name. + * acme4j also supports IP identifiers. *

* CAs may define further, proprietary identifier types. * @@ -45,6 +48,13 @@ public class Identifier implements Serializable { */ public static final String DNS = "dns"; + /** + * Type constant for IP identifiers. + * + * @see draft-ietf-acme-ip + */ + public static final String IP = "ip"; + private static final String KEY_TYPE = "type"; private static final String KEY_VALUE = "value"; @@ -62,6 +72,17 @@ public class Identifier implements Serializable { return new Identifier(DNS, toAce(domain)); } + /** + * Creates a new IP identifier for the given {@link InetAddress}. + * + * @param ip + * {@link InetAddress} + * @return New {@link Identifier} + */ + public static Identifier ip(InetAddress ip) { + return new Identifier(IP, ip.getHostAddress()); + } + /** * Creates a new {@link Identifier}. *

@@ -119,6 +140,24 @@ public class Identifier implements Serializable { return value; } + /** + * Returns the IP address if this is an IP identifier. + * + * @return {@link InetAddress} + * @throws AcmeProtocolException + * if this is not a DNS identifier. + */ + public InetAddress getIP() { + if (!IP.equals(type)) { + throw new AcmeProtocolException("expected 'ip' identifier, but found '" + type + "'"); + } + try { + return InetAddress.getByName(value); + } catch (UnknownHostException ex) { + throw new AcmeProtocolException("bad ip identifier value", ex); + } + } + /** * Returns the identifier as JSON map. */ diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/IdentifierTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/IdentifierTest.java index 4a560ac5..3aa90981 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/IdentifierTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/IdentifierTest.java @@ -16,6 +16,8 @@ package org.shredzone.acme4j; import static org.hamcrest.Matchers.is; import static org.junit.Assert.*; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.util.Map; import org.junit.Test; @@ -30,6 +32,7 @@ public class IdentifierTest { @Test public void testConstants() { assertThat(Identifier.DNS, is("dns")); + assertThat(Identifier.IP, is("ip")); } @Test @@ -74,6 +77,24 @@ public class IdentifierTest { new Identifier("foo", "example.com").getDomain(); } + @Test + public void testIp() throws UnknownHostException { + Identifier id1 = Identifier.ip(InetAddress.getByName("192.168.1.2")); + assertThat(id1.getType(), is(Identifier.IP)); + assertThat(id1.getValue(), is("192.168.1.2")); + assertThat(id1.getIP().getHostAddress(), is("192.168.1.2")); + + Identifier id2 = Identifier.ip(InetAddress.getByName("2001:db8:85a3::8a2e:370:7334")); + assertThat(id2.getType(), is(Identifier.IP)); + assertThat(id2.getValue(), is("2001:db8:85a3:0:0:8a2e:370:7334")); + assertThat(id2.getIP().getHostAddress(), is("2001:db8:85a3:0:0:8a2e:370:7334")); + } + + @Test(expected = AcmeProtocolException.class) + public void testNoIp() { + new Identifier("foo", "example.com").getIP(); + } + @Test public void testEquals() { Identifier idRef = new Identifier("foo", "123.456"); diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/OrderBuilderTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/OrderBuilderTest.java index d3758eb7..cab33d25 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/OrderBuilderTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/OrderBuilderTest.java @@ -20,6 +20,7 @@ import static org.shredzone.acme4j.toolbox.TestUtils.*; import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs; import java.net.HttpURLConnection; +import java.net.InetAddress; import java.net.URL; import java.time.Instant; import java.util.Arrays; @@ -80,7 +81,7 @@ public class OrderBuilderTest { .identifier(Identifier.dns("d.example.com")) .identifiers(Arrays.asList( Identifier.dns("d2.example.com"), - new Identifier("ip", "192.168.1.2"))) + Identifier.ip(InetAddress.getByName("192.168.1.2")))) .notBefore(notBefore) .notAfter(notAfter) .create(); @@ -99,7 +100,7 @@ public class OrderBuilderTest { Identifier.dns("m.example.org"), Identifier.dns("d.example.com"), Identifier.dns("d2.example.com"), - new Identifier("ip", "192.168.1.2"))); + Identifier.ip(InetAddress.getByName("192.168.1.2")))); assertThat(order.getNotBefore(), is(parseTimestamp("2016-01-01T00:10:00Z"))); assertThat(order.getNotAfter(), is(parseTimestamp("2016-01-08T00:10:00Z"))); assertThat(order.getExpires(), is(parseTimestamp("2016-01-10T00:00:00Z")));