Remove proof-of-possession challenge. Closes issue #4.

pull/17/merge
Richard Körber 2016-03-21 22:35:36 +01:00
parent bc8c8f24f0
commit b8bfc5fa0f
12 changed files with 13 additions and 540 deletions

View File

@ -1,142 +0,0 @@
/*
* acme4j - Java ACME client
*
* Copyright (C) 2015 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.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.KeyPair;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.jose4j.base64url.Base64Url;
import org.jose4j.json.JsonUtil;
import org.jose4j.lang.JoseException;
import org.shredzone.acme4j.Registration;
import org.shredzone.acme4j.exception.AcmeProtocolException;
import org.shredzone.acme4j.util.ClaimBuilder;
import org.shredzone.acme4j.util.ValidationBuilder;
/**
* Implements the {@value TYPE} challenge.
*
* @author Richard "Shred" Körber
*/
public class ProofOfPossession01Challenge extends GenericChallenge {
private static final long serialVersionUID = 6212440828380185335L;
protected static final String KEY_CERTS = "certs";
protected static final String KEY_AUTHORIZATION = "authorization";
/**
* Challenge type name: {@value}
*/
public static final String TYPE = "proof-of-possession-01";
private Collection<X509Certificate> certs;
private String validation;
/**
* Gets the collection of {@link X509Certificate} known by the server.
*/
public Collection<X509Certificate> getCertificates() {
return certs;
}
/**
* Authorizes the challenge by signing it with the {@link Registration} of the current
* domain owner.
*
* @param ownerRegistration
* {@link Registration} of the certificate holder
* @param domainKeypair
* {@link KeyPair} matching one of the requested certificates
* @param domains
* Domains to validate
*/
public void authorize(Registration ownerRegistration, KeyPair domainKeypair, String... domains) {
importValidation(new ValidationBuilder()
.domains(domains)
.sign(ownerRegistration, domainKeypair));
}
/**
* Imports a validation JWS.
*
* @param validation
* JWS of the validation
* @see ValidationBuilder
*/
public void importValidation(String validation) {
try {
Map<String, Object> json = JsonUtil.parseJson(validation);
if (!json.keySet().containsAll(Arrays.asList("header", "payload", "signature"))) {
throw new IllegalArgumentException("not a JWS");
}
} catch (JoseException ex) {
throw new IllegalArgumentException("invalid JSON", ex);
}
this.validation = validation;
}
@Override
public void unmarshall(Map<String, Object> map) {
super.unmarshall(map);
List<String> certData = get(KEY_CERTS);
if (certData != null) {
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
certs = new ArrayList<>(certData.size());
for (String c : certData) {
byte[] certDer = Base64Url.decode(c);
try (ByteArrayInputStream in = new ByteArrayInputStream(certDer)) {
certs.add((X509Certificate) certificateFactory.generateCertificate(in));
}
}
} catch (CertificateException | IOException ex) {
throw new AcmeProtocolException("Invalid certificates", ex);
}
}
}
@Override
public void respond(ClaimBuilder cb) {
if (validation == null) {
throw new IllegalStateException("not validated");
}
super.respond(cb);
try {
cb.put(KEY_AUTHORIZATION, JsonUtil.parseJson(validation));
} catch (JoseException ex) {
// should not happen, as the JSON is prevalidated in the setter
throw new AcmeProtocolException("validation: invalid JSON", ex);
}
}
@Override
protected boolean acceptable(String type) {
return TYPE.equals(type);
}
}

View File

@ -19,7 +19,6 @@ 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.ProofOfPossession01Challenge;
import org.shredzone.acme4j.challenge.TlsSni01Challenge;
import org.shredzone.acme4j.challenge.TlsSni02Challenge;
import org.shredzone.acme4j.connector.Connection;
@ -73,7 +72,6 @@ public abstract class AbstractAcmeClientProvider implements AcmeClientProvider {
case Dns01Challenge.TYPE: return new Dns01Challenge();
case TlsSni01Challenge.TYPE: return new TlsSni01Challenge();
case TlsSni02Challenge.TYPE: return new TlsSni02Challenge();
case ProofOfPossession01Challenge.TYPE: return new ProofOfPossession01Challenge();
case Http01Challenge.TYPE: return new Http01Challenge();
default: return null;
}

View File

@ -1,129 +0,0 @@
/*
* acme4j - Java ACME client
*
* Copyright (C) 2015 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.util;
import java.security.KeyPair;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.jose4j.jwk.PublicJsonWebKey;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.lang.JoseException;
import org.shredzone.acme4j.Registration;
import org.shredzone.acme4j.challenge.ProofOfPossession01Challenge;
import org.shredzone.acme4j.exception.AcmeProtocolException;
/**
* Generates a validation string for {@link ProofOfPossession01Challenge}.
*
* @author Richard "Shred" Körber
*/
public class ValidationBuilder {
private final List<Map<String, Object>> identifiers = new ArrayList<>();
/**
* Adds a domain to the validation.
*
* @param domain
* Domain to be added
* @return {@code this}
*/
public ValidationBuilder domain(String domain) {
if (domain == null || domain.isEmpty()) {
throw new IllegalArgumentException("domain must not be empty or null");
}
ClaimBuilder cb = new ClaimBuilder();
cb.put("type", "dns").put("value", domain);
identifiers.add(cb.toMap());
return this;
}
/**
* Adds a collection of domains to the validation.
*
* @param domains
* Domains to be added
* @return {@code this}
*/
public ValidationBuilder domains(Collection<String> domains) {
if (domains == null) {
throw new NullPointerException("domains must not be null");
}
for (String d : domains) {
domain(d);
}
return this;
}
/**
* Adds multiple domains to the validation.
*
* @param domains
* Domains to be added
* @return {@code this}
*/
public ValidationBuilder domains(String... domains) {
return domains(Arrays.asList(domains));
}
/**
* Signs with the given {@link KeyPair} and returns a signed JSON Web Signature
* structure that can be used for validation.
*
* @param registration
* {@link Registration} of the current domain owner
* @param keypair
* One of the {@link KeyPair} requested by the challenge
* @return JWS validation object
*/
public String sign(Registration registration, KeyPair keypair) {
if (registration == null) {
throw new NullPointerException("registration must not be null");
}
if (keypair == null) {
throw new NullPointerException("keypair must not be null");
}
try {
ClaimBuilder claims = new ClaimBuilder();
claims.put("type", ProofOfPossession01Challenge.TYPE);
claims.array("identifiers", identifiers.toArray());
claims.putKey("accountKey", registration.getKeyPair().getPublic());
final PublicJsonWebKey jwk = PublicJsonWebKey.Factory.newPublicJwk(keypair.getPublic());
JsonWebSignature jws = new JsonWebSignature();
jws.setPayload(claims.toString());
jws.getHeaders().setJwkHeaderValue("jwk", jwk);
jws.setAlgorithmHeaderValue(SignatureUtils.keyAlgorithm(jwk));
jws.setKey(keypair.getPrivate());
jws.sign();
ClaimBuilder auth = new ClaimBuilder();
auth.put("header", jws.getHeaders().getFullHeaderAsJsonString());
auth.put("payload", jws.getEncodedPayload());
auth.put("signature", jws.getEncodedSignature());
return auth.toString();
} catch (JoseException ex) {
throw new AcmeProtocolException("Failed to sign", ex);
}
}
}

View File

@ -32,7 +32,6 @@ import org.junit.Test;
import org.shredzone.acme4j.challenge.Challenge;
import org.shredzone.acme4j.challenge.Dns01Challenge;
import org.shredzone.acme4j.challenge.Http01Challenge;
import org.shredzone.acme4j.challenge.ProofOfPossession01Challenge;
import org.shredzone.acme4j.challenge.TlsSni02Challenge;
/**
@ -42,6 +41,8 @@ import org.shredzone.acme4j.challenge.TlsSni02Challenge;
*/
public class AuthorizationTest {
private static final String SNAILMAIL_TYPE = "snail-01"; // a non-existent challenge
private Authorization authorization;
/**
@ -101,8 +102,8 @@ public class AuthorizationTest {
*/
@Test
public void testFindChallenge() {
// ProofOfPossesionChallenge is not available at all
Challenge c1 = authorization.findChallenge(ProofOfPossession01Challenge.TYPE);
// A snail mail challenge is not available at all
Challenge c1 = authorization.findChallenge(SNAILMAIL_TYPE);
assertThat(c1, is(nullValue()));
// HttpChallenge is available as standalone challenge
@ -140,7 +141,7 @@ public class AuthorizationTest {
instanceOf(TlsSni02Challenge.class)));
// Finds smaller combinations as well
Collection<Challenge> c4 = authorization.findCombination(Dns01Challenge.TYPE, TlsSni02Challenge.TYPE, ProofOfPossession01Challenge.TYPE);
Collection<Challenge> c4 = authorization.findCombination(Dns01Challenge.TYPE, TlsSni02Challenge.TYPE, SNAILMAIL_TYPE);
assertThat(c4, hasSize(2));
assertThat(c4, contains(instanceOf(Dns01Challenge.class),
instanceOf(TlsSni02Challenge.class)));
@ -155,7 +156,7 @@ public class AuthorizationTest {
assertThat(c6, is(nullValue()));
// Does not find challenges that have not been provided
Collection<Challenge> c7 = authorization.findCombination(ProofOfPossession01Challenge.TYPE);
Collection<Challenge> c7 = authorization.findCombination(SNAILMAIL_TYPE);
assertThat(c7, is(nullValue()));
}

View File

@ -1,100 +0,0 @@
/*
* acme4j - Java ACME client
*
* Copyright (C) 2015 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.*;
import static org.junit.Assert.*;
import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs;
import java.io.IOException;
import java.security.KeyPair;
import java.security.cert.X509Certificate;
import org.junit.Test;
import org.shredzone.acme4j.Registration;
import org.shredzone.acme4j.Status;
import org.shredzone.acme4j.util.ClaimBuilder;
import org.shredzone.acme4j.util.TestUtils;
import org.shredzone.acme4j.util.ValidationBuilder;
/**
* Unit tests for {@link ProofOfPossession01Challenge}.
*
* @author Richard "Shred" Körber
*/
public class ProofOfPossessionChallengeTest {
/**
* Test that {@link ProofOfPossession01Challenge} generates a correct authorization key.
*/
@Test
public void testProofOfPossessionChallenge() throws IOException {
X509Certificate cert = TestUtils.createCertificate();
KeyPair keypair = TestUtils.createKeyPair();
Registration reg = new Registration(keypair);
KeyPair domainKeyPair = TestUtils.createDomainKeyPair();
ProofOfPossession01Challenge challenge = new ProofOfPossession01Challenge();
challenge.unmarshall(TestUtils.getJsonAsMap("proofOfPossessionChallenge"));
assertThat(challenge.getCertificates(), contains(cert));
assertThat(challenge.getType(), is(ProofOfPossession01Challenge.TYPE));
assertThat(challenge.getStatus(), is(Status.PENDING));
try {
challenge.respond(new ClaimBuilder());
fail("marshall() without previous authorize()");
} catch (IllegalStateException ex) {
// expected
}
challenge.authorize(reg, domainKeyPair, "example.org");
ClaimBuilder cb = new ClaimBuilder();
challenge.respond(cb);
assertThat(cb.toString(), sameJSONAs("{\"type\"=\""
+ ProofOfPossession01Challenge.TYPE + "\",\"authorization\"="
+ new ValidationBuilder().domain("example.org").sign(reg, domainKeyPair)
+ "}"));
}
/**
* Test that {@link ProofOfPossession01Challenge#importValidation(String)} works
* correctly.
*/
@Test
public void testImportValidation() throws IOException {
KeyPair keypair = TestUtils.createKeyPair();
Registration reg = new Registration(keypair);
KeyPair domainKeyPair = TestUtils.createDomainKeyPair();
String validation = new ValidationBuilder()
.domain("example.org")
.sign(reg, domainKeyPair);
ProofOfPossession01Challenge challenge = new ProofOfPossession01Challenge();
challenge.unmarshall(TestUtils.getJsonAsMap("proofOfPossessionChallenge"));
challenge.importValidation(validation);
ClaimBuilder cb = new ClaimBuilder();
challenge.respond(cb);
assertThat(cb.toString(), sameJSONAs("{\"type\"=\""
+ ProofOfPossession01Challenge.TYPE + "\",\"authorization\"=" + validation
+ "}"));
}
}

View File

@ -24,7 +24,6 @@ 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.ProofOfPossession01Challenge;
import org.shredzone.acme4j.challenge.TlsSni02Challenge;
/**
@ -101,20 +100,16 @@ public class AbstractAcmeClientProviderTest {
assertThat(c3, not(nullValue()));
assertThat(c3, instanceOf(Dns01Challenge.class));
Challenge c4 = provider.createChallenge(ProofOfPossession01Challenge.TYPE);
Challenge c4 = provider.createChallenge(org.shredzone.acme4j.challenge.TlsSni01Challenge.TYPE);
assertThat(c4, not(nullValue()));
assertThat(c4, instanceOf(ProofOfPossession01Challenge.class));
assertThat(c4, instanceOf(org.shredzone.acme4j.challenge.TlsSni01Challenge.class));
Challenge c5 = provider.createChallenge(org.shredzone.acme4j.challenge.TlsSni01Challenge.TYPE);
Challenge c5 = provider.createChallenge(TlsSni02Challenge.TYPE);
assertThat(c5, not(nullValue()));
assertThat(c5, instanceOf(org.shredzone.acme4j.challenge.TlsSni01Challenge.class));
assertThat(c5, instanceOf(TlsSni02Challenge.class));
Challenge c6 = provider.createChallenge(TlsSni02Challenge.TYPE);
assertThat(c6, not(nullValue()));
assertThat(c6, instanceOf(TlsSni02Challenge.class));
Challenge c7 = provider.createChallenge("foobar-01");
assertThat(c7, is(nullValue()));
Challenge c6 = provider.createChallenge("foobar-01");
assertThat(c6, is(nullValue()));
try {
provider.createChallenge(null);

View File

@ -1,90 +0,0 @@
/*
* acme4j - Java ACME client
*
* Copyright (C) 2015 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.util;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertThat;
import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs;
import java.io.IOException;
import java.security.KeyPair;
import java.util.Arrays;
import java.util.Map;
import org.jose4j.base64url.Base64Url;
import org.jose4j.json.JsonUtil;
import org.jose4j.lang.JoseException;
import org.junit.Test;
import org.shredzone.acme4j.Registration;
/**
* Unit test for {@link ValidationBuilder}.
*
* @author Richard "Shred" Körber
*/
public class ValidationBuilderTest {
/**
* Test if a correct JWS validation object is generated.
*/
@Test
public void testValidationBuilder() throws IOException, JoseException {
Registration reg = new Registration(TestUtils.createKeyPair());
KeyPair domainKeyPair = TestUtils.createDomainKeyPair();
assertThat(reg.getKeyPair(), not(domainKeyPair));
ValidationBuilder vb = new ValidationBuilder();
vb.domain("abc.de").domain("ef.gh");
vb.domains("ijk.lm", "no.pq", "rst.uv");
vb.domains(Arrays.asList("w.x", "y.z"));
String json = vb.sign(reg, domainKeyPair);
Map<String, Object> data = JsonUtil.parseJson(json);
String header = (String) data.get("header");
String payload = Base64Url.decodeToUtf8String((String) data.get("payload"));
String signature = (String) data.get("signature");
StringBuilder expectedHeader = new StringBuilder();
expectedHeader.append('{');
expectedHeader.append("\"alg\":\"RS256\",");
expectedHeader.append("\"jwk\":{");
expectedHeader.append("\"kty\":\"").append(TestUtils.D_KTY).append("\",");
expectedHeader.append("\"e\":\"").append(TestUtils.D_E).append("\",");
expectedHeader.append("\"n\":\"").append(TestUtils.D_N).append("\"");
expectedHeader.append("}}");
StringBuilder expectedPayload = new StringBuilder();
expectedPayload.append('{');
expectedPayload.append("\"type\":\"proof-of-possession-01\",");
expectedPayload.append("\"identifiers\":[");
for (String d : Arrays.asList("abc.de", "ef.gh", "ijk.lm", "no.pq", "rst.uv", "w.x", "y.z")) {
expectedPayload.append("{\"type\":\"dns\",\"value\":\"").append(d).append("\"}");
if (!"y.z".equals(d)) {
expectedPayload.append(',');
}
}
expectedPayload.append("],\"accountKey\":{");
expectedPayload.append("\"kty\":\"").append(TestUtils.KTY).append("\",");
expectedPayload.append("\"e\":\"").append(TestUtils.E).append("\",");
expectedPayload.append("\"n\":\"").append(TestUtils.N).append("\"");
expectedPayload.append("}}");
assertThat(header, sameJSONAs(expectedHeader.toString()).allowingExtraUnexpectedFields());
assertThat(payload, sameJSONAs(expectedPayload.toString()));
assertThat(signature, not(isEmptyOrNullString()));
}
}

View File

@ -150,12 +150,6 @@ httpChallenge = \
"token": "rSoI9JpyvFi-ltdnBW0W1DjKstzG7cHixjzcOjwzAEQ" \
}
proofOfPossessionChallenge = \
{ \
"type": "proof-of-possession-01", \
"certs": ["MIIDVzCCAj-gAwIBAgIJAM4KDTzb0Y7NMA0GCSqGSIb3DQEBCwUAMEIxCzAJBgNVBAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQgQ29tcGFueSBMdGQwHhcNMTUxMjEwMDAxMTA4WhcNMjUxMjA3MDAxMTA4WjBCMQswCQYDVQQGEwJYWDEVMBMGA1UEBwwMRGVmYXVsdCBDaXR5MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBhbnkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr0g3w4C8xbj_5lzJiDxk0HkEJeZeyruq-0AzOPMigJZ7zxZtX_KUxOIHrQ4qjcFhl0DmQImoM0wESU-kcsjAHCx8E1lgRVlVsMfLAQPHkg5UybqfadzKT3ALcSD-9F9mVIP6liC_6KzLTASmx6zM7j92KTl1ArObZr5mh0jvSNORrMhEC4Byn3-NTxjuHON1rWppCMwpeNNhFzaAig3O8PY8IyaLXNP2Ac5pXn0iW16S-Im9by7751UeW5a7DznmuMEM-WY640ffJDQ4-I64H403uAgvvSu-BGw8SEEZGuBCxoCnG1g6y6OvJyN5TgqFdGosAfm1u-_MP1seoPdpBQIDAQABo1AwTjAdBgNVHQ4EFgQUrie5ZLOrA_HuhW1b_CHjzEvj34swHwYDVR0jBBgwFoAUrie5ZLOrA_HuhW1b_CHjzEvj34swDAYDVR0TBAUwAwEB_zANBgkqhkiG9w0BAQsFAAOCAQEAkSOP0FUgIIUeJTObgXrenHzZpLAkqXi37dgdYuPhNveo3agueP51N7yIoh6YGShiJ73Rvr-lVYTwFXStrLih1Wh3tWvksMxnvocgd7l6USRb5_AgH7eHeFK4DoCAak2hUAcCLDRJN3XMhNLpyJhw7GJxowVIGUlxcW5Asrmh9qflfyMyjripTP3CdHobmNcNHyScjNncKj37m8vomel9acekTtDl2Ci7nLdE-3VqQCXMIfLiF3PO0gGpKei0RuVCSOG6W83zVInCPd_l3aluSR-f_VZlk8KGQ4As4uTQi89j-J1YepzG0ASMZpjVbXeIg5QBAywVxBh5XVTz37KN8A"] \
}
tlsSniChallenge = \
{ \
"type":"tls-sni-01", \

View File

@ -12,4 +12,3 @@ 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)
* [proof-of-possession-01](./proof-of-possession-01.html)

View File

@ -1,52 +0,0 @@
# proof-of-possession-01 Challenge
With the `proof-of-possession-01` challenge, you prove to the CA that you are able to provide a verification document that is signed with a key that is known to the server. The main purpose of this challenge is to transfer the authorization of a domain to your account.
The challenge object contains a list of `X509Certificate`s that are already known to the CA:
```java
ProofOfPossession01Challenge challenge =
auth.findChallenge(ProofOfPossession01Challenge.TYPE);
Collection<X509Certificate> certificates = challenge.getCertificates();
```
In the next step, the _current owner of the domain_ authorizes the challenge, by signing it with a key pair that corresponds to one of the `certificates`:
```java
Registration ownerRegistration = ... // Registration of the domain owner
KeyPair domainKeyPair = ... // Key pair matching a certificate
String domain = ... // Domain to authorize
challenge.authorize(ownerRegistration, domainKeyPair, domain);
```
The challenge is completed when the domain is associated with the account of the `ownerRegistration`, and the `domainKeyPair` matches one of the `certificates`.
## Importing a Validation
A problem with this challenge is that a third party needs to provide the account and domain key pairs for authorization.
There is a way to prepare the validation externally, and import a validation document into the challenge in a separate step. The validation document is signed by the domain owner, but does not contain any private keys.
_acme4j_ offers a `ValidationBuilder` class for generating the validation document:
```java
Registration ownerRegistration = ... // Registration of the domain owner
KeyPair domainKeyPair = ... // Key pair matching a certificates
ValidationBuilder vb = new ValidationBuilder();
vb.domain("example.org");
String json = vb.sign(ownerRegistration, domainKeyPair);
```
This `json` string can be transported (e.g. via email) and then imported into the challenge:
```java
String json = ... // validation document
ProofOfPossession01Challenge challenge =
auth.findChallenge(ProofOfPossession01Challenge.TYPE);
challenge.importValidation(json);
```
The challenge is authorized now, and is ready to be executed.

View File

@ -8,7 +8,7 @@ Individual CAs may offer further ways of recovery, which are not part of this do
## Contact-Based Recovery
> **CAUTION**: Contact-Based Recovery is [currently not supported by _Let's Encrypt_](https://github.com/letsencrypt/boulder/issues/432). If you should lose your key pair, you are stuck. All you can do at the moment is to register a new account and then recover your domains by using the [Proof of Possession](../challenge/proof-of-possession.html) challenge in combination with the domain key pairs.
> **CAUTION**: Contact-Based Recovery is [currently not supported by _Let's Encrypt_](https://github.com/letsencrypt/boulder/issues/432). If you should lose your key pair, you are stuck.
On this recovery method, the CA contacts the account owner via one of the contact addresses given on account creation. The owner is asked to take some action (e.g. clicking on a link in an email). If it was successful, the account data is transferred to the new account.

View File

@ -41,7 +41,6 @@
<item name="dns-01" href="challenge/dns-01.html"/>
<item name="tls-sni-01" href="challenge/tls-sni-01.html"/>
<item name="tls-sni-02" href="challenge/tls-sni-02.html"/>
<item name="proof-of-possession-01" href="challenge/proof-of-possession-01.html"/>
</item>
<item name="CAs" href="ca/index.html">
<item name="Let's Encrypt" href="ca/letsencrypt.html"/>