diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/OutOfBand01Challenge.java b/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/OutOfBand01Challenge.java new file mode 100644 index 00000000..23c89a2e --- /dev/null +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/OutOfBand01Challenge.java @@ -0,0 +1,46 @@ +/* + * 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.challenge; + +import java.net.MalformedURLException; +import java.net.URL; + +import org.shredzone.acme4j.exception.AcmeProtocolException; + +/** + * Implements the {@value TYPE} challenge. + * + * @author Richard "Shred" Körber + */ +public class OutOfBand01Challenge extends GenericChallenge { + private static final long serialVersionUID = -7459595198486630582L; + + /** + * Challenge type name: {@value} + */ + public static final String TYPE = "oob-01"; + + /** + * Returns the validation URL to be visited by the customer in order to complete the + * challenge. + */ + public URL getValidationUrl() { + try { + return new URL((String) get("url")); + } catch (MalformedURLException ex) { + throw new AcmeProtocolException("Invalid validation URL", ex); + } + } + +} diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/provider/AbstractAcmeClientProvider.java b/acme4j-client/src/main/java/org/shredzone/acme4j/provider/AbstractAcmeClientProvider.java index 1f135ee0..4b2d70a0 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/provider/AbstractAcmeClientProvider.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/provider/AbstractAcmeClientProvider.java @@ -19,6 +19,7 @@ import org.shredzone.acme4j.AcmeClient; import org.shredzone.acme4j.challenge.Challenge; import org.shredzone.acme4j.challenge.Dns01Challenge; import org.shredzone.acme4j.challenge.Http01Challenge; +import org.shredzone.acme4j.challenge.OutOfBand01Challenge; import org.shredzone.acme4j.challenge.TlsSni01Challenge; import org.shredzone.acme4j.challenge.TlsSni02Challenge; import org.shredzone.acme4j.connector.Connection; @@ -73,6 +74,7 @@ public abstract class AbstractAcmeClientProvider implements AcmeClientProvider { case TlsSni01Challenge.TYPE: return new TlsSni01Challenge(); case TlsSni02Challenge.TYPE: return new TlsSni02Challenge(); case Http01Challenge.TYPE: return new Http01Challenge(); + case OutOfBand01Challenge.TYPE: return new OutOfBand01Challenge(); default: return null; } } diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/OutOfBandChallengeTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/OutOfBandChallengeTest.java new file mode 100644 index 00000000..0fed1c9b --- /dev/null +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/OutOfBandChallengeTest.java @@ -0,0 +1,54 @@ +/* + * 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.challenge; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs; + +import java.io.IOException; +import java.net.URL; + +import org.junit.Test; +import org.shredzone.acme4j.Status; +import org.shredzone.acme4j.util.ClaimBuilder; +import org.shredzone.acme4j.util.TestUtils; + +/** + * Unit tests for {@link OutOfBand01Challenge}. + * + * @author Richard "Shred" Körber + */ +public class OutOfBandChallengeTest { + + /** + * Test that {@link OutOfBand01Challenge} is returning the validation URL. + */ + @Test + public void testHttpChallenge() throws IOException { + OutOfBand01Challenge challenge = new OutOfBand01Challenge(); + challenge.unmarshall(TestUtils.getJsonAsMap("oobChallenge")); + + assertThat(challenge.getType(), is(OutOfBand01Challenge.TYPE)); + assertThat(challenge.getStatus(), is(Status.PENDING)); + assertThat(challenge.getValidationUrl(), + is(new URL("https://example.com/validate/evaGxfADs6pSRb2LAv9IZ"))); + + ClaimBuilder cb = new ClaimBuilder(); + challenge.respond(cb); + + assertThat(cb.toString(), sameJSONAs("{\"type\": \"oob-01\"}")); + } + +} diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/provider/AbstractAcmeClientProviderTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/provider/AbstractAcmeClientProviderTest.java index c4b56422..27a9c5c9 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/provider/AbstractAcmeClientProviderTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/provider/AbstractAcmeClientProviderTest.java @@ -24,6 +24,7 @@ import org.shredzone.acme4j.AcmeClient; import org.shredzone.acme4j.challenge.Challenge; import org.shredzone.acme4j.challenge.Dns01Challenge; import org.shredzone.acme4j.challenge.Http01Challenge; +import org.shredzone.acme4j.challenge.OutOfBand01Challenge; import org.shredzone.acme4j.challenge.TlsSni02Challenge; /** @@ -111,6 +112,10 @@ public class AbstractAcmeClientProviderTest { Challenge c6 = provider.createChallenge("foobar-01"); assertThat(c6, is(nullValue())); + Challenge c7 = provider.createChallenge(OutOfBand01Challenge.TYPE); + assertThat(c7, not(nullValue())); + assertThat(c7, instanceOf(OutOfBand01Challenge.class)); + try { provider.createChallenge(null); fail("null was accepted"); diff --git a/acme4j-client/src/test/resources/json.properties b/acme4j-client/src/test/resources/json.properties index d9d949ef..1dbfd721 100644 --- a/acme4j-client/src/test/resources/json.properties +++ b/acme4j-client/src/test/resources/json.properties @@ -164,4 +164,10 @@ tlsSni02Challenge = \ "token": "VNLBdSiZ3LppU2CRG8bilqlwq4DuApJMg3ZJowU6JhQ" \ } +oobChallenge = \ + { \ + "type": "oob-01", \ + "url": "https://example.com/validate/evaGxfADs6pSRb2LAv9IZ" \ + } + # \ No newline at end of file diff --git a/src/site/markdown/challenge/index.md b/src/site/markdown/challenge/index.md index ef22a8dc..cba4c31d 100644 --- a/src/site/markdown/challenge/index.md +++ b/src/site/markdown/challenge/index.md @@ -12,3 +12,4 @@ The ACME specifications define these standard challenges: * [dns-01](./dns-01.html) * [tls-sni-01](./tls-sni-01.html) * [tls-sni-02](./tls-sni-02.html) +* [oob-01](./oob-01.html) diff --git a/src/site/markdown/challenge/oob-01.md b/src/site/markdown/challenge/oob-01.md new file mode 100644 index 00000000..e1f6e25b --- /dev/null +++ b/src/site/markdown/challenge/oob-01.md @@ -0,0 +1,9 @@ +# oob-01 Challenge + +The `oob-01` challenge is an "out of band" challenge that is used when there is no automatic way of validating ownership of a domain. The client is instead required to perform actions outside of the ACME protocol. + +`OutOfBand01Challenge` implements this challenge. Its `getValidationUrl()` method returns a URL that refers to a web page with further instructions about the actions to be taken. + +The challenge must be triggered via `AcmeClient.triggerChallenge()` before the URL is opened in a browser. + +Due to the nature of this challenge, it may take a considerable amount of time until its state changes to `VALID`. diff --git a/src/site/site.xml b/src/site/site.xml index b45f474c..fc5b8263 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -40,6 +40,7 @@ +