Change Certificate resource

pull/55/head
Richard Körber 2017-04-27 22:42:18 +02:00
parent 3951577708
commit 846e200e62
17 changed files with 300 additions and 519 deletions

View File

@ -13,6 +13,10 @@
*/
package org.shredzone.acme4j;
import static java.util.Collections.unmodifiableList;
import java.io.IOException;
import java.io.Writer;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.cert.CertificateEncodingException;
@ -24,7 +28,7 @@ import org.shredzone.acme4j.connector.Connection;
import org.shredzone.acme4j.connector.Resource;
import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.exception.AcmeProtocolException;
import org.shredzone.acme4j.exception.AcmeRetryAfterException;
import org.shredzone.acme4j.util.AcmeUtils;
import org.shredzone.acme4j.util.JSONBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -35,24 +39,14 @@ import org.slf4j.LoggerFactory;
public class Certificate extends AcmeResource {
private static final long serialVersionUID = 7381527770159084201L;
private static final Logger LOG = LoggerFactory.getLogger(Certificate.class);
private static final int MAX_CHAIN_LENGTH = 10;
private URL chainCertUrl;
private X509Certificate cert = null;
private X509Certificate[] chain = null;
private ArrayList<X509Certificate> certChain = null;
protected Certificate(Session session, URL certUrl) {
super(session);
setLocation(certUrl);
}
protected Certificate(Session session, URL certUrl, URL chainUrl, X509Certificate cert) {
super(session);
setLocation(certUrl);
this.chainCertUrl = chainUrl;
this.cert = cert;
}
/**
* Creates a new instance of {@link Certificate} and binds it to the {@link Session}.
*
@ -67,79 +61,65 @@ public class Certificate extends AcmeResource {
}
/**
* Returns the URL of the certificate chain. {@code null} if not known or not
* available.
*/
public URL getChainLocation() {
return chainCertUrl;
}
/**
* Downloads the certificate. The result is cached.
* Downloads the certificate chain.
*
* @return {@link X509Certificate} that was downloaded
* @throws AcmeRetryAfterException
* the certificate is still being created, and the server returned an
* estimated date when it will be ready for download. You should wait for
* the date given in {@link AcmeRetryAfterException#getRetryAfter()}
* before trying again.
* @throws AcmeException
* if the certificate could not be downloaded
*/
public X509Certificate download() throws AcmeException {
if (cert == null) {
public void download() throws AcmeException {
if (certChain == null) {
LOG.debug("download");
try (Connection conn = getSession().provider().connect()) {
conn.sendRequest(getLocation(), getSession());
conn.accept(HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_ACCEPTED);
conn.handleRetryAfter("certificate is not available for download yet");
chainCertUrl = conn.getLink("up");
cert = conn.readCertificate();
conn.accept(HttpURLConnection.HTTP_OK);
certChain = new ArrayList<>(conn.readCertificates());
}
}
return cert;
}
/**
* Downloads the certificate chain. The result is cached.
* Returns the created certificate.
*
* @return Chain of {@link X509Certificate}s
* @throws AcmeRetryAfterException
* the certificate is still being created, and the server returned an
* estimated date when it will be ready for download. You should wait for
* the date given in {@link AcmeRetryAfterException#getRetryAfter()}
* before trying again.
* @return The created end-entity {@link X509Certificate} without issuer chain.
* @throws AcmeProtocolException
* if lazy downloading failed
*/
public X509Certificate[] downloadChain() throws AcmeException {
if (chain == null) {
if (chainCertUrl == null) {
download();
public X509Certificate getCertificate() {
lazyDownload();
return certChain.get(0);
}
/**
* Returns the created certificate and issuer chain.
*
* @return The created end-entity {@link X509Certificate} and issuer chain. The first
* certificate is always the end-entity certificate, followed by the
* intermediate certificates required to build a path to a trusted root.
* @throws AcmeProtocolException
* if lazy downloading failed
*/
public List<X509Certificate> getCertificateChain() {
lazyDownload();
return unmodifiableList(certChain);
}
/**
* Writes the certificate to the given writer. It is written in PEM format, with the
* end-entity cert coming first, followed by the intermediate ceritificates.
*
* @param out
* {@link Writer} to write to. The writer is not closed after use.
* @throws AcmeProtocolException
* if lazy downloading failed
*/
public void writeCertificate(Writer out) throws IOException {
try {
for (X509Certificate cert : getCertificateChain()) {
AcmeUtils.writeToPem(cert.getEncoded(), "CERTIFICATE", out);
}
if (chainCertUrl == null) {
throw new AcmeProtocolException("No certificate chain provided");
}
LOG.debug("downloadChain");
List<X509Certificate> certChain = new ArrayList<>();
URL link = chainCertUrl;
while (link != null && certChain.size() < MAX_CHAIN_LENGTH) {
try (Connection conn = getSession().provider().connect()) {
conn.sendRequest(chainCertUrl, getSession());
conn.accept(HttpURLConnection.HTTP_OK);
certChain.add(conn.readCertificate());
link = conn.getLink("up");
}
}
if (link != null) {
throw new AcmeProtocolException("Recursion limit reached (" + MAX_CHAIN_LENGTH
+ "). Didn't get " + link);
}
chain = certChain.toArray(new X509Certificate[certChain.size()]);
} catch (CertificateEncodingException ex) {
throw new IOException("Encoding error", ex);
}
return chain;
}
/**
@ -164,14 +144,10 @@ public class Certificate extends AcmeResource {
throw new AcmeProtocolException("CA does not support certificate revocation");
}
if (cert == null) {
download();
}
try (Connection conn = getSession().provider().connect()) {
JSONBuilder claims = new JSONBuilder();
claims.putResource(Resource.REVOKE_CERT);
claims.putBase64("certificate", cert.getEncoded());
claims.putBase64("certificate", getCertificate().getEncoded());
if (reason != null) {
claims.put("reason", reason.getReasonCode());
}
@ -183,4 +159,16 @@ public class Certificate extends AcmeResource {
}
}
/**
* Lazily downloads the certificate. Throws a runtime {@link AcmeProtocolException} if
* the download failed.
*/
private void lazyDownload() {
try {
download();
} catch (AcmeException ex) {
throw new AcmeProtocolException("Could not lazily download certificate", ex);
}
}
}

View File

@ -40,7 +40,7 @@ public class Order extends AcmeResource {
private Instant notBefore;
private Instant notAfter;
private List<URL> authorizations;
private URL certificate;
private Certificate certificate;
private boolean loaded = false;
protected Order(Session session, URL location) {
@ -113,10 +113,9 @@ public class Order extends AcmeResource {
}
/**
* Gets the {@link URL} where the certificate can be downloaded from, if it is
* available. {@code null} otherwise.
* Gets the {@link Certificate} if it is available. {@code null} otherwise.
*/
public URL getCertificateLocation() {
public Certificate getCertificate() {
load();
return certificate;
}
@ -160,7 +159,10 @@ public class Order extends AcmeResource {
this.csr = json.get("csr").asBinary();
this.notBefore = json.get("notBefore").asInstant();
this.notAfter = json.get("notAfter").asInstant();
this.certificate = json.get("certificate").asURL();
URL certUrl = json.get("certificate").asURL();
certificate = certUrl != null ? Certificate.bind(getSession(), certUrl) : null;
this.authorizations = json.get("authorizations").asArray().stream()
.map(JSON.Value::asURL)
.collect(toList());

View File

@ -19,7 +19,6 @@ import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.security.KeyPair;
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
@ -228,68 +227,6 @@ public class Registration extends AcmeResource {
}
}
/**
* Requests a certificate for the given CSR.
* <p>
* All domains given in the CSR must be authorized before.
*
* @param csr
* PKCS#10 Certificate Signing Request to be sent to the server
* @return The {@link Certificate}
*/
public Certificate requestCertificate(byte[] csr) throws AcmeException {
return requestCertificate(csr, null, null);
}
/**
* Requests a certificate for the given CSR.
* <p>
* All domains given in the CSR must be authorized before.
*
* @param csr
* PKCS#10 Certificate Signing Request to be sent to the server
* @param notBefore
* requested value of the notBefore field in the certificate, {@code null}
* for default. May be ignored by the server.
* @param notAfter
* requested value of the notAfter field in the certificate, {@code null}
* for default. May be ignored by the server.
* @return The {@link Certificate}
*/
public Certificate requestCertificate(byte[] csr, Instant notBefore, Instant notAfter)
throws AcmeException {
Objects.requireNonNull(csr, "csr");
LOG.debug("requestCertificate");
try (Connection conn = getSession().provider().connect()) {
JSONBuilder claims = new JSONBuilder();
claims.putResource(Resource.NEW_CERT);
claims.putBase64("csr", csr);
if (notBefore != null) {
claims.put("notBefore", notBefore);
}
if (notAfter != null) {
claims.put("notAfter", notAfter);
}
conn.sendSignedRequest(getSession().resourceUrl(Resource.NEW_CERT), claims, getSession());
int rc = conn.accept(HttpURLConnection.HTTP_CREATED, HttpURLConnection.HTTP_ACCEPTED);
X509Certificate cert = null;
if (rc == HttpURLConnection.HTTP_CREATED) {
try {
cert = conn.readCertificate();
} catch (AcmeProtocolException ex) {
LOG.warn("Could not parse attached certificate", ex);
}
}
URL chainCertUrl = conn.getLink("up");
return new Certificate(getSession(), conn.getLocation(), chainCertUrl, cert);
}
}
/**
* Changes the {@link KeyPair} associated with the registration.
* <p>

View File

@ -17,6 +17,7 @@ import java.net.URI;
import java.net.URL;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.List;
import org.shredzone.acme4j.Session;
import org.shredzone.acme4j.exception.AcmeException;
@ -91,11 +92,11 @@ public interface Connection extends AutoCloseable {
JSON readJsonResponse() throws AcmeException;
/**
* Reads a certificate.
* Reads a certificate and its issuers.
*
* @return {@link X509Certificate} that was read.
* @return List of X.509 certificate and chain that was read.
*/
X509Certificate readCertificate() throws AcmeException;
List<X509Certificate> readCertificates() throws AcmeException;
/**
* Throws an {@link AcmeRetryAfterException} if the last status was HTTP Accepted and

View File

@ -13,6 +13,7 @@
*/
package org.shredzone.acme4j.connector;
import static java.util.stream.Collectors.toList;
import static org.shredzone.acme4j.util.AcmeUtils.keyAlgorithm;
import java.io.IOException;
@ -272,17 +273,19 @@ public class DefaultConnection implements Connection {
}
@Override
public X509Certificate readCertificate() throws AcmeException {
public List<X509Certificate> readCertificates() throws AcmeException {
assertConnectionIsOpen();
String contentType = conn.getHeaderField(CONTENT_TYPE_HEADER);
if (!("application/pkix-cert".equals(contentType))) {
if (!("application/pem-certificate-chain".equals(contentType))) {
throw new AcmeProtocolException("Unexpected content type: " + contentType);
}
try (InputStream in = conn.getInputStream()) {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
return (X509Certificate) cf.generateCertificate(in);
return cf.generateCertificates(in).stream()
.map(c -> (X509Certificate) c)
.collect(toList());
} catch (IOException ex) {
throw new AcmeNetworkException(ex);
} catch (CertificateException ex) {

View File

@ -13,13 +13,16 @@
*/
package org.shredzone.acme4j.util;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.net.IDN;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Base64;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -50,6 +53,9 @@ public final class AcmeUtils {
private static final Pattern TZ_PATTERN = Pattern.compile(
"([+-])(\\d{2}):?(\\d{2})$");
private static final Base64.Encoder PEM_ENCODER = Base64.getMimeEncoder(64, "\n".getBytes());
private AcmeUtils() {
// Utility class without constructor
}
@ -229,4 +235,20 @@ public final class AcmeUtils {
}
}
/**
* Writes an encoded key or certificate to a file in PEM format.
*
* @param encoded
* Encoded data to write
* @param label
* PEM label, e.g. "CERTIFICATE"
* @param out
* {@link Writer} to write to. It will not be closed after use!
*/
public static void writeToPem(byte[] encoded, String label, Writer out) throws IOException {
out.append("-----BEGIN ").append(label).append("-----\n");
out.append(new String(PEM_ENCODER.encode(encoded)));
out.append("\n-----END ").append(label).append("-----\n");
}
}

View File

@ -14,21 +14,22 @@
package org.shredzone.acme4j;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.junit.Assert.assertThat;
import static org.shredzone.acme4j.util.TestUtils.*;
import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import org.junit.Test;
import org.shredzone.acme4j.connector.Resource;
import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.exception.AcmeRetryAfterException;
import org.shredzone.acme4j.provider.TestableConnectionProvider;
import org.shredzone.acme4j.util.JSONBuilder;
import org.shredzone.acme4j.util.TestUtils;
@ -40,105 +41,64 @@ public class CertificateTest {
private URL resourceUrl = url("http://example.com/acme/resource");
private URL locationUrl = url("http://example.com/acme/certificate");
private URL chainUrl = url("http://example.com/acme/chain");
/**
* Test that a certificate can be downloaded.
*/
@Test
public void testDownload() throws AcmeException, IOException {
final X509Certificate originalCert = TestUtils.createCertificate();
TestableConnectionProvider provider = new TestableConnectionProvider() {
private boolean isLocationUrl;
@Override
public void sendRequest(URL url, Session session) {
assertThat(url, isOneOf(locationUrl, chainUrl));
isLocationUrl = url.equals(locationUrl);
}
@Override
public int accept(int... httpStatus) throws AcmeException {
if (isLocationUrl) {
// The leaf certificate, might be asynchronous
assertThat(httpStatus, isIntArrayContainingInAnyOrder(
HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_ACCEPTED));
} else {
// The root certificate chain, always OK
assertThat(httpStatus, isIntArrayContainingInAnyOrder(
HttpURLConnection.HTTP_OK));
}
return HttpURLConnection.HTTP_OK;
}
@Override
public X509Certificate readCertificate() {
return originalCert;
}
@Override
public void handleRetryAfter(String message) throws AcmeException {
// Just do nothing
}
@Override
public URL getLink(String relation) {
switch(relation) {
case "up": return (isLocationUrl ? chainUrl : null);
default: return null;
}
}
};
Certificate cert = new Certificate(provider.createSession(), locationUrl);
X509Certificate downloadedCert = cert.download();
assertThat(downloadedCert, is(sameInstance(originalCert)));
assertThat(cert.getChainLocation(), is(chainUrl));
X509Certificate[] downloadedChain = cert.downloadChain();
assertThat(downloadedChain.length, is(1));
assertThat(downloadedChain[0], is(sameInstance(originalCert)));
provider.close();
}
/**
* Test that a {@link AcmeRetryAfterException} is thrown.
*/
@Test
public void testRetryAfter() throws AcmeException, IOException {
final Instant retryAfter = Instant.now().plus(Duration.ofSeconds(30));
public void testDownload() throws Exception {
final List<X509Certificate> originalCert = TestUtils.createCertificate();
TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override
public void sendRequest(URL url, Session session) {
assertThat(url, is(locationUrl));
assertThat(session, is(notNullValue()));
}
@Override
public int accept(int... httpStatus) throws AcmeException {
assertThat(httpStatus, isIntArrayContainingInAnyOrder(
HttpURLConnection.HTTP_OK, HttpURLConnection.HTTP_ACCEPTED));
return HttpURLConnection.HTTP_ACCEPTED;
assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_OK));
return HttpURLConnection.HTTP_OK;
}
@Override
public void handleRetryAfter(String message) throws AcmeException {
throw new AcmeRetryAfterException(message, retryAfter);
public List<X509Certificate> readCertificates() throws AcmeException {
return originalCert;
}
};
Certificate cert = new Certificate(provider.createSession(), locationUrl);
cert.download();
try {
cert.download();
fail("Expected AcmeRetryAfterException");
} catch (AcmeRetryAfterException ex) {
assertThat(ex.getRetryAfter(), is(retryAfter));
X509Certificate downloadedCert = cert.getCertificate();
assertThat(downloadedCert.getEncoded(), is(originalCert.get(0).getEncoded()));
List<X509Certificate> downloadedChain = cert.getCertificateChain();
assertThat(downloadedChain.size(), is(originalCert.size()));
for (int ix = 0; ix < downloadedChain.size(); ix++) {
assertThat(downloadedChain.get(ix).getEncoded(), is(originalCert.get(ix).getEncoded()));
}
byte[] writtenPem;
byte[] originalPem;
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
OutputStreamWriter w = new OutputStreamWriter(baos)) {
cert.writeCertificate(w);
w.flush();
writtenPem = baos.toByteArray();
}
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
InputStream in = getClass().getResourceAsStream("/cert.pem")) {
int len;
byte[] buffer = new byte[2048];
while((len = in.read(buffer)) >= 0) {
baos.write(buffer, 0, len);
}
originalPem = baos.toByteArray();
}
assertThat(writtenPem, is(originalPem));
provider.close();
}
@ -147,14 +107,24 @@ public class CertificateTest {
*/
@Test
public void testRevokeCertificate() throws AcmeException, IOException {
final X509Certificate originalCert = TestUtils.createCertificate();
final List<X509Certificate> originalCert = TestUtils.createCertificate();
TestableConnectionProvider provider = new TestableConnectionProvider() {
private boolean certRequested = false;
@Override
public void sendRequest(URL url, Session session) {
assertThat(url, is(locationUrl));
assertThat(session, is(notNullValue()));
certRequested = true;
}
@Override
public void sendSignedRequest(URL url, JSONBuilder claims, Session session) {
assertThat(url, is(resourceUrl));
assertThat(claims.toString(), sameJSONAs(getJSON("revokeCertificateRequest").toString()));
assertThat(session, is(notNullValue()));
certRequested = false;
}
@Override
@ -162,11 +132,17 @@ public class CertificateTest {
assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_OK));
return HttpURLConnection.HTTP_OK;
}
@Override
public List<X509Certificate> readCertificates() throws AcmeException {
assertThat(certRequested, is(true));
return originalCert;
}
};
provider.putTestResource(Resource.REVOKE_CERT, resourceUrl);
Certificate cert = new Certificate(provider.createSession(), locationUrl, null, originalCert);
Certificate cert = new Certificate(provider.createSession(), locationUrl);
cert.revoke();
provider.close();
@ -177,14 +153,24 @@ public class CertificateTest {
*/
@Test
public void testRevokeCertificateWithReason() throws AcmeException, IOException {
final X509Certificate originalCert = TestUtils.createCertificate();
final List<X509Certificate> originalCert = TestUtils.createCertificate();
TestableConnectionProvider provider = new TestableConnectionProvider() {
private boolean certRequested = false;
@Override
public void sendRequest(URL url, Session session) {
assertThat(url, is(locationUrl));
assertThat(session, is(notNullValue()));
certRequested = true;
}
@Override
public void sendSignedRequest(URL url, JSONBuilder claims, Session session) {
assertThat(url, is(resourceUrl));
assertThat(claims.toString(), sameJSONAs(getJSON("revokeCertificateWithReasonRequest").toString()));
assertThat(session, is(notNullValue()));
certRequested = false;
}
@Override
@ -192,11 +178,17 @@ public class CertificateTest {
assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_OK));
return HttpURLConnection.HTTP_OK;
}
@Override
public List<X509Certificate> readCertificates() throws AcmeException {
assertThat(certRequested, is(true));
return originalCert;
}
};
provider.putTestResource(Resource.REVOKE_CERT, resourceUrl);
Certificate cert = new Certificate(provider.createSession(), locationUrl, null, originalCert);
Certificate cert = new Certificate(provider.createSession(), locationUrl);
cert.revoke(RevocationReason.KEY_COMPROMISE);
provider.close();

View File

@ -72,7 +72,7 @@ public class OrderTest {
assertThat(order.getNotBefore(), is(parseTimestamp("2016-01-01T00:00:00Z")));
assertThat(order.getNotAfter(), is(parseTimestamp("2016-01-08T00:00:00Z")));
assertThat(order.getCertificateLocation(), is(url("https://example.com/acme/cert/1234")));
assertThat(order.getCertificate().getLocation(), is(url("https://example.com/acme/cert/1234")));
assertThat(order.getCsr(), is(csr));
List<Authorization> auths = order.getAuthorizations();
@ -117,12 +117,12 @@ public class OrderTest {
// Lazy loading
assertThat(requestWasSent.get(), is(false));
assertThat(order.getCertificateLocation(), is(url("https://example.com/acme/cert/1234")));
assertThat(order.getCertificate().getLocation(), is(url("https://example.com/acme/cert/1234")));
assertThat(requestWasSent.get(), is(true));
// Subsequent queries do not trigger another load
requestWasSent.set(false);
assertThat(order.getCertificateLocation(), is(url("https://example.com/acme/cert/1234")));
assertThat(order.getCertificate().getLocation(), is(url("https://example.com/acme/cert/1234")));
assertThat(order.getStatus(), is(Status.PENDING));
assertThat(order.getExpires(), is(parseTimestamp("2015-03-01T14:09:00Z")));
assertThat(requestWasSent.get(), is(false));

View File

@ -15,7 +15,6 @@ package org.shredzone.acme4j;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.shredzone.acme4j.util.AcmeUtils.parseTimestamp;
import static org.shredzone.acme4j.util.TestUtils.*;
import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs;
@ -25,14 +24,9 @@ import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.KeyPair;
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.jose4j.jws.JsonWebSignature;
@ -45,7 +39,6 @@ import org.shredzone.acme4j.challenge.Dns01Challenge;
import org.shredzone.acme4j.challenge.Http01Challenge;
import org.shredzone.acme4j.connector.Resource;
import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.exception.AcmeProtocolException;
import org.shredzone.acme4j.provider.AcmeProvider;
import org.shredzone.acme4j.provider.TestableConnectionProvider;
import org.shredzone.acme4j.util.JSON;
@ -60,11 +53,9 @@ public class RegistrationTest {
private URL resourceUrl = url("http://example.com/acme/resource");
private URL locationUrl = url("http://example.com/acme/registration");
private URI agreementUri = URI.create("http://example.com/agreement.pdf");
private URL chainUrl = url("http://example.com/acme/chain");
/**
* Test that a registration can be updated.
* @throws URISyntaxException
*/
@Test
public void testUpdateRegistration() throws AcmeException, IOException, URISyntaxException {
@ -298,232 +289,6 @@ public class RegistrationTest {
provider.close();
}
/**
* Test that a order can be requested.
*/
@Test
public void testOrder() throws AcmeException, IOException {
TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override
public void sendSignedRequest(URL url, JSONBuilder claims, Session session) {
assertThat(url, is(resourceUrl));
assertThat(claims.toString(), sameJSONAs(getJSON("requestOrderRequest").toString()));
assertThat(session, is(notNullValue()));
}
@Override
public int accept(int... httpStatus) throws AcmeException {
assertThat(httpStatus, isIntArrayContainingInAnyOrder(HttpURLConnection.HTTP_CREATED));
return HttpURLConnection.HTTP_CREATED;
}
@Override
public JSON readJsonResponse() {
return getJSON("requestOrderResponse");
}
@Override
public URL getLocation() {
return locationUrl;
}
};
provider.putTestResource(Resource.NEW_ORDER, resourceUrl);
byte[] csr = TestUtils.getResourceAsByteArray("/csr.der");
ZoneId utc = ZoneId.of("UTC");
Instant notBefore = LocalDate.of(2016, 1, 1).atStartOfDay(utc).toInstant();
Instant notAfter = LocalDate.of(2016, 1, 8).atStartOfDay(utc).toInstant();
Registration registration = new Registration(provider.createSession(), locationUrl);
Order order = registration.orderCertificate(csr, notBefore, notAfter);
assertThat(order.getLocation(), is(locationUrl));
assertThat(order.getCsr(), is(csr));
assertThat(order.getStatus(), is(Status.PENDING));
assertThat(order.getExpires(), is(parseTimestamp("2016-01-01T00:00:00Z")));
assertThat(order.getLocation(), is(locationUrl));
assertThat(order.getNotBefore(), is(notBefore));
assertThat(order.getNotAfter(), is(notAfter));
assertThat(order.getCertificateLocation(), is(nullValue()));
List<Authorization> auths = order.getAuthorizations();
assertThat(auths.size(), is(2));
assertThat(auths.stream().map(Authorization::getLocation)::iterator,
containsInAnyOrder(
url("https://example.com/acme/authz/1234"),
url("https://example.com/acme/authz/2345")));
provider.close();
}
/**
* Test that a certificate can be requested and is delivered synchronously.
*/
@Test
public void testRequestCertificateSync() throws AcmeException, IOException {
final X509Certificate originalCert = TestUtils.createCertificate();
TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override
public void sendRequest(URL url, Session session) {
fail("Attempted to download the certificate. Should be downloaded already!");
}
@Override
public void sendSignedRequest(URL url, JSONBuilder claims, Session session) {
assertThat(url, is(resourceUrl));
assertThat(claims.toString(), sameJSONAs(getJSON("requestCertificateRequestWithDate").toString()));
assertThat(session, is(notNullValue()));
}
@Override
public int accept(int... httpStatus) throws AcmeException {
assertThat(httpStatus, isIntArrayContainingInAnyOrder(
HttpURLConnection.HTTP_CREATED, HttpURLConnection.HTTP_ACCEPTED));
return HttpURLConnection.HTTP_CREATED;
}
@Override
public X509Certificate readCertificate() {
return originalCert;
}
@Override
public URL getLocation() {
return locationUrl;
}
@Override
public URL getLink(String relation) {
switch(relation) {
case "up": return chainUrl;
default: return null;
}
}
};
provider.putTestResource(Resource.NEW_CERT, resourceUrl);
byte[] csr = TestUtils.getResourceAsByteArray("/csr.der");
ZoneId utc = ZoneId.of("UTC");
Instant notBefore = LocalDate.of(2016, 1, 1).atStartOfDay(utc).toInstant();
Instant notAfter = LocalDate.of(2016, 1, 8).atStartOfDay(utc).toInstant();
Registration registration = new Registration(provider.createSession(), locationUrl);
Certificate cert = registration.requestCertificate(csr, notBefore, notAfter);
assertThat(cert.download(), is(originalCert));
assertThat(cert.getLocation(), is(locationUrl));
assertThat(cert.getChainLocation(), is(chainUrl));
provider.close();
}
/**
* Test that a certificate can be requested and is delivered asynchronously.
*/
@Test
public void testRequestCertificateAsync() throws AcmeException, IOException {
TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override
public void sendSignedRequest(URL url, JSONBuilder claims, Session session) {
assertThat(url, is(resourceUrl));
assertThat(claims.toString(), sameJSONAs(getJSON("requestCertificateRequest").toString()));
assertThat(session, is(notNullValue()));
}
@Override
public int accept(int... httpStatus) throws AcmeException {
assertThat(httpStatus, isIntArrayContainingInAnyOrder(
HttpURLConnection.HTTP_CREATED, HttpURLConnection.HTTP_ACCEPTED));
return HttpURLConnection.HTTP_ACCEPTED;
}
@Override
public URL getLink(String relation) {
switch(relation) {
case "up": return chainUrl;
default: return null;
}
}
@Override
public URL getLocation() {
return locationUrl;
}
};
provider.putTestResource(Resource.NEW_CERT, resourceUrl);
byte[] csr = TestUtils.getResourceAsByteArray("/csr.der");
Registration registration = new Registration(provider.createSession(), locationUrl);
Certificate cert = registration.requestCertificate(csr);
assertThat(cert.getLocation(), is(locationUrl));
assertThat(cert.getChainLocation(), is(chainUrl));
provider.close();
}
/**
* Test that an unparseable certificate can be requested, and at least its location
* is made available.
*/
@Test
public void testRequestCertificateBrokenSync() throws AcmeException, IOException {
TestableConnectionProvider provider = new TestableConnectionProvider() {
@Override
public void sendSignedRequest(URL url, JSONBuilder claims, Session session) {
assertThat(url, is(resourceUrl));
assertThat(claims.toString(), sameJSONAs(getJSON("requestCertificateRequestWithDate").toString()));
assertThat(session, is(notNullValue()));
}
@Override
public int accept(int... httpStatus) throws AcmeException {
assertThat(httpStatus, isIntArrayContainingInAnyOrder(
HttpURLConnection.HTTP_CREATED, HttpURLConnection.HTTP_ACCEPTED));
return HttpURLConnection.HTTP_CREATED;
}
@Override
public X509Certificate readCertificate() {
throw new AcmeProtocolException("Failed to read certificate");
}
@Override
public URL getLink(String relation) {
switch(relation) {
case "up": return chainUrl;
default: return null;
}
}
@Override
public URL getLocation() {
return locationUrl;
}
};
provider.putTestResource(Resource.NEW_CERT, resourceUrl);
byte[] csr = TestUtils.getResourceAsByteArray("/csr.der");
ZoneId utc = ZoneId.of("UTC");
Instant notBefore = LocalDate.of(2016, 1, 1).atStartOfDay(utc).toInstant();
Instant notAfter = LocalDate.of(2016, 1, 8).atStartOfDay(utc).toInstant();
Registration registration = new Registration(provider.createSession(), locationUrl);
Certificate cert = registration.requestCertificate(csr, notBefore, notAfter);
assertThat(cert.getLocation(), is(locationUrl));
assertThat(cert.getChainLocation(), is(chainUrl));
provider.close();
}
/**
* Test that the account key can be changed.
*/

View File

@ -21,6 +21,7 @@ import static uk.co.datumedge.hamcrest.json.SameJSONAs.sameJSONAs;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
@ -47,6 +48,7 @@ import org.shredzone.acme4j.exception.AcmeProtocolException;
import org.shredzone.acme4j.exception.AcmeRetryAfterException;
import org.shredzone.acme4j.exception.AcmeServerException;
import org.shredzone.acme4j.provider.AcmeProvider;
import org.shredzone.acme4j.util.AcmeUtils;
import org.shredzone.acme4j.util.JSON;
import org.shredzone.acme4j.util.JSONBuilder;
import org.shredzone.acme4j.util.TestUtils;
@ -759,20 +761,23 @@ public class DefaultConnectionTest {
*/
@Test
public void testReadCertificate() throws Exception {
X509Certificate original = TestUtils.createCertificate();
when(mockUrlConnection.getHeaderField("Content-Type")).thenReturn("application/pem-certificate-chain");
when(mockUrlConnection.getInputStream()).thenReturn(getClass().getResourceAsStream("/cert.pem"));
when(mockUrlConnection.getHeaderField("Content-Type")).thenReturn("application/pkix-cert");
when(mockUrlConnection.getInputStream()).thenReturn(new ByteArrayInputStream(original.getEncoded()));
X509Certificate downloaded;
List<X509Certificate> downloaded;
try (DefaultConnection conn = new DefaultConnection(mockHttpConnection)) {
conn.conn = mockUrlConnection;
downloaded = conn.readCertificate();
downloaded = conn.readCertificates();
}
assertThat(original, not(nullValue()));
List<X509Certificate> original = TestUtils.createCertificate();
assertThat(original.size(), is(2));
assertThat(downloaded, not(nullValue()));
assertThat(original.getEncoded(), is(equalTo(downloaded.getEncoded())));
assertThat(downloaded.size(), is(original.size()));
for (int ix = 0; ix < downloaded.size(); ix++) {
assertThat(downloaded.get(ix).getEncoded(), is(original.get(ix).getEncoded()));
};
verify(mockUrlConnection).getHeaderField("Content-Type");
verify(mockUrlConnection).getInputStream();
@ -784,16 +789,25 @@ public class DefaultConnectionTest {
*/
@Test(expected = AcmeProtocolException.class)
public void testReadBadCertificate() throws Exception {
X509Certificate original = TestUtils.createCertificate();
byte[] badCert = original.getEncoded();
Arrays.sort(badCert); // break it
// Build a broken certificate chain PEM file
byte[] brokenPem;
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
OutputStreamWriter w = new OutputStreamWriter(baos)) {
for (X509Certificate cert : TestUtils.createCertificate()) {
byte[] badCert = cert.getEncoded();
Arrays.sort(badCert); // break it
AcmeUtils.writeToPem(badCert, "CERTIFICATE", w);
}
w.flush();
brokenPem = baos.toByteArray();
}
when(mockUrlConnection.getHeaderField("Content-Type")).thenReturn("application/pkix-cert");
when(mockUrlConnection.getInputStream()).thenReturn(new ByteArrayInputStream(badCert));
when(mockUrlConnection.getHeaderField("Content-Type")).thenReturn("application/pem-certificate-chain");
when(mockUrlConnection.getInputStream()).thenReturn(new ByteArrayInputStream(brokenPem));
try (DefaultConnection conn = new DefaultConnection(mockHttpConnection)) {
conn.conn = mockUrlConnection;
conn.readCertificate();
conn.readCertificates();
}
}

View File

@ -17,6 +17,7 @@ import java.net.URI;
import java.net.URL;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.List;
import org.shredzone.acme4j.Session;
import org.shredzone.acme4j.exception.AcmeException;
@ -60,7 +61,7 @@ public class DummyConnection implements Connection {
}
@Override
public X509Certificate readCertificate() {
public List<X509Certificate> readCertificates() throws AcmeException {
throw new UnsupportedOperationException();
}

View File

@ -17,14 +17,22 @@ import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.shredzone.acme4j.util.AcmeUtils.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.security.KeyPair;
import java.security.Security;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import javax.xml.bind.DatatypeConverter;
@ -251,6 +259,33 @@ public class AcmeUtilsTest {
assertThat(stripErrorPrefix(null), is(nullValue()));
}
/**
* Test that {@link AcmeUtils#writeToPem(byte[], String, Writer)} writes a correct PEM
* file.
*/
@Test
public void testWriteToPem() throws IOException, CertificateEncodingException {
List<X509Certificate> certChain = TestUtils.createCertificate();
ByteArrayOutputStream pemFile = new ByteArrayOutputStream();
try (Writer w = new OutputStreamWriter(pemFile)) {
for (X509Certificate cert : certChain) {
AcmeUtils.writeToPem(cert.getEncoded(), "CERTIFICATE", w);
}
}
ByteArrayOutputStream originalFile = new ByteArrayOutputStream();
try (InputStream in = getClass().getResourceAsStream("/cert.pem")) {
byte[] buffer = new byte[2048];
int len;
while ((len = in.read(buffer)) >= 0) {
originalFile.write(buffer, 0, len);
}
}
assertThat(pemFile.toByteArray(), is(originalFile.toByteArray()));
}
/**
* Matches the given time.
*/

View File

@ -13,6 +13,9 @@
*/
package org.shredzone.acme4j.util;
import static java.util.Collections.unmodifiableList;
import static java.util.stream.Collectors.toList;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
@ -38,6 +41,7 @@ import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
@ -218,15 +222,17 @@ public final class TestUtils {
}
/**
* Creates a standard certificate for testing. This certificate is read from a test
* resource and is guaranteed not to change between test runs.
* Creates a standard certificate chain for testing. This certificate is read from a
* test resource and is guaranteed not to change between test runs.
*
* @return {@link X509Certificate} for testing
* @return List of {@link X509Certificate} for testing
*/
public static X509Certificate createCertificate() throws IOException {
try (InputStream cert = TestUtils.class.getResourceAsStream("/cert.pem")) {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
return (X509Certificate) certificateFactory.generateCertificate(cert);
public static List<X509Certificate> createCertificate() throws IOException {
try (InputStream in = TestUtils.class.getResourceAsStream("/cert.pem")) {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
return unmodifiableList(cf.generateCertificates(in).stream()
.map(c -> (X509Certificate) c)
.collect(toList()));
} catch (CertificateException ex) {
throw new IOException(ex);
}

View File

@ -1,20 +1,38 @@
-----BEGIN CERTIFICATE-----
MIIDVzCCAj+gAwIBAgIJAM4KDTzb0Y7NMA0GCSqGSIb3DQEBCwUAMEIxCzAJBgNV
BAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQg
Q29tcGFueSBMdGQwHhcNMTUxMjEwMDAxMTA4WhcNMjUxMjA3MDAxMTA4WjBCMQsw
CQYDVQQGEwJYWDEVMBMGA1UEBwwMRGVmYXVsdCBDaXR5MRwwGgYDVQQKDBNEZWZh
dWx0IENvbXBhbnkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
r0g3w4C8xbj/5lzJiDxk0HkEJeZeyruq+0AzOPMigJZ7zxZtX/KUxOIHrQ4qjcFh
l0DmQImoM0wESU+kcsjAHCx8E1lgRVlVsMfLAQPHkg5UybqfadzKT3ALcSD+9F9m
VIP6liC/6KzLTASmx6zM7j92KTl1ArObZr5mh0jvSNORrMhEC4Byn3+NTxjuHON1
rWppCMwpeNNhFzaAig3O8PY8IyaLXNP2Ac5pXn0iW16S+Im9by7751UeW5a7Dznm
uMEM+WY640ffJDQ4+I64H403uAgvvSu+BGw8SEEZGuBCxoCnG1g6y6OvJyN5TgqF
dGosAfm1u+/MP1seoPdpBQIDAQABo1AwTjAdBgNVHQ4EFgQUrie5ZLOrA/HuhW1b
/CHjzEvj34swHwYDVR0jBBgwFoAUrie5ZLOrA/HuhW1b/CHjzEvj34swDAYDVR0T
BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAkSOP0FUgIIUeJTObgXrenHzZpLAk
qXi37dgdYuPhNveo3agueP51N7yIoh6YGShiJ73Rvr+lVYTwFXStrLih1Wh3tWvk
sMxnvocgd7l6USRb5/AgH7eHeFK4DoCAak2hUAcCLDRJN3XMhNLpyJhw7GJxowVI
GUlxcW5Asrmh9qflfyMyjripTP3CdHobmNcNHyScjNncKj37m8vomel9acekTtDl
2Ci7nLdE+3VqQCXMIfLiF3PO0gGpKei0RuVCSOG6W83zVInCPd/l3aluSR+f/VZl
k8KGQ4As4uTQi89j+J1YepzG0ASMZpjVbXeIg5QBAywVxBh5XVTz37KN8A==
MIIDFzCCAf+gAwIBAgIIYZRPVr9ji5UwDQYJKoZIhvcNAQELBQAwKDEmMCQGA1UE
AxMdUGViYmxlIEludGVybWVkaWF0ZSBDQSA2NDVmYzUwHhcNMTcwNDI2MTE0NDEz
WhcNMjIwNDI2MTE0NDEzWjAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTCCASIwDQYJ
KoZIhvcNAQEBBQADggEPADCCAQoCggEBANCRYYYLLZxJeoJKOcSwe+VpwUR/vehv
x1dMy1fZoK3UX9sDcc5kRKxQJ7vog7q6XG4vA4fGcrGAfG6AeuwplWq3kb3UzYeq
JeESeoRG0QhWVwCtIUPPVjHaPS19jP1xaE0vsfzCP3gD4l6W9ZhYlIqirFHEFgK8
aKtMxFsmEVR2cDOyH9S5Eoe7QAY43mcflSV6+BzULRwvtT6ds+0Upf0UMbzp0z8V
dx017MoZdDMAumTaQt8MuIbwxcmRBrZp3pltF3mjGvtBMmuEUoqkiLWtCzhiH2pq
4T9LDBbilZmjgCWB9pLcqe+KxsdgmBSwPVB/3yhvDaAX0ZuvafjEF68CAwEAAaNX
MFUwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD
AjAMBgNVHRMBAf8EAjAAMBYGA1UdEQQPMA2CC2V4YW1wbGUuY29tMA0GCSqGSIb3
DQEBCwUAA4IBAQAtZKzESSFF9wVUQdjSe+2P+0OFR7vvfnABs0p1fRv3n17OEgwq
iZEui8aUVkY/mzH90rnL25iIUt+7v4PUUIa7NgZ5adxNvnMvTpuQyFYSwfJODFHZ
TZnJQJikvmxa0hIoH+zV0s3Pe3OctNeBEMAu2Tq4KsZZY4hF3c7G0Uwe7vmmffgH
tixADkbOKwqZm1fBzRx6CUjz3u+rmGa4b30unRuF81YI4jqyeOJGNezSYsvLPdIn
p+ISa9mbQvI09bZY/zis0uMGVFcNwKLX3X95xxMONdX7VUsEBq1rFz4ec7priCoi
aEPAD7lAq7FFB1HHwVkPovtYQq7IKXS5VXr4
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDFDCCAfygAwIBAgIIZF/FmWNLATcwDQYJKoZIhvcNAQELBQAwKDEmMCQGA1UE
AxMdUGViYmxlIEludGVybWVkaWF0ZSBDQSA2NDVmYzUwHhcNMTcwNDI2MTE0MzE4
WhcNNDcwNDI2MTE0MzE4WjAoMSYwJAYDVQQDEx1QZWJibGUgSW50ZXJtZWRpYXRl
IENBIDY0NWZjNTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMKU292V
U2wr0+8ZyFGZEEziJrqpNPyWPOZtPhjKQt2H8JeAuAnxBDSUjy8h88NKy/wakzIv
v+sYWdfpdezg1Ba331KyN31HWX7AMij35cTBQEx+1rzi+9v2S7woGe2UCuSv6cdz
nJaS0/NOvdDoPSGPctFwOBsCsgx6gr9m5ItanLXMCb8ToKVcUj6GOus0vpB3NNRb
m8sial/o7Sd4cw52riov1mIkR7Pbi6iACGd/KhFxpKAXQ1UMPTd4tZYGU8pCfyiB
0rddSmwhh8eWU5ONShLzHi1aDjiu4NEpRxp8K4Tf0MealIJpyjQf5NV8Dz+QG7aX
DSoH0n+1tGMdMI8CAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgKEMB0GA1UdJQQWMBQG
CCsGAQUFBwMBBggrBgEFBQcDAjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB
CwUAA4IBAQBwOTlS9hOo1RhD2/DgBYZl+gYhqjuBedDJfPS3K34n4Z1FVVG+F6IL
zP5CGTIABI3L3Ri1pfgUh2lU5nWfE95gUCnmJf8UA0dp0roJInQ25ux/nKFwcuA/
JL58QZ43TZ/T3BNm8aF/lPvkEut0HnCct1B5IYOzFhqmYS6+BtsiJ2qWxhjiP/yc
CXq3U289glMeSo7mz6FaUEinx6CZL6qHe5Ins/hMo57Jjay32RHjOeFmx+IlCA0o
6kXvrZJy1QUpiUkkV7vbnt/PvQLvKo43YR/MsvuYEiOcPoyt7b7FmZ5VXtCnKBcf
6BcViMAeJ6QzC1qJI6HlWIoqzsO6SKuu
-----END CERTIFICATE-----

View File

@ -1,4 +1,4 @@
{
"certificate": "MIIDVzCCAj-gAwIBAgIJAM4KDTzb0Y7NMA0GCSqGSIb3DQEBCwUAMEIxCzAJBgNVBAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQgQ29tcGFueSBMdGQwHhcNMTUxMjEwMDAxMTA4WhcNMjUxMjA3MDAxMTA4WjBCMQswCQYDVQQGEwJYWDEVMBMGA1UEBwwMRGVmYXVsdCBDaXR5MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBhbnkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr0g3w4C8xbj_5lzJiDxk0HkEJeZeyruq-0AzOPMigJZ7zxZtX_KUxOIHrQ4qjcFhl0DmQImoM0wESU-kcsjAHCx8E1lgRVlVsMfLAQPHkg5UybqfadzKT3ALcSD-9F9mVIP6liC_6KzLTASmx6zM7j92KTl1ArObZr5mh0jvSNORrMhEC4Byn3-NTxjuHON1rWppCMwpeNNhFzaAig3O8PY8IyaLXNP2Ac5pXn0iW16S-Im9by7751UeW5a7DznmuMEM-WY640ffJDQ4-I64H403uAgvvSu-BGw8SEEZGuBCxoCnG1g6y6OvJyN5TgqFdGosAfm1u-_MP1seoPdpBQIDAQABo1AwTjAdBgNVHQ4EFgQUrie5ZLOrA_HuhW1b_CHjzEvj34swHwYDVR0jBBgwFoAUrie5ZLOrA_HuhW1b_CHjzEvj34swDAYDVR0TBAUwAwEB_zANBgkqhkiG9w0BAQsFAAOCAQEAkSOP0FUgIIUeJTObgXrenHzZpLAkqXi37dgdYuPhNveo3agueP51N7yIoh6YGShiJ73Rvr-lVYTwFXStrLih1Wh3tWvksMxnvocgd7l6USRb5_AgH7eHeFK4DoCAak2hUAcCLDRJN3XMhNLpyJhw7GJxowVIGUlxcW5Asrmh9qflfyMyjripTP3CdHobmNcNHyScjNncKj37m8vomel9acekTtDl2Ci7nLdE-3VqQCXMIfLiF3PO0gGpKei0RuVCSOG6W83zVInCPd_l3aluSR-f_VZlk8KGQ4As4uTQi89j-J1YepzG0ASMZpjVbXeIg5QBAywVxBh5XVTz37KN8A",
"certificate": "MIIDFzCCAf-gAwIBAgIIYZRPVr9ji5UwDQYJKoZIhvcNAQELBQAwKDEmMCQGA1UEAxMdUGViYmxlIEludGVybWVkaWF0ZSBDQSA2NDVmYzUwHhcNMTcwNDI2MTE0NDEzWhcNMjIwNDI2MTE0NDEzWjAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANCRYYYLLZxJeoJKOcSwe-VpwUR_vehvx1dMy1fZoK3UX9sDcc5kRKxQJ7vog7q6XG4vA4fGcrGAfG6AeuwplWq3kb3UzYeqJeESeoRG0QhWVwCtIUPPVjHaPS19jP1xaE0vsfzCP3gD4l6W9ZhYlIqirFHEFgK8aKtMxFsmEVR2cDOyH9S5Eoe7QAY43mcflSV6-BzULRwvtT6ds-0Upf0UMbzp0z8Vdx017MoZdDMAumTaQt8MuIbwxcmRBrZp3pltF3mjGvtBMmuEUoqkiLWtCzhiH2pq4T9LDBbilZmjgCWB9pLcqe-KxsdgmBSwPVB_3yhvDaAX0ZuvafjEF68CAwEAAaNXMFUwDgYDVR0PAQH_BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMBYGA1UdEQQPMA2CC2V4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQAtZKzESSFF9wVUQdjSe-2P-0OFR7vvfnABs0p1fRv3n17OEgwqiZEui8aUVkY_mzH90rnL25iIUt-7v4PUUIa7NgZ5adxNvnMvTpuQyFYSwfJODFHZTZnJQJikvmxa0hIoH-zV0s3Pe3OctNeBEMAu2Tq4KsZZY4hF3c7G0Uwe7vmmffgHtixADkbOKwqZm1fBzRx6CUjz3u-rmGa4b30unRuF81YI4jqyeOJGNezSYsvLPdInp-ISa9mbQvI09bZY_zis0uMGVFcNwKLX3X95xxMONdX7VUsEBq1rFz4ec7priCoiaEPAD7lAq7FFB1HHwVkPovtYQq7IKXS5VXr4",
"resource": "revoke-cert"
}

View File

@ -1,5 +1,5 @@
{
"certificate": "MIIDVzCCAj-gAwIBAgIJAM4KDTzb0Y7NMA0GCSqGSIb3DQEBCwUAMEIxCzAJBgNVBAYTAlhYMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxHDAaBgNVBAoME0RlZmF1bHQgQ29tcGFueSBMdGQwHhcNMTUxMjEwMDAxMTA4WhcNMjUxMjA3MDAxMTA4WjBCMQswCQYDVQQGEwJYWDEVMBMGA1UEBwwMRGVmYXVsdCBDaXR5MRwwGgYDVQQKDBNEZWZhdWx0IENvbXBhbnkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr0g3w4C8xbj_5lzJiDxk0HkEJeZeyruq-0AzOPMigJZ7zxZtX_KUxOIHrQ4qjcFhl0DmQImoM0wESU-kcsjAHCx8E1lgRVlVsMfLAQPHkg5UybqfadzKT3ALcSD-9F9mVIP6liC_6KzLTASmx6zM7j92KTl1ArObZr5mh0jvSNORrMhEC4Byn3-NTxjuHON1rWppCMwpeNNhFzaAig3O8PY8IyaLXNP2Ac5pXn0iW16S-Im9by7751UeW5a7DznmuMEM-WY640ffJDQ4-I64H403uAgvvSu-BGw8SEEZGuBCxoCnG1g6y6OvJyN5TgqFdGosAfm1u-_MP1seoPdpBQIDAQABo1AwTjAdBgNVHQ4EFgQUrie5ZLOrA_HuhW1b_CHjzEvj34swHwYDVR0jBBgwFoAUrie5ZLOrA_HuhW1b_CHjzEvj34swDAYDVR0TBAUwAwEB_zANBgkqhkiG9w0BAQsFAAOCAQEAkSOP0FUgIIUeJTObgXrenHzZpLAkqXi37dgdYuPhNveo3agueP51N7yIoh6YGShiJ73Rvr-lVYTwFXStrLih1Wh3tWvksMxnvocgd7l6USRb5_AgH7eHeFK4DoCAak2hUAcCLDRJN3XMhNLpyJhw7GJxowVIGUlxcW5Asrmh9qflfyMyjripTP3CdHobmNcNHyScjNncKj37m8vomel9acekTtDl2Ci7nLdE-3VqQCXMIfLiF3PO0gGpKei0RuVCSOG6W83zVInCPd_l3aluSR-f_VZlk8KGQ4As4uTQi89j-J1YepzG0ASMZpjVbXeIg5QBAywVxBh5XVTz37KN8A",
"certificate": "MIIDFzCCAf-gAwIBAgIIYZRPVr9ji5UwDQYJKoZIhvcNAQELBQAwKDEmMCQGA1UEAxMdUGViYmxlIEludGVybWVkaWF0ZSBDQSA2NDVmYzUwHhcNMTcwNDI2MTE0NDEzWhcNMjIwNDI2MTE0NDEzWjAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANCRYYYLLZxJeoJKOcSwe-VpwUR_vehvx1dMy1fZoK3UX9sDcc5kRKxQJ7vog7q6XG4vA4fGcrGAfG6AeuwplWq3kb3UzYeqJeESeoRG0QhWVwCtIUPPVjHaPS19jP1xaE0vsfzCP3gD4l6W9ZhYlIqirFHEFgK8aKtMxFsmEVR2cDOyH9S5Eoe7QAY43mcflSV6-BzULRwvtT6ds-0Upf0UMbzp0z8Vdx017MoZdDMAumTaQt8MuIbwxcmRBrZp3pltF3mjGvtBMmuEUoqkiLWtCzhiH2pq4T9LDBbilZmjgCWB9pLcqe-KxsdgmBSwPVB_3yhvDaAX0ZuvafjEF68CAwEAAaNXMFUwDgYDVR0PAQH_BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMBYGA1UdEQQPMA2CC2V4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQAtZKzESSFF9wVUQdjSe-2P-0OFR7vvfnABs0p1fRv3n17OEgwqiZEui8aUVkY_mzH90rnL25iIUt-7v4PUUIa7NgZ5adxNvnMvTpuQyFYSwfJODFHZTZnJQJikvmxa0hIoH-zV0s3Pe3OctNeBEMAu2Tq4KsZZY4hF3c7G0Uwe7vmmffgHtixADkbOKwqZm1fBzRx6CUjz3u-rmGa4b30unRuF81YI4jqyeOJGNezSYsvLPdInp-ISa9mbQvI09bZY_zis0uMGVFcNwKLX3X95xxMONdX7VUsEBq1rFz4ec7priCoiaEPAD7lAq7FFB1HHwVkPovtYQq7IKXS5VXr4",
"resource": "revoke-cert",
"reason": 1
}

View File

@ -106,18 +106,15 @@ public class ClientTest {
}
// Now request a signed certificate.
Certificate certificate = reg.requestCertificate(csrb.getEncoded());
Order order = reg.orderCertificate(csrb.getEncoded(), null, null);
Certificate certificate = order.getCertificate();
LOG.info("Success! The certificate for domains " + domains + " has been generated!");
LOG.info("Certificate URI: " + certificate.getLocation());
// Download the leaf certificate and certificate chain.
X509Certificate cert = certificate.download();
X509Certificate[] chain = certificate.downloadChain();
// Write a combined file containing the certificate and chain.
try (FileWriter fw = new FileWriter(DOMAIN_CHAIN_FILE)) {
CertificateUtils.writeX509CertificateChain(fw, cert, chain);
certificate.writeCertificate(fw);
}
// That's all! Configure your web server to use the DOMAIN_KEY_FILE and