mirror of https://github.com/shred/acme4j
add support for fetching certificate chain
parent
41e9e177be
commit
d5b4ff37dc
|
@ -135,9 +135,22 @@ public interface AcmeClient {
|
||||||
* {@link Registration} to be used for conversation
|
* {@link Registration} to be used for conversation
|
||||||
* @param csr
|
* @param csr
|
||||||
* PKCS#10 Certificate Signing Request to be sent to the server
|
* 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.
|
* Downloads a certificate.
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -28,10 +28,7 @@ import java.util.Map;
|
||||||
import org.jose4j.jwk.PublicJsonWebKey;
|
import org.jose4j.jwk.PublicJsonWebKey;
|
||||||
import org.jose4j.jws.JsonWebSignature;
|
import org.jose4j.jws.JsonWebSignature;
|
||||||
import org.jose4j.lang.JoseException;
|
import org.jose4j.lang.JoseException;
|
||||||
import org.shredzone.acme4j.AcmeClient;
|
import org.shredzone.acme4j.*;
|
||||||
import org.shredzone.acme4j.Authorization;
|
|
||||||
import org.shredzone.acme4j.Registration;
|
|
||||||
import org.shredzone.acme4j.Status;
|
|
||||||
import org.shredzone.acme4j.challenge.Challenge;
|
import org.shredzone.acme4j.challenge.Challenge;
|
||||||
import org.shredzone.acme4j.connector.Connection;
|
import org.shredzone.acme4j.connector.Connection;
|
||||||
import org.shredzone.acme4j.connector.Resource;
|
import org.shredzone.acme4j.connector.Resource;
|
||||||
|
@ -54,6 +51,7 @@ import org.slf4j.LoggerFactory;
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractAcmeClient implements AcmeClient {
|
public abstract class AbstractAcmeClient implements AcmeClient {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(AbstractAcmeClient.class);
|
private static final Logger LOG = LoggerFactory.getLogger(AbstractAcmeClient.class);
|
||||||
|
private static final int MAX_CHAIN_LENGTH = 10;
|
||||||
|
|
||||||
private final Session session = new Session();
|
private final Session session = new Session();
|
||||||
|
|
||||||
|
@ -410,7 +408,7 @@ public abstract class AbstractAcmeClient implements AcmeClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public URI requestCertificate(Registration registration, byte[] csr) throws AcmeException {
|
public CertificateURIs requestCertificate(Registration registration, byte[] csr) throws AcmeException {
|
||||||
if (registration == null) {
|
if (registration == null) {
|
||||||
throw new NullPointerException("registration must not be null");
|
throw new NullPointerException("registration must not be null");
|
||||||
}
|
}
|
||||||
|
@ -434,12 +432,42 @@ public abstract class AbstractAcmeClient implements AcmeClient {
|
||||||
// Optionally returns the certificate. Currently it is just ignored.
|
// Optionally returns the certificate. Currently it is just ignored.
|
||||||
// X509Certificate cert = conn.readCertificate();
|
// X509Certificate cert = conn.readCertificate();
|
||||||
|
|
||||||
return conn.getLocation();
|
return new CertificateURIs(conn.getLocation(), conn.getLink("up"));
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
throw new AcmeNetworkException(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
|
@Override
|
||||||
public X509Certificate downloadCertificate(URI certUri) throws AcmeException {
|
public X509Certificate downloadCertificate(URI certUri) throws AcmeException {
|
||||||
if (certUri == null) {
|
if (certUri == null) {
|
||||||
|
|
|
@ -32,6 +32,7 @@ import org.jose4j.lang.JoseException;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.shredzone.acme4j.Authorization;
|
import org.shredzone.acme4j.Authorization;
|
||||||
|
import org.shredzone.acme4j.CertificateURIs;
|
||||||
import org.shredzone.acme4j.Registration;
|
import org.shredzone.acme4j.Registration;
|
||||||
import org.shredzone.acme4j.Status;
|
import org.shredzone.acme4j.Status;
|
||||||
import org.shredzone.acme4j.challenge.Challenge;
|
import org.shredzone.acme4j.challenge.Challenge;
|
||||||
|
@ -56,6 +57,7 @@ public class AbstractAcmeClientTest {
|
||||||
|
|
||||||
private URI resourceUri;
|
private URI resourceUri;
|
||||||
private URI locationUri;
|
private URI locationUri;
|
||||||
|
private URI certChainUri;
|
||||||
private URI agreementUri;
|
private URI agreementUri;
|
||||||
private KeyPair accountKeyPair;
|
private KeyPair accountKeyPair;
|
||||||
private Registration testRegistration;
|
private Registration testRegistration;
|
||||||
|
@ -64,6 +66,7 @@ public class AbstractAcmeClientTest {
|
||||||
public void setup() throws IOException, URISyntaxException {
|
public void setup() throws IOException, URISyntaxException {
|
||||||
resourceUri = new URI("https://example.com/acme/some-resource");
|
resourceUri = new URI("https://example.com/acme/some-resource");
|
||||||
locationUri = new URI("https://example.com/acme/some-location");
|
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");
|
agreementUri = new URI("http://example.com/agreement.pdf");
|
||||||
accountKeyPair = TestUtils.createKeyPair();
|
accountKeyPair = TestUtils.createKeyPair();
|
||||||
testRegistration = new Registration(accountKeyPair);
|
testRegistration = new Registration(accountKeyPair);
|
||||||
|
@ -456,6 +459,7 @@ public class AbstractAcmeClientTest {
|
||||||
@Test
|
@Test
|
||||||
public void testRequestCertificate() throws AcmeException, IOException {
|
public void testRequestCertificate() throws AcmeException, IOException {
|
||||||
Connection connection = new DummyConnection() {
|
Connection connection = new DummyConnection() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Registration registration) {
|
public int sendSignedRequest(URI uri, ClaimBuilder claims, Session session, Registration registration) {
|
||||||
assertThat(uri, is(resourceUri));
|
assertThat(uri, is(resourceUri));
|
||||||
|
@ -469,15 +473,21 @@ public class AbstractAcmeClientTest {
|
||||||
public URI getLocation() {
|
public URI getLocation() {
|
||||||
return locationUri;
|
return locationUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URI getLink(String relation) {
|
||||||
|
return certChainUri;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
TestableAbstractAcmeClient client = new TestableAbstractAcmeClient(connection);
|
TestableAbstractAcmeClient client = new TestableAbstractAcmeClient(connection);
|
||||||
client.putTestResource(Resource.NEW_CERT, resourceUri);
|
client.putTestResource(Resource.NEW_CERT, resourceUri);
|
||||||
|
|
||||||
byte[] csr = TestUtils.getResourceAsByteArray("/csr.der");
|
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue