mirror of https://github.com/shred/acme4j
Remove tls-sni-02 challenge
parent
472f1497db
commit
137c2c7dd0
|
@ -1,67 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.shredzone.acme4j.toolbox.AcmeUtils.*;
|
|
||||||
|
|
||||||
import org.shredzone.acme4j.Session;
|
|
||||||
import org.shredzone.acme4j.toolbox.JSON;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implements the {@value TYPE} challenge.
|
|
||||||
*/
|
|
||||||
public class TlsSni02Challenge extends TokenChallenge {
|
|
||||||
private static final long serialVersionUID = 8921833167878544518L;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Challenge type name: {@value}
|
|
||||||
*/
|
|
||||||
public static final String TYPE = "tls-sni-02";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new generic {@link TlsSni02Challenge} object.
|
|
||||||
*
|
|
||||||
* @param session
|
|
||||||
* {@link Session} to bind to.
|
|
||||||
* @param data
|
|
||||||
* {@link JSON} challenge data
|
|
||||||
*/
|
|
||||||
public TlsSni02Challenge(Session session, JSON data) {
|
|
||||||
super(session, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the subject, which is to be used as "SAN-A" in a self-signed certificate.
|
|
||||||
* The CA will send the SNI request against this domain.
|
|
||||||
*/
|
|
||||||
public String getSubject() {
|
|
||||||
String tokenHash = hexEncode(sha256hash(getToken()));
|
|
||||||
return tokenHash.substring(0, 32) + '.' + tokenHash.substring(32) + ".token.acme.invalid";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the key authorization, which is to be used as "SAN-B" in a self-signed
|
|
||||||
* certificate.
|
|
||||||
*/
|
|
||||||
public String getSanB() {
|
|
||||||
String kaHash = hexEncode(sha256hash(getAuthorization()));
|
|
||||||
return kaHash.substring(0, 32) + '.' + kaHash.substring(32) + ".ka.acme.invalid";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean acceptable(String type) {
|
|
||||||
return TYPE.equals(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -24,7 +24,6 @@ import org.shredzone.acme4j.Session;
|
||||||
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.challenge.TokenChallenge;
|
import org.shredzone.acme4j.challenge.TokenChallenge;
|
||||||
import org.shredzone.acme4j.connector.Connection;
|
import org.shredzone.acme4j.connector.Connection;
|
||||||
import org.shredzone.acme4j.connector.DefaultConnection;
|
import org.shredzone.acme4j.connector.DefaultConnection;
|
||||||
|
@ -64,7 +63,6 @@ public abstract class AbstractAcmeProvider implements AcmeProvider {
|
||||||
Map<String, BiFunction<Session, JSON, Challenge>> map = new HashMap<>();
|
Map<String, BiFunction<Session, JSON, Challenge>> map = new HashMap<>();
|
||||||
|
|
||||||
map.put(Dns01Challenge.TYPE, Dns01Challenge::new);
|
map.put(Dns01Challenge.TYPE, Dns01Challenge::new);
|
||||||
map.put(TlsSni02Challenge.TYPE, TlsSni02Challenge::new);
|
|
||||||
map.put(Http01Challenge.TYPE, Http01Challenge::new);
|
map.put(Http01Challenge.TYPE, Http01Challenge::new);
|
||||||
|
|
||||||
return Collections.unmodifiableMap(map);
|
return Collections.unmodifiableMap(map);
|
||||||
|
|
|
@ -29,7 +29,6 @@ import org.junit.Test;
|
||||||
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.AcmeException;
|
import org.shredzone.acme4j.exception.AcmeException;
|
||||||
import org.shredzone.acme4j.exception.AcmeProtocolException;
|
import org.shredzone.acme4j.exception.AcmeProtocolException;
|
||||||
import org.shredzone.acme4j.exception.AcmeRetryAfterException;
|
import org.shredzone.acme4j.exception.AcmeRetryAfterException;
|
||||||
|
@ -67,11 +66,6 @@ public class AuthorizationTest {
|
||||||
Challenge c3 = authorization.findChallenge(Dns01Challenge.TYPE);
|
Challenge c3 = authorization.findChallenge(Dns01Challenge.TYPE);
|
||||||
assertThat(c3, is(notNullValue()));
|
assertThat(c3, is(notNullValue()));
|
||||||
assertThat(c3, is(instanceOf(Dns01Challenge.class)));
|
assertThat(c3, is(instanceOf(Dns01Challenge.class)));
|
||||||
|
|
||||||
// TlsSni02Challenge is available
|
|
||||||
Challenge c4 = authorization.findChallenge(TlsSni02Challenge.TYPE);
|
|
||||||
assertThat(c4, is(notNullValue()));
|
|
||||||
assertThat(c4, is(instanceOf(TlsSni02Challenge.class)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -265,7 +259,6 @@ public class AuthorizationTest {
|
||||||
|
|
||||||
provider.putTestChallenge(Http01Challenge.TYPE, Http01Challenge::new);
|
provider.putTestChallenge(Http01Challenge.TYPE, Http01Challenge::new);
|
||||||
provider.putTestChallenge(Dns01Challenge.TYPE, Dns01Challenge::new);
|
provider.putTestChallenge(Dns01Challenge.TYPE, Dns01Challenge::new);
|
||||||
provider.putTestChallenge(TlsSni02Challenge.TYPE, TlsSni02Challenge::new);
|
|
||||||
provider.putTestChallenge(DUPLICATE_TYPE, Challenge::new);
|
provider.putTestChallenge(DUPLICATE_TYPE, Challenge::new);
|
||||||
|
|
||||||
Authorization authorization = new Authorization(session, locationUrl);
|
Authorization authorization = new Authorization(session, locationUrl);
|
||||||
|
|
|
@ -1,63 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 org.shredzone.acme4j.toolbox.TestUtils.getJSON;
|
|
||||||
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.toolbox.JSONBuilder;
|
|
||||||
import org.shredzone.acme4j.toolbox.TestUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unit tests for {@link TlsSni02Challenge}.
|
|
||||||
*/
|
|
||||||
public class TlsSni02ChallengeTest {
|
|
||||||
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 TlsSni02Challenge} generates a correct authorization key.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testTlsSni02Challenge() throws IOException {
|
|
||||||
TlsSni02Challenge challenge = new TlsSni02Challenge(session, getJSON("tlsSni02Challenge"));
|
|
||||||
|
|
||||||
assertThat(challenge.getType(), is(TlsSni02Challenge.TYPE));
|
|
||||||
assertThat(challenge.getStatus(), is(Status.PENDING));
|
|
||||||
assertThat(challenge.getSubject(), is("5bf0b9908ed73bc53ed3327afa52f76b.0a4bea00520f0753f42abe0bb39e3ea8.token.acme.invalid"));
|
|
||||||
assertThat(challenge.getSanB(), is("14e2350a04434f93c2e0b6012968d99d.ed459b6a7a019d9695609b8514f9d63d.ka.acme.invalid"));
|
|
||||||
|
|
||||||
JSONBuilder response = new JSONBuilder();
|
|
||||||
challenge.prepareResponse(response);
|
|
||||||
|
|
||||||
assertThat(response.toString(), sameJSONAs("{\"keyAuthorization\"=\""
|
|
||||||
+ KEY_AUTHORIZATION + "\"}").allowingExtraUnexpectedFields());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -29,7 +29,6 @@ import org.shredzone.acme4j.Session;
|
||||||
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.challenge.TokenChallenge;
|
import org.shredzone.acme4j.challenge.TokenChallenge;
|
||||||
import org.shredzone.acme4j.connector.Connection;
|
import org.shredzone.acme4j.connector.Connection;
|
||||||
import org.shredzone.acme4j.connector.DefaultConnection;
|
import org.shredzone.acme4j.connector.DefaultConnection;
|
||||||
|
@ -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 c5 = provider.createChallenge(session, getJSON("tlsSni02Challenge"));
|
|
||||||
assertThat(c5, not(nullValue()));
|
|
||||||
assertThat(c5, instanceOf(TlsSni02Challenge.class));
|
|
||||||
|
|
||||||
JSON json6 = new JSONBuilder()
|
JSON json6 = new JSONBuilder()
|
||||||
.put("type", "foobar-01")
|
.put("type", "foobar-01")
|
||||||
.put("url", "https://example.com/some/challenge")
|
.put("url", "https://example.com/some/challenge")
|
||||||
|
|
|
@ -10,11 +10,6 @@
|
||||||
"url": "https://example.com/authz/asdf/1",
|
"url": "https://example.com/authz/asdf/1",
|
||||||
"token": "DGyRejmCefe7v4NfDGDKfA"
|
"token": "DGyRejmCefe7v4NfDGDKfA"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "tls-sni-02",
|
|
||||||
"url": "https://example.com/authz/asdf/2",
|
|
||||||
"token": "VNLBdSiZ3LppU2CRG8bilqlwq4DuApJMg3ZJowU6JhQ"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "duplicate-01",
|
"type": "duplicate-01",
|
||||||
"url": "https://example.com/authz/asdf/3"
|
"url": "https://example.com/authz/asdf/3"
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"type": "tls-sni-02",
|
|
||||||
"url": "https://example.com/acme/authz/0",
|
|
||||||
"status": "pending",
|
|
||||||
"token": "VNLBdSiZ3LppU2CRG8bilqlwq4DuApJMg3ZJowU6JhQ"
|
|
||||||
}
|
|
|
@ -21,21 +21,17 @@ import java.io.Writer;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.Security;
|
import java.security.Security;
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
import javax.swing.JOptionPane;
|
import javax.swing.JOptionPane;
|
||||||
|
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
|
|
||||||
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.AcmeException;
|
import org.shredzone.acme4j.exception.AcmeException;
|
||||||
import org.shredzone.acme4j.util.CSRBuilder;
|
import org.shredzone.acme4j.util.CSRBuilder;
|
||||||
import org.shredzone.acme4j.util.CertificateUtils;
|
|
||||||
import org.shredzone.acme4j.util.KeyPairUtils;
|
import org.shredzone.acme4j.util.KeyPairUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -66,7 +62,7 @@ public class ClientTest {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(ClientTest.class);
|
private static final Logger LOG = LoggerFactory.getLogger(ClientTest.class);
|
||||||
|
|
||||||
private enum ChallengeType { HTTP, DNS, TLSSNI }
|
private enum ChallengeType { HTTP, DNS }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a certificate for the given domains. Also takes care for the registration
|
* Generates a certificate for the given domains. Also takes care for the registration
|
||||||
|
@ -218,10 +214,6 @@ public class ClientTest {
|
||||||
case DNS:
|
case DNS:
|
||||||
challenge = dnsChallenge(auth);
|
challenge = dnsChallenge(auth);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TLSSNI:
|
|
||||||
challenge = tlsSniChallenge(auth);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (challenge == null) {
|
if (challenge == null) {
|
||||||
|
@ -334,62 +326,6 @@ public class ClientTest {
|
||||||
return challenge;
|
return challenge;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepares a TLS-SNI challenge.
|
|
||||||
* <p>
|
|
||||||
* The verification of this challenge expects that the web server returns a special
|
|
||||||
* validation certificate.
|
|
||||||
* <p>
|
|
||||||
* This example outputs instructions that need to be executed manually. In a
|
|
||||||
* production environment, you would rather configure your web server automatically.
|
|
||||||
*
|
|
||||||
* @param auth
|
|
||||||
* {@link Authorization} to find the challenge in
|
|
||||||
* @return {@link Challenge} to verify
|
|
||||||
*/
|
|
||||||
public Challenge tlsSniChallenge(Authorization auth) throws AcmeException {
|
|
||||||
// Find a single tls-sni-02 challenge
|
|
||||||
TlsSni02Challenge challenge = auth.findChallenge(TlsSni02Challenge.TYPE);
|
|
||||||
if (challenge == null) {
|
|
||||||
throw new AcmeException("Found no " + TlsSni02Challenge.TYPE + " challenge, don't know what to do...");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the Subject
|
|
||||||
String subject = challenge.getSubject();
|
|
||||||
String sanB = challenge.getSanB();
|
|
||||||
|
|
||||||
// Create a validation key pair
|
|
||||||
KeyPair domainKeyPair;
|
|
||||||
try (FileWriter fw = new FileWriter("tlssni.key")) {
|
|
||||||
domainKeyPair = KeyPairUtils.createKeyPair(2048);
|
|
||||||
KeyPairUtils.writeKeyPair(domainKeyPair, fw);
|
|
||||||
} catch (IOException ex) {
|
|
||||||
throw new AcmeException("Could not write keypair", ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a validation certificate
|
|
||||||
try (JcaPEMWriter pw = new JcaPEMWriter(new FileWriter("tlssni.crt"))) {
|
|
||||||
X509Certificate cert = CertificateUtils.createTlsSni02Certificate(domainKeyPair, subject, sanB);
|
|
||||||
pw.writeObject(cert);
|
|
||||||
} catch (IOException ex) {
|
|
||||||
throw new AcmeException("Could not write certificate", ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output the challenge, wait for acknowledge...
|
|
||||||
LOG.info("Please configure your web server.");
|
|
||||||
LOG.info("It must return the certificate 'tlssni.crt' on a SNI request to:");
|
|
||||||
LOG.info(subject);
|
|
||||||
LOG.info("The matching keypair is available at 'tlssni.key'.");
|
|
||||||
LOG.info("If you're ready, dismiss the dialog...");
|
|
||||||
|
|
||||||
StringBuilder message = new StringBuilder();
|
|
||||||
message.append("Please use 'tlssni.key' and 'tlssni.crt' cert for SNI requests to:\n\n");
|
|
||||||
message.append("https://").append(subject).append("\n\n");
|
|
||||||
acceptChallenge(message.toString());
|
|
||||||
|
|
||||||
return challenge;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Presents the instructions for preparing the challenge validation, and waits for
|
* Presents the instructions for preparing the challenge validation, and waits for
|
||||||
* dismissal. If the user cancelled the dialog, an exception is thrown.
|
* dismissal. If the user cancelled the dialog, an exception is thrown.
|
||||||
|
|
|
@ -178,7 +178,6 @@
|
||||||
<from>openjdk:8-jre</from>
|
<from>openjdk:8-jre</from>
|
||||||
<ports>
|
<ports>
|
||||||
<port>53/udp</port>
|
<port>53/udp</port>
|
||||||
<port>5001</port>
|
|
||||||
<port>5002</port>
|
<port>5002</port>
|
||||||
<port>14001</port>
|
<port>14001</port>
|
||||||
</ports>
|
</ports>
|
||||||
|
|
|
@ -19,7 +19,6 @@ import java.util.ResourceBundle;
|
||||||
|
|
||||||
import org.shredzone.acme4j.it.server.DnsServer;
|
import org.shredzone.acme4j.it.server.DnsServer;
|
||||||
import org.shredzone.acme4j.it.server.HttpServer;
|
import org.shredzone.acme4j.it.server.HttpServer;
|
||||||
import org.shredzone.acme4j.it.server.TlsSniServer;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -27,8 +26,8 @@ import fi.iki.elonen.NanoHTTPD;
|
||||||
import fi.iki.elonen.router.RouterNanoHTTPD;
|
import fi.iki.elonen.router.RouterNanoHTTPD;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A mock server to test Pebble. It provides a HTTP server, TLS-SNI server, and DNS
|
* A mock server to test Pebble. It provides a HTTP server and DNS server. The servers can
|
||||||
* server. The servers can be configured remotely via simple HTTP POST requests.
|
* be configured remotely via simple HTTP POST requests.
|
||||||
* <p>
|
* <p>
|
||||||
* <em>WARNING:</em> This is a very simple server that is only meant to be used for
|
* <em>WARNING:</em> This is a very simple server that is only meant to be used for
|
||||||
* integration tests. Do not use in the outside world!
|
* integration tests. Do not use in the outside world!
|
||||||
|
@ -41,22 +40,18 @@ public class BammBamm {
|
||||||
private final int appPort;
|
private final int appPort;
|
||||||
private final int httpPort;
|
private final int httpPort;
|
||||||
private final int dnsPort;
|
private final int dnsPort;
|
||||||
private final int tlsSniPort;
|
|
||||||
private final AppServer appServer;
|
private final AppServer appServer;
|
||||||
private final DnsServer dnsServer;
|
private final DnsServer dnsServer;
|
||||||
private final HttpServer httpServer;
|
private final HttpServer httpServer;
|
||||||
private final TlsSniServer tlsSniServer;
|
|
||||||
|
|
||||||
private BammBamm() {
|
private BammBamm() {
|
||||||
ResourceBundle bundle = ResourceBundle.getBundle("bammbamm");
|
ResourceBundle bundle = ResourceBundle.getBundle("bammbamm");
|
||||||
appPort = Integer.parseInt(bundle.getString("app.port"));
|
appPort = Integer.parseInt(bundle.getString("app.port"));
|
||||||
dnsPort = Integer.parseInt(bundle.getString("dns.port"));
|
dnsPort = Integer.parseInt(bundle.getString("dns.port"));
|
||||||
httpPort = Integer.parseInt(bundle.getString("http.port"));
|
httpPort = Integer.parseInt(bundle.getString("http.port"));
|
||||||
tlsSniPort = Integer.parseInt(bundle.getString("tlsSni.port"));
|
|
||||||
|
|
||||||
dnsServer = new DnsServer();
|
dnsServer = new DnsServer();
|
||||||
httpServer = new HttpServer();
|
httpServer = new HttpServer();
|
||||||
tlsSniServer = new TlsSniServer();
|
|
||||||
appServer = new AppServer(appPort);
|
appServer = new AppServer(appPort);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,20 +78,12 @@ public class BammBamm {
|
||||||
return httpServer;
|
return httpServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the {@link TlsSniServer} instance.
|
|
||||||
*/
|
|
||||||
public TlsSniServer getTlsSniServer() {
|
|
||||||
return tlsSniServer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts the servers.
|
* Starts the servers.
|
||||||
*/
|
*/
|
||||||
public void start() {
|
public void start() {
|
||||||
dnsServer.start(dnsPort);
|
dnsServer.start(dnsPort);
|
||||||
httpServer.start(httpPort);
|
httpServer.start(httpPort);
|
||||||
tlsSniServer.start(tlsSniPort);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
appServer.start(NanoHTTPD.SOCKET_READ_TIMEOUT, true);
|
appServer.start(NanoHTTPD.SOCKET_READ_TIMEOUT, true);
|
||||||
|
@ -112,7 +99,6 @@ public class BammBamm {
|
||||||
*/
|
*/
|
||||||
public void stop() {
|
public void stop() {
|
||||||
appServer.stop();
|
appServer.stop();
|
||||||
tlsSniServer.stop();
|
|
||||||
httpServer.stop();
|
httpServer.stop();
|
||||||
dnsServer.stop();
|
dnsServer.stop();
|
||||||
|
|
||||||
|
@ -134,9 +120,6 @@ public class BammBamm {
|
||||||
|
|
||||||
addRoute(HttpHandler.ADD, HttpHandler.Add.class);
|
addRoute(HttpHandler.ADD, HttpHandler.Add.class);
|
||||||
addRoute(HttpHandler.REMOVE, HttpHandler.Remove.class);
|
addRoute(HttpHandler.REMOVE, HttpHandler.Remove.class);
|
||||||
|
|
||||||
addRoute(TlsSniHandler.ADD, TlsSniHandler.Add.class);
|
|
||||||
addRoute(TlsSniHandler.REMOVE, TlsSniHandler.Remove.class);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,12 +17,7 @@ import java.io.IOException;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.security.PrivateKey;
|
|
||||||
import java.security.cert.CertificateEncodingException;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Base64;
|
|
||||||
import java.util.Base64.Encoder;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.http.HttpResponse;
|
import org.apache.http.HttpResponse;
|
||||||
|
@ -136,41 +131,6 @@ public class BammBammClient {
|
||||||
.submit();
|
.submit();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a certificate for TLS-SNI tests.
|
|
||||||
*
|
|
||||||
* @param alias
|
|
||||||
* An alias to be used for removal, for example the domain name being
|
|
||||||
* validated.
|
|
||||||
* @param privateKey
|
|
||||||
* {@link PrivateKey} of the certificate
|
|
||||||
* @param cert
|
|
||||||
* {@link X509Certificate} containing the domain names to respond to
|
|
||||||
*/
|
|
||||||
public void tlsSniAddCertificate(String alias, PrivateKey privateKey, X509Certificate cert) throws IOException {
|
|
||||||
try {
|
|
||||||
createRequest(TlsSniHandler.ADD)
|
|
||||||
.arg(":alias", alias)
|
|
||||||
.param("privateKey", privateKey.getEncoded())
|
|
||||||
.param("cert", cert.getEncoded())
|
|
||||||
.submit();
|
|
||||||
} catch (CertificateEncodingException ex) {
|
|
||||||
throw new IOException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a certificate.
|
|
||||||
*
|
|
||||||
* @param alias
|
|
||||||
* Certificate alias to remove
|
|
||||||
*/
|
|
||||||
public void tlsSniRemoveCertificate(String alias) throws IOException {
|
|
||||||
createRequest(TlsSniHandler.REMOVE)
|
|
||||||
.arg(":alias", alias)
|
|
||||||
.submit();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@link Request} object.
|
* Creates a new {@link Request} object.
|
||||||
*
|
*
|
||||||
|
@ -187,7 +147,6 @@ public class BammBammClient {
|
||||||
*/
|
*/
|
||||||
private static class Request {
|
private static class Request {
|
||||||
private static final HttpClient CLIENT = HttpClients.createDefault();
|
private static final HttpClient CLIENT = HttpClients.createDefault();
|
||||||
private static final Encoder BASE64 = Base64.getEncoder();
|
|
||||||
private static final Charset UTF8 = Charset.forName("utf-8");
|
private static final Charset UTF8 = Charset.forName("utf-8");
|
||||||
|
|
||||||
private final List<NameValuePair> params = new ArrayList<>();
|
private final List<NameValuePair> params = new ArrayList<>();
|
||||||
|
@ -239,19 +198,6 @@ public class BammBammClient {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a binary form parameter. It will be sent in the request body.
|
|
||||||
*
|
|
||||||
* @param key
|
|
||||||
* Parameter name
|
|
||||||
* @param value
|
|
||||||
* Parameter value. It will be Base64 encoded.
|
|
||||||
* @return itself
|
|
||||||
*/
|
|
||||||
public Request param(String key, byte[] value) {
|
|
||||||
return param(key, BASE64.encodeToString(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Submits the POST request.
|
* Submits the POST request.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,80 +0,0 @@
|
||||||
/*
|
|
||||||
* acme4j - Java ACME client
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017 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.it;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.security.KeyFactory;
|
|
||||||
import java.security.PrivateKey;
|
|
||||||
import java.security.cert.CertificateFactory;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.security.spec.PKCS8EncodedKeySpec;
|
|
||||||
import java.util.Base64;
|
|
||||||
import java.util.Base64.Decoder;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.shredzone.acme4j.it.server.TlsSniServer;
|
|
||||||
|
|
||||||
import fi.iki.elonen.NanoHTTPD.IHTTPSession;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request handler for all {@code tls-sni-02} related requests.
|
|
||||||
*/
|
|
||||||
public final class TlsSniHandler {
|
|
||||||
|
|
||||||
public static final String ADD = "/tlssni/add/:alias";
|
|
||||||
public static final String REMOVE = "/tlssni/remove/:alias";
|
|
||||||
|
|
||||||
private TlsSniHandler() {
|
|
||||||
// this class cannot be instanciated.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds an TLS-SNI certificate.
|
|
||||||
*/
|
|
||||||
public static class Add extends AbstractResponder {
|
|
||||||
@Override
|
|
||||||
public void handle(Map<String, String> urlParams, IHTTPSession session) throws Exception {
|
|
||||||
String alias = urlParams.get("alias");
|
|
||||||
String privateKeyEncoded = session.getParameters().get("privateKey").get(0);
|
|
||||||
String certEncoded = session.getParameters().get("cert").get(0);
|
|
||||||
|
|
||||||
Decoder base64 = Base64.getDecoder();
|
|
||||||
|
|
||||||
KeyFactory kf = KeyFactory.getInstance("RSA");
|
|
||||||
PrivateKey privateKey = kf.generatePrivate(new PKCS8EncodedKeySpec(
|
|
||||||
base64.decode(privateKeyEncoded)));
|
|
||||||
|
|
||||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
|
||||||
X509Certificate cert = (X509Certificate) cf.generateCertificate(
|
|
||||||
new ByteArrayInputStream(base64.decode(certEncoded)));
|
|
||||||
|
|
||||||
TlsSniServer server = BammBamm.instance().getTlsSniServer();
|
|
||||||
server.addCertificate(alias, privateKey, cert);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes an TLS-SNI certificate.
|
|
||||||
*/
|
|
||||||
public static class Remove extends AbstractResponder {
|
|
||||||
@Override
|
|
||||||
public void handle(Map<String, String> urlParams, IHTTPSession session) throws Exception {
|
|
||||||
String alias = urlParams.get("alias");
|
|
||||||
|
|
||||||
TlsSniServer server = BammBamm.instance().getTlsSniServer();
|
|
||||||
server.removeCertificate(alias);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,242 +0,0 @@
|
||||||
/*
|
|
||||||
* acme4j - Java ACME client
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017 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.it.server;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.security.KeyManagementException;
|
|
||||||
import java.security.KeyStore;
|
|
||||||
import java.security.KeyStoreException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.PrivateKey;
|
|
||||||
import java.security.UnrecoverableKeyException;
|
|
||||||
import java.security.cert.Certificate;
|
|
||||||
import java.security.cert.CertificateException;
|
|
||||||
import java.security.cert.CertificateParsingException;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import javax.net.ssl.KeyManager;
|
|
||||||
import javax.net.ssl.KeyManagerFactory;
|
|
||||||
import javax.net.ssl.SSLContext;
|
|
||||||
import javax.net.ssl.SSLServerSocket;
|
|
||||||
import javax.net.ssl.SSLServerSocketFactory;
|
|
||||||
import javax.net.ssl.SSLSession;
|
|
||||||
import javax.net.ssl.SSLSocket;
|
|
||||||
import javax.net.ssl.TrustManager;
|
|
||||||
import javax.net.ssl.TrustManagerFactory;
|
|
||||||
|
|
||||||
import org.bouncycastle.asn1.x509.GeneralName;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A very simple TnsSni server. It waits for a connection and performs a TLS handshake
|
|
||||||
* returning the matching certificate to the requested domain.
|
|
||||||
* <p>
|
|
||||||
* This server can be used to validate {@code tls-sni-02} challenges.
|
|
||||||
*/
|
|
||||||
public class TlsSniServer {
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(TlsSniServer.class);
|
|
||||||
private static final char[] PASSWORD = "shibboleet".toCharArray();
|
|
||||||
|
|
||||||
private KeyStore keyStore = null;
|
|
||||||
private Thread thread = null;
|
|
||||||
private volatile boolean running = false;
|
|
||||||
private volatile boolean listening = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a certificate to the set of known certificates.
|
|
||||||
* <p>
|
|
||||||
* The certificate's CN and SANs are used for SNI.
|
|
||||||
*
|
|
||||||
* @param alias
|
|
||||||
* Internal alias
|
|
||||||
* @param privateKey
|
|
||||||
* Private key to be used with this certificate
|
|
||||||
* @param cert
|
|
||||||
* {@link X509Certificate} to be added
|
|
||||||
*/
|
|
||||||
public void addCertificate(String alias, PrivateKey privateKey, X509Certificate cert) {
|
|
||||||
initKeyStore();
|
|
||||||
|
|
||||||
try {
|
|
||||||
keyStore.setKeyEntry(alias, privateKey, PASSWORD, new Certificate[] {cert});
|
|
||||||
} catch (KeyStoreException ex) {
|
|
||||||
throw new IllegalArgumentException("Failed to add certificate " + alias, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a certificate.
|
|
||||||
*
|
|
||||||
* @param alias
|
|
||||||
* Internal alias of the certificate to remove
|
|
||||||
*/
|
|
||||||
public void removeCertificate(String alias) {
|
|
||||||
initKeyStore();
|
|
||||||
|
|
||||||
try {
|
|
||||||
keyStore.deleteEntry(alias);
|
|
||||||
} catch (KeyStoreException ex) {
|
|
||||||
throw new IllegalArgumentException("Failed to remove certificate " + alias, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts the TlsSni server.
|
|
||||||
*
|
|
||||||
* @param port
|
|
||||||
* Port to listen to
|
|
||||||
*/
|
|
||||||
public void start(int port) {
|
|
||||||
if (thread != null) {
|
|
||||||
throw new IllegalStateException("Server is already running");
|
|
||||||
}
|
|
||||||
|
|
||||||
running = true;
|
|
||||||
thread = new Thread(() -> serve(port));
|
|
||||||
thread.setName("tls-sni server");
|
|
||||||
thread.start();
|
|
||||||
LOG.info("tls-sni server listening at port {}", port);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops the TlsSni server.
|
|
||||||
*/
|
|
||||||
public void stop() {
|
|
||||||
if (thread != null) {
|
|
||||||
running = false;
|
|
||||||
thread.interrupt();
|
|
||||||
thread = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the server was started up and is listening to connections.
|
|
||||||
*/
|
|
||||||
public boolean isListening() {
|
|
||||||
return listening;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens an SSL server socket and processes incoming requests.
|
|
||||||
*
|
|
||||||
* @param port
|
|
||||||
* Port to listen at
|
|
||||||
*/
|
|
||||||
private void serve(int port) {
|
|
||||||
SSLContext sslContext = createSSLContext();
|
|
||||||
SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory();
|
|
||||||
|
|
||||||
try (SSLServerSocket sslServerSocket = (SSLServerSocket)
|
|
||||||
sslServerSocketFactory.createServerSocket(port)){
|
|
||||||
listening = true;
|
|
||||||
while (running) {
|
|
||||||
process(sslServerSocket);
|
|
||||||
}
|
|
||||||
} catch (IOException ex) {
|
|
||||||
LOG.error("Failed to create socket on port {}", port, ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
listening = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Accept and process an incoming request. Only the TLS handshake is used here.
|
|
||||||
* Incoming data is just consumed, and the socket is closed after that.
|
|
||||||
*
|
|
||||||
* @param sslServerSocket
|
|
||||||
* {@link SSLServerSocket} to accept connections from
|
|
||||||
*/
|
|
||||||
private void process(SSLServerSocket sslServerSocket) {
|
|
||||||
try (SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept()) {
|
|
||||||
sslSocket.setEnabledCipherSuites(sslSocket.getSupportedCipherSuites());
|
|
||||||
sslSocket.startHandshake();
|
|
||||||
|
|
||||||
SSLSession sslSession = sslSocket.getSession();
|
|
||||||
X509Certificate cert = (X509Certificate) sslSession.getLocalCertificates()[0];
|
|
||||||
LOG.info("tls-sni: {}", domainsToString(cert));
|
|
||||||
|
|
||||||
try (InputStream in = sslSocket.getInputStream()) {
|
|
||||||
while (in.read() >= 0); //NOSONAR: intentional empty statement
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
LOG.error("Failed to process request", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lazily initializes the {@link KeyStore} instance to be used. The key store is empty
|
|
||||||
* after initialization.
|
|
||||||
*/
|
|
||||||
private void initKeyStore() {
|
|
||||||
if (keyStore == null) {
|
|
||||||
try {
|
|
||||||
keyStore = KeyStore.getInstance("JKS");
|
|
||||||
keyStore.load(null, null);
|
|
||||||
} catch (KeyStoreException | NoSuchAlgorithmException | CertificateException
|
|
||||||
| IOException ex) {
|
|
||||||
throw new IllegalStateException("Failed to create key store", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a {@link SSLContext} that uses the internal {@link KeyStore} for key and
|
|
||||||
* trust management.
|
|
||||||
*
|
|
||||||
* @return {@link SSLContext} instance
|
|
||||||
*/
|
|
||||||
private SSLContext createSSLContext() {
|
|
||||||
initKeyStore();
|
|
||||||
|
|
||||||
try {
|
|
||||||
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("NewSunX509");
|
|
||||||
keyManagerFactory.init(keyStore, PASSWORD);
|
|
||||||
KeyManager[] km = keyManagerFactory.getKeyManagers();
|
|
||||||
|
|
||||||
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
|
|
||||||
trustManagerFactory.init(keyStore);
|
|
||||||
TrustManager[] tm = trustManagerFactory.getTrustManagers();
|
|
||||||
|
|
||||||
SSLContext sslContext = SSLContext.getInstance("TLSv1");
|
|
||||||
sslContext.init(km, tm, null);
|
|
||||||
|
|
||||||
return sslContext;
|
|
||||||
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException
|
|
||||||
| UnrecoverableKeyException ex) {
|
|
||||||
throw new IllegalStateException("Could not create SSLContext", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts all SANs of the given certificate and returns them as a string.
|
|
||||||
*
|
|
||||||
* @param cert
|
|
||||||
* {@link X509Certificate} to read the SANs from
|
|
||||||
* @return String of all SAN names joined together and separated by comma
|
|
||||||
*/
|
|
||||||
private String domainsToString(X509Certificate cert) {
|
|
||||||
try {
|
|
||||||
return cert.getSubjectAlternativeNames().stream()
|
|
||||||
.filter(c -> ((Number) c.get(0)).intValue() == GeneralName.dNSName)
|
|
||||||
.map(c -> (String) c.get(1))
|
|
||||||
.collect(Collectors.joining(", "));
|
|
||||||
} catch (CertificateParsingException ex) {
|
|
||||||
throw new IllegalArgumentException("bad certificate", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -7,6 +7,3 @@ dns.port = 53
|
||||||
|
|
||||||
# HTTP server port
|
# HTTP server port
|
||||||
http.port = 5002
|
http.port = 5002
|
||||||
|
|
||||||
# TLS-SNI server port
|
|
||||||
tlsSni.port = 5001
|
|
||||||
|
|
|
@ -57,6 +57,6 @@ The tags `maven` and `docker` are used to select the executor.
|
||||||
`acme4j-it` API
|
`acme4j-it` API
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
The `acme4j-it` module provides test servers for the `http-01`, `dns-01` and `tls-sni-02` challenges. You can use these classes for your own projects. However, they are not part of the official _acme4j_ API and subject to change without notice.
|
The `acme4j-it` module provides test servers for the `http-01` and `dns-01` challenges. You can use these classes for your own projects. However, they are not part of the official _acme4j_ API and subject to change without notice.
|
||||||
|
|
||||||
Note that these servers are very simple implementations without any security measures. They are tailor-made for integration tests. Do not use them in production code!
|
Note that these servers are very simple implementations without any security measures. They are tailor-made for integration tests. Do not use them in production code!
|
||||||
|
|
|
@ -1,157 +0,0 @@
|
||||||
/*
|
|
||||||
* acme4j - Java ACME client
|
|
||||||
*
|
|
||||||
* Copyright (C) 2017 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.it.boulder;
|
|
||||||
|
|
||||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
|
||||||
import static org.awaitility.Awaitility.await;
|
|
||||||
import static org.hamcrest.Matchers.*;
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
import java.security.KeyPair;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.shredzone.acme4j.Account;
|
|
||||||
import org.shredzone.acme4j.AccountBuilder;
|
|
||||||
import org.shredzone.acme4j.Authorization;
|
|
||||||
import org.shredzone.acme4j.Certificate;
|
|
||||||
import org.shredzone.acme4j.Order;
|
|
||||||
import org.shredzone.acme4j.Session;
|
|
||||||
import org.shredzone.acme4j.Status;
|
|
||||||
import org.shredzone.acme4j.challenge.TlsSni02Challenge;
|
|
||||||
import org.shredzone.acme4j.exception.AcmeException;
|
|
||||||
import org.shredzone.acme4j.exception.AcmeLazyLoadingException;
|
|
||||||
import org.shredzone.acme4j.it.BammBammClient;
|
|
||||||
import org.shredzone.acme4j.util.CSRBuilder;
|
|
||||||
import org.shredzone.acme4j.util.CertificateUtils;
|
|
||||||
import org.shredzone.acme4j.util.KeyPairUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests a complete certificate order with different challenges.
|
|
||||||
*/
|
|
||||||
public class OrderTlsSniIT {
|
|
||||||
|
|
||||||
private static final String TEST_DOMAIN = "example.com";
|
|
||||||
|
|
||||||
private final String bammbammUrl = System.getProperty("bammbammUrl", "http://localhost:14001");
|
|
||||||
|
|
||||||
private BammBammClient client = new BammBammClient(bammbammUrl);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test if a certificate can be ordered via http-01 challenge.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testHttpValidation() throws Exception {
|
|
||||||
KeyPair keyPair = createKeyPair();
|
|
||||||
Session session = new Session(boulderURI(), keyPair);
|
|
||||||
|
|
||||||
Account account = new AccountBuilder()
|
|
||||||
.agreeToTermsOfService()
|
|
||||||
.create(session);
|
|
||||||
|
|
||||||
KeyPair domainKeyPair = createKeyPair();
|
|
||||||
|
|
||||||
Order order = account.newOrder().domain(TEST_DOMAIN).create();
|
|
||||||
|
|
||||||
for (Authorization auth : order.getAuthorizations()) {
|
|
||||||
TlsSni02Challenge challenge = auth.findChallenge(TlsSni02Challenge.TYPE);
|
|
||||||
assertThat(challenge, is(notNullValue()));
|
|
||||||
|
|
||||||
KeyPair challengeKeyPair = createKeyPair();
|
|
||||||
X509Certificate challengeCert = CertificateUtils.createTlsSni02Certificate(challengeKeyPair, challenge.getSubject(), challenge.getSanB());
|
|
||||||
|
|
||||||
client.tlsSniAddCertificate(challenge.getSubject(), challengeKeyPair.getPrivate(), challengeCert);
|
|
||||||
|
|
||||||
challenge.trigger();
|
|
||||||
|
|
||||||
await()
|
|
||||||
.pollInterval(1, SECONDS)
|
|
||||||
.timeout(30, SECONDS)
|
|
||||||
.conditionEvaluationListener(cond -> updateAuth(auth))
|
|
||||||
.until(auth::getStatus, not(isOneOf(Status.PENDING, Status.PROCESSING)));
|
|
||||||
|
|
||||||
if (auth.getStatus() != Status.VALID) {
|
|
||||||
fail("Authorization failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
client.tlsSniRemoveCertificate(challenge.getSubject());
|
|
||||||
}
|
|
||||||
|
|
||||||
CSRBuilder csr = new CSRBuilder();
|
|
||||||
csr.addDomain(TEST_DOMAIN);
|
|
||||||
csr.sign(domainKeyPair);
|
|
||||||
byte[] encodedCsr = csr.getEncoded();
|
|
||||||
|
|
||||||
order.execute(encodedCsr);
|
|
||||||
|
|
||||||
await()
|
|
||||||
.pollInterval(1, SECONDS)
|
|
||||||
.timeout(30, SECONDS)
|
|
||||||
.conditionEvaluationListener(cond -> updateOrder(order))
|
|
||||||
.until(order::getStatus, not(isOneOf(Status.PENDING, Status.PROCESSING)));
|
|
||||||
|
|
||||||
Certificate certificate = order.getCertificate();
|
|
||||||
X509Certificate cert = certificate.getCertificate();
|
|
||||||
assertThat(cert, not(nullValue()));
|
|
||||||
assertThat(cert.getNotAfter(), not(nullValue()));
|
|
||||||
assertThat(cert.getNotBefore(), not(nullValue()));
|
|
||||||
assertThat(cert.getSubjectX500Principal().getName(), containsString("CN=" + TEST_DOMAIN));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The {@link URI} of the Boulder server to test against.
|
|
||||||
*/
|
|
||||||
protected URI boulderURI() {
|
|
||||||
return URI.create("http://localhost:4001/directory");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a fresh key pair.
|
|
||||||
*
|
|
||||||
* @return Created new {@link KeyPair}
|
|
||||||
*/
|
|
||||||
protected KeyPair createKeyPair() {
|
|
||||||
return KeyPairUtils.createKeyPair(2048);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Safely updates the authorization, catching checked exceptions.
|
|
||||||
*
|
|
||||||
* @param auth
|
|
||||||
* {@link Authorization} to update
|
|
||||||
*/
|
|
||||||
private void updateAuth(Authorization auth) {
|
|
||||||
try {
|
|
||||||
auth.update();
|
|
||||||
} catch (AcmeException ex) {
|
|
||||||
throw new AcmeLazyLoadingException(auth, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Safely updates the order, catching checked exceptions.
|
|
||||||
*
|
|
||||||
* @param order
|
|
||||||
* {@link Order} to update
|
|
||||||
*/
|
|
||||||
private void updateOrder(Order order) {
|
|
||||||
try {
|
|
||||||
order.update();
|
|
||||||
} catch (AcmeException ex) {
|
|
||||||
throw new AcmeLazyLoadingException(order, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -35,10 +35,8 @@ import org.shredzone.acme4j.Status;
|
||||||
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.it.BammBammClient;
|
import org.shredzone.acme4j.it.BammBammClient;
|
||||||
import org.shredzone.acme4j.util.CSRBuilder;
|
import org.shredzone.acme4j.util.CSRBuilder;
|
||||||
import org.shredzone.acme4j.util.CertificateUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests a complete certificate order with different challenges.
|
* Tests a complete certificate order with different challenges.
|
||||||
|
@ -47,32 +45,6 @@ public class OrderIT extends PebbleITBase {
|
||||||
|
|
||||||
private static final String TEST_DOMAIN = "example.com";
|
private static final String TEST_DOMAIN = "example.com";
|
||||||
|
|
||||||
/**
|
|
||||||
* Test if a certificate can be ordered via tns-sni-02 challenge.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testTlsSniValidation() throws Exception {
|
|
||||||
orderCertificate(TEST_DOMAIN, auth -> {
|
|
||||||
BammBammClient client = getBammBammClient();
|
|
||||||
|
|
||||||
TlsSni02Challenge challenge = auth.findChallenge(TlsSni02Challenge.TYPE);
|
|
||||||
assertThat(challenge, is(notNullValue()));
|
|
||||||
|
|
||||||
KeyPair challengeKey = createKeyPair();
|
|
||||||
|
|
||||||
X509Certificate cert = CertificateUtils.createTlsSni02Certificate(
|
|
||||||
challengeKey, challenge.getSubject(), challenge.getSanB());
|
|
||||||
|
|
||||||
client.dnsAddARecord(TEST_DOMAIN, getBammBammHostname());
|
|
||||||
client.tlsSniAddCertificate(challenge.getSubject(), challengeKey.getPrivate(), cert);
|
|
||||||
|
|
||||||
cleanup(() -> client.dnsRemoveARecord(TEST_DOMAIN));
|
|
||||||
cleanup(() -> client.tlsSniRemoveCertificate(challenge.getSubject()));
|
|
||||||
|
|
||||||
return challenge;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test if a certificate can be ordered via http-01 challenge.
|
* Test if a certificate can be ordered via http-01 challenge.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
"listenAddress": "0.0.0.0:14000",
|
"listenAddress": "0.0.0.0:14000",
|
||||||
"certificate": "/go/src/github.com/letsencrypt/pebble/test/certs/localhost/cert.pem",
|
"certificate": "/go/src/github.com/letsencrypt/pebble/test/certs/localhost/cert.pem",
|
||||||
"privateKey": "/go/src/github.com/letsencrypt/pebble/test/certs/localhost/key.pem",
|
"privateKey": "/go/src/github.com/letsencrypt/pebble/test/certs/localhost/key.pem",
|
||||||
"httpPort": 5002,
|
"httpPort": 5002
|
||||||
"tlsPort": 5001
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,29 +13,12 @@
|
||||||
*/
|
*/
|
||||||
package org.shredzone.acme4j.util;
|
package org.shredzone.acme4j.util;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.math.BigInteger;
|
|
||||||
import java.security.KeyPair;
|
|
||||||
import java.security.cert.CertificateException;
|
|
||||||
import java.security.cert.CertificateFactory;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
import org.bouncycastle.asn1.x500.X500Name;
|
|
||||||
import org.bouncycastle.asn1.x509.Extension;
|
|
||||||
import org.bouncycastle.asn1.x509.GeneralName;
|
|
||||||
import org.bouncycastle.asn1.x509.GeneralNames;
|
|
||||||
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
|
|
||||||
import org.bouncycastle.openssl.PEMParser;
|
import org.bouncycastle.openssl.PEMParser;
|
||||||
import org.bouncycastle.operator.OperatorCreationException;
|
|
||||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
|
||||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
|
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
|
||||||
import org.shredzone.acme4j.challenge.TlsSni02Challenge;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class offering convenience methods for certificates.
|
* Utility class offering convenience methods for certificates.
|
||||||
|
@ -66,63 +49,4 @@ public final class CertificateUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a self-signed {@link X509Certificate} that can be used for
|
|
||||||
* {@link TlsSni02Challenge}. The certificate is valid for 7 days.
|
|
||||||
*
|
|
||||||
* @param keypair
|
|
||||||
* A domain {@link KeyPair} to be used for the challenge
|
|
||||||
* @param sanA
|
|
||||||
* SAN-A to be used in the certificate
|
|
||||||
* @param sanB
|
|
||||||
* SAN-B to be used in the certificate
|
|
||||||
* @return Created certificate
|
|
||||||
*/
|
|
||||||
public static X509Certificate createTlsSni02Certificate(KeyPair keypair, String sanA, String sanB)
|
|
||||||
throws IOException {
|
|
||||||
return createCertificate(keypair, sanA, sanB);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a generic self-signed challenge {@link X509Certificate}. The certificate is
|
|
||||||
* valid for 7 days.
|
|
||||||
*
|
|
||||||
* @param keypair
|
|
||||||
* A domain {@link KeyPair} to be used for the challenge
|
|
||||||
* @param subject
|
|
||||||
* Subjects to create a certificate for
|
|
||||||
* @return Created certificate
|
|
||||||
*/
|
|
||||||
private static X509Certificate createCertificate(KeyPair keypair, String... subject) throws IOException {
|
|
||||||
final long now = System.currentTimeMillis();
|
|
||||||
final String signatureAlg = "SHA256withRSA";
|
|
||||||
|
|
||||||
try {
|
|
||||||
X500Name issuer = new X500Name("CN=acme.invalid");
|
|
||||||
BigInteger serial = BigInteger.valueOf(now);
|
|
||||||
Instant notBefore = Instant.ofEpochMilli(now);
|
|
||||||
Instant notAfter = notBefore.plus(Duration.ofDays(7));
|
|
||||||
|
|
||||||
JcaX509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(
|
|
||||||
issuer, serial, Date.from(notBefore), Date.from(notAfter),
|
|
||||||
issuer, keypair.getPublic());
|
|
||||||
|
|
||||||
GeneralName[] gns = new GeneralName[subject.length];
|
|
||||||
for (int ix = 0; ix < subject.length; ix++) {
|
|
||||||
gns[ix] = new GeneralName(GeneralName.dNSName, subject[ix]);
|
|
||||||
}
|
|
||||||
|
|
||||||
certBuilder.addExtension(Extension.subjectAlternativeName, false, new GeneralNames(gns));
|
|
||||||
|
|
||||||
JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder(signatureAlg);
|
|
||||||
|
|
||||||
byte[] cert = certBuilder.build(signerBuilder.build(keypair.getPrivate())).getEncoded();
|
|
||||||
|
|
||||||
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
|
|
||||||
return (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(cert));
|
|
||||||
} catch (CertificateException | OperatorCreationException ex) {
|
|
||||||
throw new IOException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,16 +23,7 @@ import java.io.InputStream;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.cert.CertificateParsingException;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.bouncycastle.asn1.x509.GeneralName;
|
|
||||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
|
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
@ -41,30 +32,6 @@ import org.junit.Test;
|
||||||
*/
|
*/
|
||||||
public class CertificateUtilsTest {
|
public class CertificateUtilsTest {
|
||||||
|
|
||||||
/**
|
|
||||||
* Test if {@link CertificateUtils#createTlsSni02Certificate(KeyPair, String, String)}
|
|
||||||
* creates a good certificate.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testCreateTlsSni02Certificate() throws IOException, CertificateParsingException {
|
|
||||||
String sanA = "1082909237a535173c8415a44539f84e.248317530d8d1a0c71de8fd23f1beae4.token.acme.invalid";
|
|
||||||
String sanB = "edc3a1d40199c1723358d57853bc23ff.4d4473417a6d76e80df17bbcfbe53d2c.ka.acme.invalid";
|
|
||||||
|
|
||||||
KeyPair keypair = KeyPairUtils.createKeyPair(2048);
|
|
||||||
|
|
||||||
X509Certificate cert = CertificateUtils.createTlsSni02Certificate(keypair, sanA, sanB);
|
|
||||||
|
|
||||||
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(sanA, sanB));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test if {@link CertificateUtils#readCSR(InputStream)} reads an identical CSR.
|
* Test if {@link CertificateUtils#readCSR(InputStream)} reads an identical CSR.
|
||||||
*/
|
*/
|
||||||
|
@ -100,23 +67,4 @@ public class CertificateUtilsTest {
|
||||||
constructor.newInstance();
|
constructor.newInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts all DNSName SANs from a certificate.
|
|
||||||
*
|
|
||||||
* @param cert
|
|
||||||
* {@link X509Certificate}
|
|
||||||
* @return Set of DNSName
|
|
||||||
*/
|
|
||||||
private Set<String> getSANs(X509Certificate cert) throws CertificateParsingException {
|
|
||||||
Set<String> result = new HashSet<>();
|
|
||||||
|
|
||||||
for (List<?> list : cert.getSubjectAlternativeNames()) {
|
|
||||||
if (((Number) list.get(0)).intValue() == GeneralName.dNSName) {
|
|
||||||
result.add((String) list.get(1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,4 +10,3 @@ 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-02](./tls-sni-02.html)
|
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
# tls-sni-02 Challenge
|
|
||||||
|
|
||||||
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:
|
|
||||||
|
|
||||||
```java
|
|
||||||
TlsSni02Challenge challenge = auth.findChallenge(TlsSni02Challenge.TYPE);
|
|
||||||
|
|
||||||
String subject = challenge.getSubject(); // SAN-A
|
|
||||||
String sanB = challenge.getSanB(); // SAN-B
|
|
||||||
```
|
|
||||||
|
|
||||||
`subject` and `sanB` are basically domain names formed like in this example:
|
|
||||||
|
|
||||||
```
|
|
||||||
5bf0b9908ed73bc53ed3327afa52f76b.0a4bea00520f0753f42abe0bb39e3ea8.token.acme.invalid
|
|
||||||
14e2350a04434f93c2e0b6012968d99d.ed459b6a7a019d9695609b8514f9d63d.ka.acme.invalid
|
|
||||||
```
|
|
||||||
|
|
||||||
You need to create a self-signed certificate with both `subject` and `sanB` set as _Subject Alternative Name_. After that, configure your web server so it will use this certificate on a SNI request to `subject`.
|
|
||||||
|
|
||||||
The `TlsSni02Challenge` 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.createTlsSni02Certificate(sniKeyPair, subject, sanB);
|
|
||||||
```
|
|
||||||
|
|
||||||
Now use `cert` and `sniKeyPair` to let your web server respond to SNI requests to `subject`. The CA is not allowed to reveal `sanB`, so it will not perform SNI requests to that domain.
|
|
||||||
|
|
||||||
The challenge is completed when the CA was able to send the SNI request and get the correct certificate in return.
|
|
||||||
|
|
||||||
> __Note:__ The request is sent to port 443 only. If your domain has multiple IP addresses, the CA randomly selects one of them. There is no way to choose a different port or a fixed IP address.
|
|
||||||
|
|
||||||
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 both `subject` and `sanB` set as `X509v3 Subject Alternative Name`.
|
|
|
@ -14,7 +14,6 @@ The `acme4j-it` module contains a small and very simple test server called _Bamm
|
||||||
|
|
||||||
* 14001: Provides a simple REST-like interface for adding and removing challenge tokens and DNS records. This port is exposed.
|
* 14001: Provides a simple REST-like interface for adding and removing challenge tokens and DNS records. This port is exposed.
|
||||||
* 53 (UDP): A simple DNS server for resolving test domain names, and providing `TXT` records for `dns-01` challenges.
|
* 53 (UDP): A simple DNS server for resolving test domain names, and providing `TXT` records for `dns-01` challenges.
|
||||||
* 5001: A simple HTTP server that responses with certificates for `tls-sni-02` challenges.
|
|
||||||
* 5002: A simple HTTP server that responses with tokens for `http-01` challenges.
|
* 5002: A simple HTTP server that responses with tokens for `http-01` challenges.
|
||||||
|
|
||||||
To run this server, you can use the Docker image mentioned above. You could also run the server directly, but since the DNS server is listening on a privileged port, it would need to be reconfigured first.
|
To run this server, you can use the Docker image mentioned above. You could also run the server directly, but since the DNS server is listening on a privileged port, it would need to be reconfigured first.
|
||||||
|
@ -33,4 +32,4 @@ Now set up a Docker instance of Boulder. Follow the instructions in the [Boulder
|
||||||
|
|
||||||
The Boulder integration tests can now be run with `mvn -P boulder verify`.
|
The Boulder integration tests can now be run with `mvn -P boulder verify`.
|
||||||
|
|
||||||
For a local Boulder installation, just make sure that `FAKE_DNS` is set to `127.0.0.1`. You'll also need to expose the ports 5001 and 5002 of _BammBamm_ by changing the `acme4j-it/pom.xml` accordingly.
|
For a local Boulder installation, just make sure that `FAKE_DNS` is set to `127.0.0.1`. You'll also need to expose the port 5002 of _BammBamm_ by changing the `acme4j-it/pom.xml` accordingly.
|
||||||
|
|
|
@ -40,7 +40,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-02" href="challenge/tls-sni-02.html"/>
|
|
||||||
</item>
|
</item>
|
||||||
<item name="CAs" href="ca/index.html">
|
<item name="CAs" href="ca/index.html">
|
||||||
<item name="Let's Encrypt" href="ca/letsencrypt.html"/>
|
<item name="Let's Encrypt" href="ca/letsencrypt.html"/>
|
||||||
|
|
Loading…
Reference in New Issue