mirror of https://github.com/shred/acme4j
Merge pull request #16 from cargy/fetch_cert_chain
add support for fetching certificate chainpull/18/head
commit
24b11fe5a9
|
@ -135,9 +135,22 @@ public interface AcmeClient {
|
|||
* {@link Registration} to be used for conversation
|
||||
* @param csr
|
||||
* PKCS#10 Certificate Signing Request to be sent to the server
|
||||
* @return {@link URI} the certificate can be downloaded from
|
||||
* @return {@link CertificateURIs} the certificate and certificate chain can be downloaded from
|
||||
*/
|
||||
URI requestCertificate(Registration registration, byte[] csr) throws AcmeException;
|
||||
CertificateURIs requestCertificate(Registration registration, byte[] csr) throws AcmeException;
|
||||
|
||||
/**
|
||||
* Downloads chain for certificate.
|
||||
*
|
||||
* @param chainCertUri
|
||||
* Certificate {@link URI}
|
||||
* @return Downloaded {@link X509Certificate[]}
|
||||
*
|
||||
* @throws AcmeException
|
||||
* if an {@link IOException} is thrown during certificate retrieval
|
||||
* or the max recursion limit is exceeded
|
||||
*/
|
||||
X509Certificate[] downloadCertificateChain(URI chainCertUri) throws AcmeException;
|
||||
|
||||
/**
|
||||
* Downloads a certificate.
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* acme4j - Java ACME client
|
||||
*
|
||||
* Copyright (C) 2015 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;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* Represents the URIs returned by a certificate request
|
||||
*
|
||||
* @author cargy
|
||||
*
|
||||
*/
|
||||
public class CertificateURIs {
|
||||
|
||||
private final URI certUri;
|
||||
private final URI chainCertUri;
|
||||
|
||||
public CertificateURIs(URI certUri, URI chainCertUri) {
|
||||
this.certUri = certUri;
|
||||
this.chainCertUri = chainCertUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* The URI from which the client may fetch the certificate
|
||||
*
|
||||
* @return
|
||||
* {@link URI} the certificate can be downloaded from
|
||||
*/
|
||||
public URI getCertUri() {
|
||||
return certUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* The URI from which the client may fetch a chain of CA certificates
|
||||
*
|
||||
* @return
|
||||
* {@link URI} the certificate chain can be downloaded from
|
||||
*/
|
||||
public URI getChainCertUri() {
|
||||
return chainCertUri;
|
||||
}
|
||||
|
||||
}
|
|
@ -30,6 +30,7 @@ import org.jose4j.jws.JsonWebSignature;
|
|||
import org.jose4j.lang.JoseException;
|
||||
import org.shredzone.acme4j.AcmeClient;
|
||||
import org.shredzone.acme4j.Authorization;
|
||||
import org.shredzone.acme4j.CertificateURIs;
|
||||
import org.shredzone.acme4j.Registration;
|
||||
import org.shredzone.acme4j.Status;
|
||||
import org.shredzone.acme4j.challenge.Challenge;
|
||||
|
@ -54,6 +55,7 @@ import org.slf4j.LoggerFactory;
|
|||
*/
|
||||
public abstract class AbstractAcmeClient implements AcmeClient {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AbstractAcmeClient.class);
|
||||
private static final int MAX_CHAIN_LENGTH = 10;
|
||||
|
||||
private final Session session = new Session();
|
||||
|
||||
|
@ -410,7 +412,7 @@ public abstract class AbstractAcmeClient implements AcmeClient {
|
|||
}
|
||||
|
||||
@Override
|
||||
public URI requestCertificate(Registration registration, byte[] csr) throws AcmeException {
|
||||
public CertificateURIs requestCertificate(Registration registration, byte[] csr) throws AcmeException {
|
||||
if (registration == null) {
|
||||
throw new NullPointerException("registration must not be null");
|
||||
}
|
||||
|
@ -434,12 +436,41 @@ public abstract class AbstractAcmeClient implements AcmeClient {
|
|||
// Optionally returns the certificate. Currently it is just ignored.
|
||||
// X509Certificate cert = conn.readCertificate();
|
||||
|
||||
return conn.getLocation();
|
||||
return new CertificateURIs(conn.getLocation(), conn.getLink("up"));
|
||||
} catch (IOException ex) {
|
||||
throw new AcmeNetworkException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] downloadCertificateChain(URI chainCertUri) throws AcmeException {
|
||||
if (chainCertUri == null) {
|
||||
throw new NullPointerException("certChainUri must not be null");
|
||||
}
|
||||
|
||||
LOG.debug("getCertificateChain");
|
||||
|
||||
List<X509Certificate> certChain = new ArrayList<>();
|
||||
URI link = chainCertUri;
|
||||
while (link != null && certChain.size() < MAX_CHAIN_LENGTH) {
|
||||
try (Connection conn = createConnection()) {
|
||||
int rc = conn.sendRequest(chainCertUri);
|
||||
if (rc != HttpURLConnection.HTTP_OK) {
|
||||
conn.throwAcmeException();
|
||||
}
|
||||
|
||||
certChain.add(conn.readCertificate());
|
||||
link = conn.getLink("up");
|
||||
} catch (IOException ex) {
|
||||
throw new AcmeNetworkException(ex);
|
||||
}
|
||||
}
|
||||
if (link != null)
|
||||
throw new AcmeException("Recursion limit reached (" + MAX_CHAIN_LENGTH + "). Didn't get " + link);
|
||||
|
||||
return certChain.toArray(new X509Certificate[certChain.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate downloadCertificate(URI certUri) throws AcmeException {
|
||||
if (certUri == null) {
|
||||
|
|
|
@ -32,6 +32,7 @@ import org.jose4j.lang.JoseException;
|
|||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.shredzone.acme4j.Authorization;
|
||||
import org.shredzone.acme4j.CertificateURIs;
|
||||
import org.shredzone.acme4j.Registration;
|
||||
import org.shredzone.acme4j.Status;
|
||||
import org.shredzone.acme4j.challenge.Challenge;
|
||||
|
@ -56,6 +57,7 @@ public class AbstractAcmeClientTest {
|
|||
|
||||
private URI resourceUri;
|
||||
private URI locationUri;
|
||||
private URI certChainUri;
|
||||
private URI agreementUri;
|
||||
private KeyPair accountKeyPair;
|
||||
private Registration testRegistration;
|
||||
|
@ -64,6 +66,7 @@ public class AbstractAcmeClientTest {
|
|||
public void setup() throws IOException, URISyntaxException {
|
||||
resourceUri = new URI("https://example.com/acme/some-resource");
|
||||
locationUri = new URI("https://example.com/acme/some-location");
|
||||
certChainUri = new URI("https://example.com/acme/some-url");
|
||||
agreementUri = new URI("http://example.com/agreement.pdf");
|
||||
accountKeyPair = TestUtils.createKeyPair();
|
||||
testRegistration = new Registration(accountKeyPair);
|
||||
|
@ -469,15 +472,21 @@ public class AbstractAcmeClientTest {
|
|||
public URI getLocation() {
|
||||
return locationUri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getLink(String relation) {
|
||||
return certChainUri;
|
||||
}
|
||||
};
|
||||
|
||||
TestableAbstractAcmeClient client = new TestableAbstractAcmeClient(connection);
|
||||
client.putTestResource(Resource.NEW_CERT, resourceUri);
|
||||
|
||||
byte[] csr = TestUtils.getResourceAsByteArray("/csr.der");
|
||||
URI certUri = client.requestCertificate(testRegistration, csr);
|
||||
CertificateURIs certUris = client.requestCertificate(testRegistration, csr);
|
||||
|
||||
assertThat(certUri, is(locationUri));
|
||||
assertThat(certUris.getCertUri(), is(locationUri));
|
||||
assertThat(certUris.getChainCertUri(), is(certChainUri));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -18,7 +18,6 @@ import java.io.FileReader;
|
|||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.net.URI;
|
||||
import java.security.KeyPair;
|
||||
import java.security.Security;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
@ -51,6 +50,7 @@ public class ClientTest {
|
|||
private static final File USER_KEY_FILE = new File("user.key");
|
||||
private static final File DOMAIN_KEY_FILE = new File("domain.key");
|
||||
private static final File DOMAIN_CERT_FILE = new File("domain.crt");
|
||||
private static final File CERT_CHAIN_FILE = new File("chain.crt");
|
||||
private static final File DOMAIN_CSR_FILE = new File("domain.csr");
|
||||
|
||||
private static final int KEY_SIZE = 2048;
|
||||
|
@ -176,16 +176,23 @@ public class ClientTest {
|
|||
}
|
||||
|
||||
// Request a signed certificate
|
||||
URI certificateUri = client.requestCertificate(reg, csrb.getEncoded());
|
||||
CertificateURIs certificateUris = client.requestCertificate(reg, csrb.getEncoded());
|
||||
LOG.info("Success! The certificate for domains " + domains + " has been generated!");
|
||||
LOG.info("Certificate URI: " + certificateUri);
|
||||
LOG.info("Certificate URI: " + certificateUris.getCertUri());
|
||||
LOG.info("Certificate Chain URI: " + certificateUris.getChainCertUri());
|
||||
|
||||
// Download the certificate
|
||||
X509Certificate cert = client.downloadCertificate(certificateUri);
|
||||
X509Certificate cert = client.downloadCertificate(certificateUris.getCertUri());
|
||||
try (FileWriter fw = new FileWriter(DOMAIN_CERT_FILE)) {
|
||||
CertificateUtils.writeX509Certificate(cert, fw);
|
||||
}
|
||||
|
||||
// Download the certificate chain
|
||||
X509Certificate[] chain = client.downloadCertificateChain(certificateUris.getChainCertUri());
|
||||
try (FileWriter fw = new FileWriter(CERT_CHAIN_FILE)) {
|
||||
CertificateUtils.writeX509CertificateChain(chain, fw);
|
||||
}
|
||||
|
||||
// Revoke the certificate (uncomment if needed...)
|
||||
// client.revokeCertificate(reg, cert);
|
||||
}
|
||||
|
|
|
@ -94,6 +94,21 @@ public final class CertificateUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an X.509 certificate chain PEM file.
|
||||
*
|
||||
* @param chain
|
||||
* {@link X509Certificate[]} to write
|
||||
* @param w
|
||||
* {@link Writer} to write the PEM file to
|
||||
*/
|
||||
public static void writeX509CertificateChain(X509Certificate[] chain, Writer w) throws IOException {
|
||||
try (JcaPEMWriter jw = new JcaPEMWriter(w)) {
|
||||
for (X509Certificate cert : chain)
|
||||
jw.writeObject(cert);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a CSR PEM file.
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue