From 137c2c7dd029227b9f82a5c5d9679af740bd3ae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20K=C3=B6rber?= Date: Sun, 14 Jan 2018 16:53:02 +0100 Subject: [PATCH] Remove tls-sni-02 challenge --- .../acme4j/challenge/TlsSni02Challenge.java | 67 ----- .../acme4j/provider/AbstractAcmeProvider.java | 2 - .../shredzone/acme4j/AuthorizationTest.java | 7 - .../challenge/TlsSni02ChallengeTest.java | 63 ----- .../provider/AbstractAcmeProviderTest.java | 5 - .../json/authorizationChallenges.json | 5 - .../resources/json/tlsSni02Challenge.json | 6 - .../java/org/shredzone/acme4j/ClientTest.java | 66 +---- acme4j-it/pom.xml | 1 - .../org/shredzone/acme4j/it/BammBamm.java | 21 +- .../shredzone/acme4j/it/BammBammClient.java | 54 ---- .../shredzone/acme4j/it/TlsSniHandler.java | 80 ------ .../acme4j/it/server/TlsSniServer.java | 242 ------------------ .../src/main/resources/bammbamm.properties | 3 - acme4j-it/src/site/markdown/index.md.vm | 2 +- .../acme4j/it/boulder/OrderTlsSniIT.java | 157 ------------ .../shredzone/acme4j/it/pebble/OrderIT.java | 28 -- acme4j-it/src/test/pebble/pebble-config.json | 3 +- .../acme4j/util/CertificateUtils.java | 76 ------ .../acme4j/util/CertificateUtilsTest.java | 52 ---- src/site/markdown/challenge/index.md | 1 - src/site/markdown/challenge/tls-sni-02.md | 44 ---- src/site/markdown/development/testing.md | 3 +- src/site/site.xml | 1 - 24 files changed, 6 insertions(+), 983 deletions(-) delete mode 100644 acme4j-client/src/main/java/org/shredzone/acme4j/challenge/TlsSni02Challenge.java delete mode 100644 acme4j-client/src/test/java/org/shredzone/acme4j/challenge/TlsSni02ChallengeTest.java delete mode 100644 acme4j-client/src/test/resources/json/tlsSni02Challenge.json delete mode 100644 acme4j-it/src/main/java/org/shredzone/acme4j/it/TlsSniHandler.java delete mode 100644 acme4j-it/src/main/java/org/shredzone/acme4j/it/server/TlsSniServer.java delete mode 100644 acme4j-it/src/test/java/org/shredzone/acme4j/it/boulder/OrderTlsSniIT.java delete mode 100644 src/site/markdown/challenge/tls-sni-02.md diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/TlsSni02Challenge.java b/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/TlsSni02Challenge.java deleted file mode 100644 index 0bf4690a..00000000 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/challenge/TlsSni02Challenge.java +++ /dev/null @@ -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); - } - -} diff --git a/acme4j-client/src/main/java/org/shredzone/acme4j/provider/AbstractAcmeProvider.java b/acme4j-client/src/main/java/org/shredzone/acme4j/provider/AbstractAcmeProvider.java index e61b7ddf..9fd1271f 100644 --- a/acme4j-client/src/main/java/org/shredzone/acme4j/provider/AbstractAcmeProvider.java +++ b/acme4j-client/src/main/java/org/shredzone/acme4j/provider/AbstractAcmeProvider.java @@ -24,7 +24,6 @@ import org.shredzone.acme4j.Session; import org.shredzone.acme4j.challenge.Challenge; import org.shredzone.acme4j.challenge.Dns01Challenge; import org.shredzone.acme4j.challenge.Http01Challenge; -import org.shredzone.acme4j.challenge.TlsSni02Challenge; import org.shredzone.acme4j.challenge.TokenChallenge; import org.shredzone.acme4j.connector.Connection; import org.shredzone.acme4j.connector.DefaultConnection; @@ -64,7 +63,6 @@ public abstract class AbstractAcmeProvider implements AcmeProvider { Map> map = new HashMap<>(); map.put(Dns01Challenge.TYPE, Dns01Challenge::new); - map.put(TlsSni02Challenge.TYPE, TlsSni02Challenge::new); map.put(Http01Challenge.TYPE, Http01Challenge::new); return Collections.unmodifiableMap(map); diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/AuthorizationTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/AuthorizationTest.java index 99132dac..017e5f6a 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/AuthorizationTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/AuthorizationTest.java @@ -29,7 +29,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.TlsSni02Challenge; import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.exception.AcmeProtocolException; import org.shredzone.acme4j.exception.AcmeRetryAfterException; @@ -67,11 +66,6 @@ public class AuthorizationTest { Challenge c3 = authorization.findChallenge(Dns01Challenge.TYPE); assertThat(c3, is(notNullValue())); 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(Dns01Challenge.TYPE, Dns01Challenge::new); - provider.putTestChallenge(TlsSni02Challenge.TYPE, TlsSni02Challenge::new); provider.putTestChallenge(DUPLICATE_TYPE, Challenge::new); Authorization authorization = new Authorization(session, locationUrl); diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/TlsSni02ChallengeTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/TlsSni02ChallengeTest.java deleted file mode 100644 index 82fd1b90..00000000 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/challenge/TlsSni02ChallengeTest.java +++ /dev/null @@ -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()); - } - -} diff --git a/acme4j-client/src/test/java/org/shredzone/acme4j/provider/AbstractAcmeProviderTest.java b/acme4j-client/src/test/java/org/shredzone/acme4j/provider/AbstractAcmeProviderTest.java index 2c6233bf..85a43b19 100644 --- a/acme4j-client/src/test/java/org/shredzone/acme4j/provider/AbstractAcmeProviderTest.java +++ b/acme4j-client/src/test/java/org/shredzone/acme4j/provider/AbstractAcmeProviderTest.java @@ -29,7 +29,6 @@ import org.shredzone.acme4j.Session; import org.shredzone.acme4j.challenge.Challenge; import org.shredzone.acme4j.challenge.Dns01Challenge; import org.shredzone.acme4j.challenge.Http01Challenge; -import org.shredzone.acme4j.challenge.TlsSni02Challenge; import org.shredzone.acme4j.challenge.TokenChallenge; import org.shredzone.acme4j.connector.Connection; import org.shredzone.acme4j.connector.DefaultConnection; @@ -147,10 +146,6 @@ public class AbstractAcmeProviderTest { assertThat(c3, not(nullValue())); 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() .put("type", "foobar-01") .put("url", "https://example.com/some/challenge") diff --git a/acme4j-client/src/test/resources/json/authorizationChallenges.json b/acme4j-client/src/test/resources/json/authorizationChallenges.json index 8c4a8509..2e044a38 100644 --- a/acme4j-client/src/test/resources/json/authorizationChallenges.json +++ b/acme4j-client/src/test/resources/json/authorizationChallenges.json @@ -10,11 +10,6 @@ "url": "https://example.com/authz/asdf/1", "token": "DGyRejmCefe7v4NfDGDKfA" }, - { - "type": "tls-sni-02", - "url": "https://example.com/authz/asdf/2", - "token": "VNLBdSiZ3LppU2CRG8bilqlwq4DuApJMg3ZJowU6JhQ" - }, { "type": "duplicate-01", "url": "https://example.com/authz/asdf/3" diff --git a/acme4j-client/src/test/resources/json/tlsSni02Challenge.json b/acme4j-client/src/test/resources/json/tlsSni02Challenge.json deleted file mode 100644 index 1308df7b..00000000 --- a/acme4j-client/src/test/resources/json/tlsSni02Challenge.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "type": "tls-sni-02", - "url": "https://example.com/acme/authz/0", - "status": "pending", - "token": "VNLBdSiZ3LppU2CRG8bilqlwq4DuApJMg3ZJowU6JhQ" -} diff --git a/acme4j-example/src/main/java/org/shredzone/acme4j/ClientTest.java b/acme4j-example/src/main/java/org/shredzone/acme4j/ClientTest.java index a6fe16b7..cb2efd24 100644 --- a/acme4j-example/src/main/java/org/shredzone/acme4j/ClientTest.java +++ b/acme4j-example/src/main/java/org/shredzone/acme4j/ClientTest.java @@ -21,21 +21,17 @@ import java.io.Writer; import java.net.URI; import java.security.KeyPair; import java.security.Security; -import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Collection; import javax.swing.JOptionPane; import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.bouncycastle.openssl.jcajce.JcaPEMWriter; import org.shredzone.acme4j.challenge.Challenge; import org.shredzone.acme4j.challenge.Dns01Challenge; import org.shredzone.acme4j.challenge.Http01Challenge; -import org.shredzone.acme4j.challenge.TlsSni02Challenge; import org.shredzone.acme4j.exception.AcmeException; import org.shredzone.acme4j.util.CSRBuilder; -import org.shredzone.acme4j.util.CertificateUtils; import org.shredzone.acme4j.util.KeyPairUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -66,7 +62,7 @@ public class ClientTest { 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 @@ -218,10 +214,6 @@ public class ClientTest { case DNS: challenge = dnsChallenge(auth); break; - - case TLSSNI: - challenge = tlsSniChallenge(auth); - break; } if (challenge == null) { @@ -334,62 +326,6 @@ public class ClientTest { return challenge; } - /** - * Prepares a TLS-SNI challenge. - *

- * The verification of this challenge expects that the web server returns a special - * validation certificate. - *

- * 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 * dismissal. If the user cancelled the dialog, an exception is thrown. diff --git a/acme4j-it/pom.xml b/acme4j-it/pom.xml index 99979f8c..8ee35a81 100644 --- a/acme4j-it/pom.xml +++ b/acme4j-it/pom.xml @@ -178,7 +178,6 @@ openjdk:8-jre 53/udp - 5001 5002 14001 diff --git a/acme4j-it/src/main/java/org/shredzone/acme4j/it/BammBamm.java b/acme4j-it/src/main/java/org/shredzone/acme4j/it/BammBamm.java index 27af37dc..1f3dbf63 100644 --- a/acme4j-it/src/main/java/org/shredzone/acme4j/it/BammBamm.java +++ b/acme4j-it/src/main/java/org/shredzone/acme4j/it/BammBamm.java @@ -19,7 +19,6 @@ import java.util.ResourceBundle; import org.shredzone.acme4j.it.server.DnsServer; import org.shredzone.acme4j.it.server.HttpServer; -import org.shredzone.acme4j.it.server.TlsSniServer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,8 +26,8 @@ import fi.iki.elonen.NanoHTTPD; import fi.iki.elonen.router.RouterNanoHTTPD; /** - * A mock server to test Pebble. It provides a HTTP server, TLS-SNI server, and DNS - * server. The servers can be configured remotely via simple HTTP POST requests. + * A mock server to test Pebble. It provides a HTTP server and DNS server. The servers can + * be configured remotely via simple HTTP POST requests. *

* WARNING: This is a very simple server that is only meant to be used for * integration tests. Do not use in the outside world! @@ -41,22 +40,18 @@ public class BammBamm { private final int appPort; private final int httpPort; private final int dnsPort; - private final int tlsSniPort; private final AppServer appServer; private final DnsServer dnsServer; private final HttpServer httpServer; - private final TlsSniServer tlsSniServer; private BammBamm() { ResourceBundle bundle = ResourceBundle.getBundle("bammbamm"); appPort = Integer.parseInt(bundle.getString("app.port")); dnsPort = Integer.parseInt(bundle.getString("dns.port")); httpPort = Integer.parseInt(bundle.getString("http.port")); - tlsSniPort = Integer.parseInt(bundle.getString("tlsSni.port")); dnsServer = new DnsServer(); httpServer = new HttpServer(); - tlsSniServer = new TlsSniServer(); appServer = new AppServer(appPort); } @@ -83,20 +78,12 @@ public class BammBamm { return httpServer; } - /** - * Returns the {@link TlsSniServer} instance. - */ - public TlsSniServer getTlsSniServer() { - return tlsSniServer; - } - /** * Starts the servers. */ public void start() { dnsServer.start(dnsPort); httpServer.start(httpPort); - tlsSniServer.start(tlsSniPort); try { appServer.start(NanoHTTPD.SOCKET_READ_TIMEOUT, true); @@ -112,7 +99,6 @@ public class BammBamm { */ public void stop() { appServer.stop(); - tlsSniServer.stop(); httpServer.stop(); dnsServer.stop(); @@ -134,9 +120,6 @@ public class BammBamm { addRoute(HttpHandler.ADD, HttpHandler.Add.class); addRoute(HttpHandler.REMOVE, HttpHandler.Remove.class); - - addRoute(TlsSniHandler.ADD, TlsSniHandler.Add.class); - addRoute(TlsSniHandler.REMOVE, TlsSniHandler.Remove.class); } } diff --git a/acme4j-it/src/main/java/org/shredzone/acme4j/it/BammBammClient.java b/acme4j-it/src/main/java/org/shredzone/acme4j/it/BammBammClient.java index 38803d4a..e19f89e4 100644 --- a/acme4j-it/src/main/java/org/shredzone/acme4j/it/BammBammClient.java +++ b/acme4j-it/src/main/java/org/shredzone/acme4j/it/BammBammClient.java @@ -17,12 +17,7 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; 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.Base64; -import java.util.Base64.Encoder; import java.util.List; import org.apache.http.HttpResponse; @@ -136,41 +131,6 @@ public class BammBammClient { .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. * @@ -187,7 +147,6 @@ public class BammBammClient { */ private static class Request { private static final HttpClient CLIENT = HttpClients.createDefault(); - private static final Encoder BASE64 = Base64.getEncoder(); private static final Charset UTF8 = Charset.forName("utf-8"); private final List params = new ArrayList<>(); @@ -239,19 +198,6 @@ public class BammBammClient { 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. */ diff --git a/acme4j-it/src/main/java/org/shredzone/acme4j/it/TlsSniHandler.java b/acme4j-it/src/main/java/org/shredzone/acme4j/it/TlsSniHandler.java deleted file mode 100644 index af54f800..00000000 --- a/acme4j-it/src/main/java/org/shredzone/acme4j/it/TlsSniHandler.java +++ /dev/null @@ -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 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 urlParams, IHTTPSession session) throws Exception { - String alias = urlParams.get("alias"); - - TlsSniServer server = BammBamm.instance().getTlsSniServer(); - server.removeCertificate(alias); - } - } - -} diff --git a/acme4j-it/src/main/java/org/shredzone/acme4j/it/server/TlsSniServer.java b/acme4j-it/src/main/java/org/shredzone/acme4j/it/server/TlsSniServer.java deleted file mode 100644 index 67d8f5b3..00000000 --- a/acme4j-it/src/main/java/org/shredzone/acme4j/it/server/TlsSniServer.java +++ /dev/null @@ -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. - *

- * 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. - *

- * 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); - } - } - -} diff --git a/acme4j-it/src/main/resources/bammbamm.properties b/acme4j-it/src/main/resources/bammbamm.properties index b05222c2..4c22d695 100644 --- a/acme4j-it/src/main/resources/bammbamm.properties +++ b/acme4j-it/src/main/resources/bammbamm.properties @@ -7,6 +7,3 @@ dns.port = 53 # HTTP server port http.port = 5002 - -# TLS-SNI server port -tlsSni.port = 5001 diff --git a/acme4j-it/src/site/markdown/index.md.vm b/acme4j-it/src/site/markdown/index.md.vm index 47e73b32..fd0edd1a 100644 --- a/acme4j-it/src/site/markdown/index.md.vm +++ b/acme4j-it/src/site/markdown/index.md.vm @@ -57,6 +57,6 @@ The tags `maven` and `docker` are used to select the executor. `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! diff --git a/acme4j-it/src/test/java/org/shredzone/acme4j/it/boulder/OrderTlsSniIT.java b/acme4j-it/src/test/java/org/shredzone/acme4j/it/boulder/OrderTlsSniIT.java deleted file mode 100644 index 5652088d..00000000 --- a/acme4j-it/src/test/java/org/shredzone/acme4j/it/boulder/OrderTlsSniIT.java +++ /dev/null @@ -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); - } - } - -} diff --git a/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/OrderIT.java b/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/OrderIT.java index c2aca0d1..ca3d199d 100644 --- a/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/OrderIT.java +++ b/acme4j-it/src/test/java/org/shredzone/acme4j/it/pebble/OrderIT.java @@ -35,10 +35,8 @@ import org.shredzone.acme4j.Status; import org.shredzone.acme4j.challenge.Challenge; import org.shredzone.acme4j.challenge.Dns01Challenge; import org.shredzone.acme4j.challenge.Http01Challenge; -import org.shredzone.acme4j.challenge.TlsSni02Challenge; import org.shredzone.acme4j.it.BammBammClient; import org.shredzone.acme4j.util.CSRBuilder; -import org.shredzone.acme4j.util.CertificateUtils; /** * 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"; - /** - * 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. */ diff --git a/acme4j-it/src/test/pebble/pebble-config.json b/acme4j-it/src/test/pebble/pebble-config.json index a2d7f7b6..7e7447d7 100644 --- a/acme4j-it/src/test/pebble/pebble-config.json +++ b/acme4j-it/src/test/pebble/pebble-config.json @@ -3,7 +3,6 @@ "listenAddress": "0.0.0.0:14000", "certificate": "/go/src/github.com/letsencrypt/pebble/test/certs/localhost/cert.pem", "privateKey": "/go/src/github.com/letsencrypt/pebble/test/certs/localhost/key.pem", - "httpPort": 5002, - "tlsPort": 5001 + "httpPort": 5002 } } diff --git a/acme4j-utils/src/main/java/org/shredzone/acme4j/util/CertificateUtils.java b/acme4j-utils/src/main/java/org/shredzone/acme4j/util/CertificateUtils.java index b1d3e716..06b92027 100644 --- a/acme4j-utils/src/main/java/org/shredzone/acme4j/util/CertificateUtils.java +++ b/acme4j-utils/src/main/java/org/shredzone/acme4j/util/CertificateUtils.java @@ -13,29 +13,12 @@ */ package org.shredzone.acme4j.util; -import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; 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.operator.OperatorCreationException; -import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.bouncycastle.pkcs.PKCS10CertificationRequest; -import org.shredzone.acme4j.challenge.TlsSni02Challenge; /** * 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); - } - } - } diff --git a/acme4j-utils/src/test/java/org/shredzone/acme4j/util/CertificateUtilsTest.java b/acme4j-utils/src/test/java/org/shredzone/acme4j/util/CertificateUtilsTest.java index 5aee3cf0..ed769cdd 100644 --- a/acme4j-utils/src/test/java/org/shredzone/acme4j/util/CertificateUtilsTest.java +++ b/acme4j-utils/src/test/java/org/shredzone/acme4j/util/CertificateUtilsTest.java @@ -23,16 +23,7 @@ import java.io.InputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Modifier; 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.junit.Test; @@ -41,30 +32,6 @@ import org.junit.Test; */ 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. */ @@ -100,23 +67,4 @@ public class CertificateUtilsTest { constructor.newInstance(); } - /** - * Extracts all DNSName SANs from a certificate. - * - * @param cert - * {@link X509Certificate} - * @return Set of DNSName - */ - private Set getSANs(X509Certificate cert) throws CertificateParsingException { - Set result = new HashSet<>(); - - for (List list : cert.getSubjectAlternativeNames()) { - if (((Number) list.get(0)).intValue() == GeneralName.dNSName) { - result.add((String) list.get(1)); - } - } - - return result; - } - } diff --git a/src/site/markdown/challenge/index.md b/src/site/markdown/challenge/index.md index 1c749296..31ab276c 100644 --- a/src/site/markdown/challenge/index.md +++ b/src/site/markdown/challenge/index.md @@ -10,4 +10,3 @@ The ACME specifications define these standard challenges: * [http-01](./http-01.html) * [dns-01](./dns-01.html) -* [tls-sni-02](./tls-sni-02.html) diff --git a/src/site/markdown/challenge/tls-sni-02.md b/src/site/markdown/challenge/tls-sni-02.md deleted file mode 100644 index 4cc76849..00000000 --- a/src/site/markdown/challenge/tls-sni-02.md +++ /dev/null @@ -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`. diff --git a/src/site/markdown/development/testing.md b/src/site/markdown/development/testing.md index 5f8cc362..70785ce1 100644 --- a/src/site/markdown/development/testing.md +++ b/src/site/markdown/development/testing.md @@ -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. * 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. 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`. -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. diff --git a/src/site/site.xml b/src/site/site.xml index da6ac1d9..7da39bcc 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -40,7 +40,6 @@ -