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 @@
+
-