Add IT for wildcard certificates

pull/55/head
Richard Körber 2017-12-28 00:07:45 +01:00
parent 22975dc844
commit 2f50f8dac5
No known key found for this signature in database
GPG Key ID: AAB9FD19C78AA3E0
3 changed files with 194 additions and 37 deletions

View File

@ -36,8 +36,6 @@ 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.TlsSni02Challenge;
import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.exception.AcmeLazyLoadingException;
import org.shredzone.acme4j.util.CSRBuilder; import org.shredzone.acme4j.util.CSRBuilder;
import org.shredzone.acme4j.util.CertificateUtils; import org.shredzone.acme4j.util.CertificateUtils;
@ -48,17 +46,14 @@ public class OrderIT extends PebbleITBase {
private static final String TEST_DOMAIN = "example.com"; private static final String TEST_DOMAIN = "example.com";
private final String bammbammUrl = System.getProperty("bammbammUrl", "http://localhost:14001");
private final String bammbammHostname = System.getProperty("bammbammHostname", "bammbamm");
private BammBammClient client = new BammBammClient(bammbammUrl);
/** /**
* Test if a certificate can be ordered via tns-sni-02 challenge. * Test if a certificate can be ordered via tns-sni-02 challenge.
*/ */
@Test @Test
public void testTlsSniValidation() throws Exception { public void testTlsSniValidation() throws Exception {
orderCertificate(TEST_DOMAIN, auth -> { orderCertificate(TEST_DOMAIN, auth -> {
BammBammClient client = getBammBammClient();
TlsSni02Challenge challenge = auth.findChallenge(TlsSni02Challenge.TYPE); TlsSni02Challenge challenge = auth.findChallenge(TlsSni02Challenge.TYPE);
assertThat(challenge, is(notNullValue())); assertThat(challenge, is(notNullValue()));
@ -67,7 +62,7 @@ public class OrderIT extends PebbleITBase {
X509Certificate cert = CertificateUtils.createTlsSni02Certificate( X509Certificate cert = CertificateUtils.createTlsSni02Certificate(
challengeKey, challenge.getSubject(), challenge.getSanB()); challengeKey, challenge.getSubject(), challenge.getSanB());
client.dnsAddARecord(TEST_DOMAIN, bammbammHostname); client.dnsAddARecord(TEST_DOMAIN, getBammBammHostname());
client.tlsSniAddCertificate(challenge.getSubject(), challengeKey.getPrivate(), cert); client.tlsSniAddCertificate(challenge.getSubject(), challengeKey.getPrivate(), cert);
cleanup(() -> client.dnsRemoveARecord(TEST_DOMAIN)); cleanup(() -> client.dnsRemoveARecord(TEST_DOMAIN));
@ -83,10 +78,12 @@ public class OrderIT extends PebbleITBase {
@Test @Test
public void testHttpValidation() throws Exception { public void testHttpValidation() throws Exception {
orderCertificate(TEST_DOMAIN, auth -> { orderCertificate(TEST_DOMAIN, auth -> {
BammBammClient client = getBammBammClient();
Http01Challenge challenge = auth.findChallenge(Http01Challenge.TYPE); Http01Challenge challenge = auth.findChallenge(Http01Challenge.TYPE);
assertThat(challenge, is(notNullValue())); assertThat(challenge, is(notNullValue()));
client.dnsAddARecord(TEST_DOMAIN, bammbammHostname); client.dnsAddARecord(TEST_DOMAIN, getBammBammHostname());
client.httpAddToken(challenge.getToken(), challenge.getAuthorization()); client.httpAddToken(challenge.getToken(), challenge.getAuthorization());
cleanup(() -> client.dnsRemoveARecord(TEST_DOMAIN)); cleanup(() -> client.dnsRemoveARecord(TEST_DOMAIN));
@ -102,6 +99,8 @@ public class OrderIT extends PebbleITBase {
@Test @Test
public void testDnsValidation() throws Exception { public void testDnsValidation() throws Exception {
orderCertificate(TEST_DOMAIN, auth -> { orderCertificate(TEST_DOMAIN, auth -> {
BammBammClient client = getBammBammClient();
Dns01Challenge challenge = auth.findChallenge(Dns01Challenge.TYPE); Dns01Challenge challenge = auth.findChallenge(Dns01Challenge.TYPE);
assertThat(challenge, is(notNullValue())); assertThat(challenge, is(notNullValue()));
@ -186,34 +185,6 @@ public class OrderIT extends PebbleITBase {
assertThat(cert.getSubjectX500Principal().getName(), containsString("CN=" + domain)); assertThat(cert.getSubjectX500Principal().getName(), containsString("CN=" + domain));
} }
/**
* 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);
}
}
@FunctionalInterface @FunctionalInterface
private static interface Validator { private static interface Validator {
Challenge prepare(Authorization auth) throws Exception; Challenge prepare(Authorization auth) throws Exception;

View File

@ -0,0 +1,132 @@
/*
* 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 static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.stream.Collectors.toList;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.security.KeyPair;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import org.bouncycastle.asn1.x509.GeneralName;
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.Dns01Challenge;
import org.shredzone.acme4j.util.CSRBuilder;
/**
* Tests a complete wildcard certificate order. Wildcard certificates currently only
* support dns-01 challenge.
*/
public class OrderWildcardIT extends PebbleITBase {
private static final String TEST_DOMAIN = "example.com";
private static final String TEST_WILDCARD_DOMAIN = "*.example.com";
/**
* Test if a wildcard certificate can be ordered via dns-01 challenge.
*/
@Test
public void testDnsValidation() throws Exception {
BammBammClient client = getBammBammClient();
KeyPair keyPair = createKeyPair();
Session session = new Session(pebbleURI(), keyPair);
Account account = new AccountBuilder()
.agreeToTermsOfService()
.create(session);
KeyPair domainKeyPair = createKeyPair();
Instant notBefore = Instant.now().truncatedTo(ChronoUnit.MILLIS);
Instant notAfter = notBefore.plus(Duration.ofDays(20L));
Order order = account.newOrder()
.domain(TEST_WILDCARD_DOMAIN)
.domain(TEST_DOMAIN)
.notBefore(notBefore)
.notAfter(notAfter)
.create();
assertThat(order.getNotBefore(), is(notBefore));
assertThat(order.getNotAfter(), is(notAfter));
assertThat(order.getStatus(), is(Status.PENDING));
for (Authorization auth : order.getAuthorizations()) {
assertThat(auth.getDomain(), is(TEST_DOMAIN));
assertThat(auth.getStatus(), is(Status.PENDING));
Dns01Challenge challenge = auth.findChallenge(Dns01Challenge.TYPE);
assertThat(challenge, is(notNullValue()));
String challengeDomainName = "_acme-challenge." + TEST_DOMAIN;
client.dnsAddTxtRecord(challengeDomainName, challenge.getDigest());
cleanup(() -> client.dnsRemoveTxtRecord(challengeDomainName));
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");
}
}
CSRBuilder csr = new CSRBuilder();
csr.addDomain(TEST_DOMAIN);
csr.addDomain(TEST_WILDCARD_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));
List<String> san = cert.getSubjectAlternativeNames().stream()
.filter(it -> ((Number) it.get(0)).intValue() == GeneralName.dNSName)
.map(it -> (String) it.get(1))
.collect(toList());
assertThat(san, contains(TEST_DOMAIN, TEST_WILDCARD_DOMAIN));
}
}

View File

@ -23,6 +23,10 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.junit.After; import org.junit.After;
import org.shredzone.acme4j.Authorization;
import org.shredzone.acme4j.Order;
import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.exception.AcmeLazyLoadingException;
import org.shredzone.acme4j.util.KeyPairUtils; import org.shredzone.acme4j.util.KeyPairUtils;
/** /**
@ -37,6 +41,11 @@ public abstract class PebbleITBase {
private final String pebbleHost = System.getProperty("pebbleHost", "localhost"); private final String pebbleHost = System.getProperty("pebbleHost", "localhost");
private final int pebblePort = Integer.parseInt(System.getProperty("pebblePort", "14000")); private final int pebblePort = Integer.parseInt(System.getProperty("pebblePort", "14000"));
private final String bammbammUrl = System.getProperty("bammbammUrl", "http://localhost:14001");
private final String bammbammHostname = System.getProperty("bammbammHostname", "bammbamm");
private BammBammClient bammBammClient;
private final List<CleanupCallback> cleanup = new ArrayList<>(); private final List<CleanupCallback> cleanup = new ArrayList<>();
@After @After
@ -58,6 +67,23 @@ public abstract class PebbleITBase {
return URI.create("acme://pebble/" + pebbleHost + ":" + pebblePort); return URI.create("acme://pebble/" + pebbleHost + ":" + pebblePort);
} }
/**
* @return {@link BammBammClient} singleton instance.
*/
protected BammBammClient getBammBammClient() {
if (bammBammClient == null) {
bammBammClient = new BammBammClient(bammbammUrl);
}
return bammBammClient;
}
/**
* @return Hostname or IP address of the BammBamm server.
*/
protected String getBammBammHostname() {
return bammbammHostname;
}
/** /**
* Creates a fresh key pair. * Creates a fresh key pair.
* *
@ -82,6 +108,34 @@ public abstract class PebbleITBase {
assertThat(url.getPath(), not(isEmptyOrNullString())); assertThat(url.getPath(), not(isEmptyOrNullString()));
} }
/**
* Safely updates the authorization, catching checked exceptions.
*
* @param auth
* {@link Authorization} to update
*/
protected 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
*/
protected void updateOrder(Order order) {
try {
order.update();
} catch (AcmeException ex) {
throw new AcmeLazyLoadingException(order, ex);
}
}
@FunctionalInterface @FunctionalInterface
public static interface CleanupCallback { public static interface CleanupCallback {
void cleanup() throws Exception; void cleanup() throws Exception;