mirror of https://github.com/shred/acme4j
Add IT for wildcard certificates
parent
22975dc844
commit
2f50f8dac5
|
@ -36,8 +36,6 @@ 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.AcmeLazyLoadingException;
|
||||
import org.shredzone.acme4j.util.CSRBuilder;
|
||||
import org.shredzone.acme4j.util.CertificateUtils;
|
||||
|
||||
|
@ -48,17 +46,14 @@ public class OrderIT extends PebbleITBase {
|
|||
|
||||
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
|
||||
public void testTlsSniValidation() throws Exception {
|
||||
orderCertificate(TEST_DOMAIN, auth -> {
|
||||
BammBammClient client = getBammBammClient();
|
||||
|
||||
TlsSni02Challenge challenge = auth.findChallenge(TlsSni02Challenge.TYPE);
|
||||
assertThat(challenge, is(notNullValue()));
|
||||
|
||||
|
@ -67,7 +62,7 @@ public class OrderIT extends PebbleITBase {
|
|||
X509Certificate cert = CertificateUtils.createTlsSni02Certificate(
|
||||
challengeKey, challenge.getSubject(), challenge.getSanB());
|
||||
|
||||
client.dnsAddARecord(TEST_DOMAIN, bammbammHostname);
|
||||
client.dnsAddARecord(TEST_DOMAIN, getBammBammHostname());
|
||||
client.tlsSniAddCertificate(challenge.getSubject(), challengeKey.getPrivate(), cert);
|
||||
|
||||
cleanup(() -> client.dnsRemoveARecord(TEST_DOMAIN));
|
||||
|
@ -83,10 +78,12 @@ public class OrderIT extends PebbleITBase {
|
|||
@Test
|
||||
public void testHttpValidation() throws Exception {
|
||||
orderCertificate(TEST_DOMAIN, auth -> {
|
||||
BammBammClient client = getBammBammClient();
|
||||
|
||||
Http01Challenge challenge = auth.findChallenge(Http01Challenge.TYPE);
|
||||
assertThat(challenge, is(notNullValue()));
|
||||
|
||||
client.dnsAddARecord(TEST_DOMAIN, bammbammHostname);
|
||||
client.dnsAddARecord(TEST_DOMAIN, getBammBammHostname());
|
||||
client.httpAddToken(challenge.getToken(), challenge.getAuthorization());
|
||||
|
||||
cleanup(() -> client.dnsRemoveARecord(TEST_DOMAIN));
|
||||
|
@ -102,6 +99,8 @@ public class OrderIT extends PebbleITBase {
|
|||
@Test
|
||||
public void testDnsValidation() throws Exception {
|
||||
orderCertificate(TEST_DOMAIN, auth -> {
|
||||
BammBammClient client = getBammBammClient();
|
||||
|
||||
Dns01Challenge challenge = auth.findChallenge(Dns01Challenge.TYPE);
|
||||
assertThat(challenge, is(notNullValue()));
|
||||
|
||||
|
@ -186,34 +185,6 @@ public class OrderIT extends PebbleITBase {
|
|||
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
|
||||
private static interface Validator {
|
||||
Challenge prepare(Authorization auth) throws Exception;
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
|
@ -23,6 +23,10 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
|
@ -37,6 +41,11 @@ public abstract class PebbleITBase {
|
|||
private final String pebbleHost = System.getProperty("pebbleHost", "localhost");
|
||||
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<>();
|
||||
|
||||
@After
|
||||
|
@ -58,6 +67,23 @@ public abstract class PebbleITBase {
|
|||
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.
|
||||
*
|
||||
|
@ -82,6 +108,34 @@ public abstract class PebbleITBase {
|
|||
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
|
||||
public static interface CleanupCallback {
|
||||
void cleanup() throws Exception;
|
||||
|
|
Loading…
Reference in New Issue