mirror of https://github.com/shred/acme4j
Remove deprecated tls-sni-01 challenge
parent
4312331fd9
commit
76ccb4587c
|
@ -1,68 +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.shredzone.acme4j.util.AcmeUtils.*;
|
|
||||||
|
|
||||||
import org.shredzone.acme4j.Session;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implements the {@value TYPE} challenge.
|
|
||||||
*
|
|
||||||
* @deprecated Use {@link TlsSni02Challenge} if supported by the CA. This challenge will
|
|
||||||
* be removed when Let's Encrypt removes support for
|
|
||||||
* {@link TlsSni01Challenge}.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public class TlsSni01Challenge extends TokenChallenge {
|
|
||||||
private static final long serialVersionUID = 7370329525205430573L;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Challenge type name: {@value}
|
|
||||||
*/
|
|
||||||
public static final String TYPE = "tls-sni-01";
|
|
||||||
|
|
||||||
private String subject;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new generic {@link TlsSni01Challenge} object.
|
|
||||||
*
|
|
||||||
* @param session
|
|
||||||
* {@link Session} to bind to.
|
|
||||||
*/
|
|
||||||
public TlsSni01Challenge(Session session) {
|
|
||||||
super(session);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the subject to generate a self-signed certificate for.
|
|
||||||
*/
|
|
||||||
public String getSubject() {
|
|
||||||
return subject;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean acceptable(String type) {
|
|
||||||
return TYPE.equals(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void authorize() {
|
|
||||||
super.authorize();
|
|
||||||
|
|
||||||
String hash = hexEncode(sha256hash(getAuthorization()));
|
|
||||||
subject = hash.substring(0, 32) + '.' + hash.substring(32) + ".acme.invalid";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -62,12 +62,10 @@ public abstract class AbstractAcmeProvider implements AcmeProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("deprecation") // must still provide deprecated challenges
|
|
||||||
private static Map<String, Function<Session, Challenge>> challengeMap() {
|
private static Map<String, Function<Session, Challenge>> challengeMap() {
|
||||||
Map<String, Function<Session, Challenge>> map = new HashMap<>();
|
Map<String, Function<Session, Challenge>> map = new HashMap<>();
|
||||||
|
|
||||||
map.put(Dns01Challenge.TYPE, Dns01Challenge::new);
|
map.put(Dns01Challenge.TYPE, Dns01Challenge::new);
|
||||||
map.put(org.shredzone.acme4j.challenge.TlsSni01Challenge.TYPE, org.shredzone.acme4j.challenge.TlsSni01Challenge::new);
|
|
||||||
map.put(TlsSni02Challenge.TYPE, TlsSni02Challenge::new);
|
map.put(TlsSni02Challenge.TYPE, TlsSni02Challenge::new);
|
||||||
map.put(Http01Challenge.TYPE, Http01Challenge::new);
|
map.put(Http01Challenge.TYPE, Http01Challenge::new);
|
||||||
map.put(OutOfBand01Challenge.TYPE, OutOfBand01Challenge::new);
|
map.put(OutOfBand01Challenge.TYPE, OutOfBand01Challenge::new);
|
||||||
|
|
|
@ -1,64 +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.is;
|
|
||||||
import static org.junit.Assert.assertThat;
|
|
||||||
import static org.shredzone.acme4j.util.TestUtils.getJsonAsObject;
|
|
||||||
import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import org.junit.BeforeClass;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.shredzone.acme4j.Session;
|
|
||||||
import org.shredzone.acme4j.Status;
|
|
||||||
import org.shredzone.acme4j.util.JSONBuilder;
|
|
||||||
import org.shredzone.acme4j.util.TestUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unit tests for {@link TlsSni01Challenge}.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("deprecation") // must test a deprecated challenge
|
|
||||||
public class TlsSni01ChallengeTest {
|
|
||||||
private static final String KEY_AUTHORIZATION =
|
|
||||||
"VNLBdSiZ3LppU2CRG8bilqlwq4DuApJMg3ZJowU6JhQ.HnWjTDnyqlCrm6tZ-6wX-TrEXgRdeNu9G71gqxSO6o0";
|
|
||||||
|
|
||||||
private static Session session;
|
|
||||||
|
|
||||||
@BeforeClass
|
|
||||||
public static void setup() throws IOException {
|
|
||||||
session = TestUtils.session();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test that {@link TlsSni01Challenge} generates a correct authorization key.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testTlsSniChallenge() throws IOException {
|
|
||||||
TlsSni01Challenge challenge = new TlsSni01Challenge(session);
|
|
||||||
challenge.unmarshall(getJsonAsObject("tlsSniChallenge"));
|
|
||||||
|
|
||||||
assertThat(challenge.getType(), is(TlsSni01Challenge.TYPE));
|
|
||||||
assertThat(challenge.getStatus(), is(Status.PENDING));
|
|
||||||
assertThat(challenge.getSubject(), is("14e2350a04434f93c2e0b6012968d99d.ed459b6a7a019d9695609b8514f9d63d.acme.invalid"));
|
|
||||||
|
|
||||||
JSONBuilder cb = new JSONBuilder();
|
|
||||||
challenge.respond(cb);
|
|
||||||
|
|
||||||
assertThat(cb.toString(), sameJSONAs("{\"keyAuthorization\"=\""
|
|
||||||
+ KEY_AUTHORIZATION + "\"}").allowingExtraUnexpectedFields());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -120,7 +120,6 @@ public class AbstractAcmeProviderTest {
|
||||||
* Test that challenges are generated properly.
|
* Test that challenges are generated properly.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
@SuppressWarnings("deprecation") // must test deprecated challenges
|
|
||||||
public void testCreateChallenge() {
|
public void testCreateChallenge() {
|
||||||
Session session = mock(Session.class);
|
Session session = mock(Session.class);
|
||||||
|
|
||||||
|
@ -147,10 +146,6 @@ public class AbstractAcmeProviderTest {
|
||||||
assertThat(c3, not(nullValue()));
|
assertThat(c3, not(nullValue()));
|
||||||
assertThat(c3, instanceOf(Dns01Challenge.class));
|
assertThat(c3, instanceOf(Dns01Challenge.class));
|
||||||
|
|
||||||
Challenge c4 = provider.createChallenge(session, org.shredzone.acme4j.challenge.TlsSni01Challenge.TYPE);
|
|
||||||
assertThat(c4, not(nullValue()));
|
|
||||||
assertThat(c4, instanceOf(org.shredzone.acme4j.challenge.TlsSni01Challenge.class));
|
|
||||||
|
|
||||||
Challenge c5 = provider.createChallenge(session, TlsSni02Challenge.TYPE);
|
Challenge c5 = provider.createChallenge(session, TlsSni02Challenge.TYPE);
|
||||||
assertThat(c5, not(nullValue()));
|
assertThat(c5, not(nullValue()));
|
||||||
assertThat(c5, instanceOf(TlsSni02Challenge.class));
|
assertThat(c5, instanceOf(TlsSni02Challenge.class));
|
||||||
|
|
|
@ -234,13 +234,6 @@ httpNoTokenChallenge = \
|
||||||
"status":"pending" \
|
"status":"pending" \
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsSniChallenge = \
|
|
||||||
{ \
|
|
||||||
"type":"tls-sni-01", \
|
|
||||||
"status":"pending", \
|
|
||||||
"token": "VNLBdSiZ3LppU2CRG8bilqlwq4DuApJMg3ZJowU6JhQ" \
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsSni02Challenge = \
|
tlsSni02Challenge = \
|
||||||
{ \
|
{ \
|
||||||
"type":"tls-sni-02", \
|
"type":"tls-sni-02", \
|
||||||
|
|
|
@ -31,6 +31,7 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
import org.shredzone.acme4j.challenge.Challenge;
|
import org.shredzone.acme4j.challenge.Challenge;
|
||||||
import org.shredzone.acme4j.challenge.Dns01Challenge;
|
import org.shredzone.acme4j.challenge.Dns01Challenge;
|
||||||
import org.shredzone.acme4j.challenge.Http01Challenge;
|
import org.shredzone.acme4j.challenge.Http01Challenge;
|
||||||
|
import org.shredzone.acme4j.challenge.TlsSni02Challenge;
|
||||||
import org.shredzone.acme4j.exception.AcmeConflictException;
|
import org.shredzone.acme4j.exception.AcmeConflictException;
|
||||||
import org.shredzone.acme4j.exception.AcmeException;
|
import org.shredzone.acme4j.exception.AcmeException;
|
||||||
import org.shredzone.acme4j.util.CSRBuilder;
|
import org.shredzone.acme4j.util.CSRBuilder;
|
||||||
|
@ -368,16 +369,16 @@ public class ClientTest {
|
||||||
* Domain name to be authorized
|
* Domain name to be authorized
|
||||||
* @return {@link Challenge} to verify
|
* @return {@link Challenge} to verify
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("deprecation") // until tls-sni-02 is supported
|
|
||||||
public Challenge tlsSniChallenge(Authorization auth, String domain) throws AcmeException {
|
public Challenge tlsSniChallenge(Authorization auth, String domain) throws AcmeException {
|
||||||
// Find a single tls-sni-01 challenge
|
// Find a single tls-sni-02 challenge
|
||||||
org.shredzone.acme4j.challenge.TlsSni01Challenge challenge = auth.findChallenge(org.shredzone.acme4j.challenge.TlsSni01Challenge.TYPE);
|
TlsSni02Challenge challenge = auth.findChallenge(TlsSni02Challenge.TYPE);
|
||||||
if (challenge == null) {
|
if (challenge == null) {
|
||||||
throw new AcmeException("Found no " + org.shredzone.acme4j.challenge.TlsSni01Challenge.TYPE + " challenge, don't know what to do...");
|
throw new AcmeException("Found no " + TlsSni02Challenge.TYPE + " challenge, don't know what to do...");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the Subject
|
// Get the Subject
|
||||||
String subject = challenge.getSubject();
|
String subject = challenge.getSubject();
|
||||||
|
String sanB = challenge.getSanB();
|
||||||
|
|
||||||
// Create a validation key pair
|
// Create a validation key pair
|
||||||
KeyPair domainKeyPair;
|
KeyPair domainKeyPair;
|
||||||
|
@ -390,7 +391,7 @@ public class ClientTest {
|
||||||
|
|
||||||
// Create a validation certificate
|
// Create a validation certificate
|
||||||
try (FileWriter fw = new FileWriter("tlssni.crt")) {
|
try (FileWriter fw = new FileWriter("tlssni.crt")) {
|
||||||
X509Certificate cert = CertificateUtils.createTlsSniCertificate(domainKeyPair, subject);
|
X509Certificate cert = CertificateUtils.createTlsSni02Certificate(domainKeyPair, subject, sanB);
|
||||||
CertificateUtils.writeX509Certificate(cert, fw);
|
CertificateUtils.writeX509Certificate(cert, fw);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
throw new AcmeException("Could not write certificate", ex);
|
throw new AcmeException("Could not write certificate", ex);
|
||||||
|
|
|
@ -167,24 +167,6 @@ public final class CertificateUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a self-signed {@link X509Certificate} that can be used for
|
|
||||||
* {@link org.shredzone.acme4j.challenge.TlsSni01Challenge}. The certificate is valid
|
|
||||||
* for 7 days.
|
|
||||||
*
|
|
||||||
* @param keypair
|
|
||||||
* A domain {@link KeyPair} to be used for the challenge
|
|
||||||
* @param subject
|
|
||||||
* Subject to create a certificate for
|
|
||||||
* @return Created certificate
|
|
||||||
* @deprecated Will be removed when
|
|
||||||
* {@link org.shredzone.acme4j.challenge.TlsSni01Challenge} is removed
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public static X509Certificate createTlsSniCertificate(KeyPair keypair, String subject) throws IOException {
|
|
||||||
return createCertificate(keypair, subject);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a self-signed {@link X509Certificate} that can be used for
|
* Creates a self-signed {@link X509Certificate} that can be used for
|
||||||
* {@link TlsSni02Challenge}. The certificate is valid for 7 days.
|
* {@link TlsSni02Challenge}. The certificate is valid for 7 days.
|
||||||
|
|
|
@ -121,30 +121,6 @@ public class CertificateUtilsTest {
|
||||||
assertThat(countCertificates(out), is(3));
|
assertThat(countCertificates(out), is(3));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Test if {@link CertificateUtils#createTlsSniCertificate(KeyPair, String)} creates a
|
|
||||||
* good certificate.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
@SuppressWarnings("deprecation") // test deprecated method
|
|
||||||
public void testCreateTlsSniCertificate() throws IOException, CertificateParsingException {
|
|
||||||
String subject = "30c452b9bd088cdbc2c4094947025d7c.7364ea602ac325a1b55ceaae024fbe29.acme.invalid";
|
|
||||||
|
|
||||||
KeyPair keypair = KeyPairUtils.createKeyPair(2048);
|
|
||||||
|
|
||||||
X509Certificate cert = CertificateUtils.createTlsSniCertificate(keypair, subject);
|
|
||||||
|
|
||||||
Instant now = Instant.now();
|
|
||||||
Instant end = now.plus(Duration.ofDays(8));
|
|
||||||
|
|
||||||
assertThat(cert, not(nullValue()));
|
|
||||||
assertThat(cert.getNotAfter(), is(greaterThan(Date.from(now))));
|
|
||||||
assertThat(cert.getNotAfter(), is(lessThan(Date.from(end))));
|
|
||||||
assertThat(cert.getNotBefore(), is(lessThanOrEqualTo(Date.from(now))));
|
|
||||||
assertThat(cert.getSubjectX500Principal().getName(), is("CN=acme.invalid"));
|
|
||||||
assertThat(getSANs(cert), containsInAnyOrder(subject));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test if {@link CertificateUtils#createTlsSni02Certificate(KeyPair, String, String)}
|
* Test if {@link CertificateUtils#createTlsSni02Certificate(KeyPair, String, String)}
|
||||||
* creates a good certificate.
|
* creates a good certificate.
|
||||||
|
|
|
@ -10,6 +10,5 @@ The ACME specifications define these standard challenges:
|
||||||
|
|
||||||
* [http-01](./http-01.html)
|
* [http-01](./http-01.html)
|
||||||
* [dns-01](./dns-01.html)
|
* [dns-01](./dns-01.html)
|
||||||
* [tls-sni-01](./tls-sni-01.html)
|
|
||||||
* [tls-sni-02](./tls-sni-02.html)
|
* [tls-sni-02](./tls-sni-02.html)
|
||||||
* [oob-01](./oob-01.html)
|
* [oob-01](./oob-01.html)
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
# tls-sni-01 Challenge
|
|
||||||
|
|
||||||
> **DEPRECATED:** According to the ACME specifications, this challenge will be replaced by [tls-sni-02](./tls-sni-02.html). However, _Let's Encrypt_ does not currently support `tls-sni-02`. For the time being, _acme4j_ supports both challenges. To be on the safe side, request both challenges and process the one that is returned.
|
|
||||||
|
|
||||||
With the `tls-sni-01` challenge, you prove to the CA that you are able to control the web server of the domain to be authorized, by letting it respond to a SNI request with a specific self-signed cert.
|
|
||||||
|
|
||||||
`TlsSni01Challenge` provides a subject:
|
|
||||||
|
|
||||||
```java
|
|
||||||
TlsSni01Challenge challenge = auth.findChallenge(TlsSni01Challenge.TYPE);
|
|
||||||
|
|
||||||
String subject = challenge.getSubject();
|
|
||||||
```
|
|
||||||
|
|
||||||
The `subject` is basically a domain name formed like in this example:
|
|
||||||
|
|
||||||
```
|
|
||||||
30c452b9bd088cdbc2c4094947025d7c.7364ea602ac325a1b55ceaae024fbe29.acme.invalid
|
|
||||||
```
|
|
||||||
|
|
||||||
You need to create a self-signed certificate with the subject set as _Subject Alternative Name_. After that, configure your web server so it will use this certificate on a SNI request to the `subject`.
|
|
||||||
|
|
||||||
The `TlsSni01Challenge` class does not generate a self-signed certificate, as it would require _Bouncy Castle_. However, there is a utility method in the _acme4j-utils_ module for this use case:
|
|
||||||
|
|
||||||
```java
|
|
||||||
KeyPair sniKeyPair = KeyPairUtils.createKeyPair(2048);
|
|
||||||
X509Certificate cert = CertificateUtils.createTlsSniCertificate(sniKeyPair, subject);
|
|
||||||
```
|
|
||||||
|
|
||||||
Now use `cert` and `sniKeyPair` to let your web server respond to a SNI request to `subject`.
|
|
||||||
|
|
||||||
The challenge is completed when the CA was able to send the SNI request and get the correct certificate in return.
|
|
||||||
|
|
||||||
Note that the request is sent to port 443 only. There is no way to choose a different port, for security reasons. This is a limitation of the ACME protocol, not of _acme4j_.
|
|
||||||
|
|
||||||
This shell command line may be helpful to test your web server configuration:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
echo QUIT | \
|
|
||||||
openssl s_client -servername $subject -connect $server_ip:443 | \
|
|
||||||
openssl x509 -text -noout
|
|
||||||
```
|
|
||||||
|
|
||||||
It should return a certificate with `subject` set as `X509v3 Subject Alternative Name`.
|
|
|
@ -1,7 +1,5 @@
|
||||||
# tls-sni-02 Challenge
|
# tls-sni-02 Challenge
|
||||||
|
|
||||||
> **NOTE:** According to the ACME specifications, this challenge will replace [tls-sni-01](./tls-sni-01.html). However, _Let's Encrypt_ does not currently support `tls-sni-02`. For the time being, _acme4j_ supports both challenges. To be on the safe side, request both challenges and process the one that is returned.
|
|
||||||
|
|
||||||
With the `tls-sni-02` challenge, you prove to the CA that you are able to control the web server of the domain to be authorized, by letting it respond to a SNI request with a specific self-signed cert.
|
With the `tls-sni-02` challenge, you prove to the CA that you are able to control the web server of the domain to be authorized, by letting it respond to a SNI request with a specific self-signed cert.
|
||||||
|
|
||||||
`TlsSni02Challenge` provides a subject and a key-authorization domain:
|
`TlsSni02Challenge` provides a subject and a key-authorization domain:
|
||||||
|
|
|
@ -39,7 +39,6 @@
|
||||||
<item name="Challenges" href="challenge/index.html">
|
<item name="Challenges" href="challenge/index.html">
|
||||||
<item name="http-01" href="challenge/http-01.html"/>
|
<item name="http-01" href="challenge/http-01.html"/>
|
||||||
<item name="dns-01" href="challenge/dns-01.html"/>
|
<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="tls-sni-02" href="challenge/tls-sni-02.html"/>
|
||||||
<item name="oob-01" href="challenge/oob-01.html"/>
|
<item name="oob-01" href="challenge/oob-01.html"/>
|
||||||
</item>
|
</item>
|
||||||
|
|
Loading…
Reference in New Issue