Add alternate certificates support

pull/55/head
Richard Körber 2017-05-02 18:36:16 +02:00
parent 7d83ef0e80
commit 4c34f9afb5
5 changed files with 94 additions and 19 deletions

View File

@ -14,14 +14,18 @@
package org.shredzone.acme4j;
import static java.util.Collections.unmodifiableList;
import static java.util.stream.Collectors.toCollection;
import java.io.IOException;
import java.io.Writer;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.shredzone.acme4j.connector.Connection;
@ -36,12 +40,16 @@ import org.slf4j.LoggerFactory;
/**
* Represents a certificate and its certificate chain.
* <p>
* Note that a certificate is immutable once it is issued. For renewal, a new certificate
* must be ordered.
*/
public class Certificate extends AcmeResource {
private static final long serialVersionUID = 7381527770159084201L;
private static final Logger LOG = LoggerFactory.getLogger(Certificate.class);
private ArrayList<X509Certificate> certChain = null;
private ArrayList<URL> alternates = null;
protected Certificate(Session session, URL certUrl) {
super(session);
@ -73,6 +81,14 @@ public class Certificate extends AcmeResource {
try (Connection conn = getSession().provider().connect()) {
conn.sendRequest(getLocation(), getSession());
conn.accept(HttpURLConnection.HTTP_OK);
Collection<URI> alternateList = conn.getLinks("alternate");
if (alternateList != null) {
alternates = alternateList.stream()
.map(AcmeUtils::toURL)
.collect(toCollection(ArrayList::new));
}
certChain = new ArrayList<>(conn.readCertificates());
}
}
@ -100,6 +116,20 @@ public class Certificate extends AcmeResource {
return unmodifiableList(certChain);
}
/**
* Returns URLs to alternate certificate chains.
*
* @return Alternate certificate chains, or empty if there are none.
*/
public List<URL> getAlternates() {
lazyDownload();
if (alternates != null) {
return unmodifiableList(alternates);
} else {
return Collections.emptyList();
}
}
/**
* 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.

View File

@ -14,13 +14,12 @@
package org.shredzone.acme4j.connector;
import static java.util.stream.Collectors.toList;
import static org.shredzone.acme4j.util.AcmeUtils.keyAlgorithm;
import static org.shredzone.acme4j.util.AcmeUtils.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
@ -496,21 +495,4 @@ public class DefaultConnection implements Connection {
}
}
/**
* Converts {@link URI} to {@link URL}.
*
* @param uri
* {@link URI} to convert
* @return {@link URL}
* @throws AcmeProtocolException
* if the URI could not be converted to URL
*/
private static URL toURL(URI uri) {
try {
return uri != null ? uri.toURL() : null;
} catch (MalformedURLException ex) {
throw new AcmeProtocolException("Invalid URL: " + uri, ex);
}
}
}

View File

@ -17,6 +17,9 @@ import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.net.IDN;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
@ -273,4 +276,21 @@ public final class AcmeUtils {
out.append("\n-----END ").append(label.toString()).append("-----\n");
}
/**
* Converts {@link URI} to {@link URL}.
*
* @param uri
* {@link URI} to convert
* @return {@link URL}
* @throws AcmeProtocolException
* if the URI could not be converted to URL
*/
public static URL toURL(URI uri) {
try {
return uri != null ? uri.toURL() : null;
} catch (MalformedURLException ex) {
throw new AcmeProtocolException("Invalid URL: " + uri, ex);
}
}
}

View File

@ -23,8 +23,11 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.junit.Test;
@ -66,6 +69,14 @@ public class CertificateTest {
public List<X509Certificate> readCertificates() throws AcmeException {
return originalCert;
}
@Override
public Collection<URI> getLinks(String relation) {
assertThat(relation, is("alternate"));
return Arrays.asList(
URI.create("https://example.com/acme/alt-cert/1"),
URI.create("https://example.com/acme/alt-cert/2"));
}
};
Certificate cert = new Certificate(provider.createSession(), locationUrl);
@ -99,6 +110,11 @@ public class CertificateTest {
}
assertThat(writtenPem, is(originalPem));
assertThat(cert.getAlternates(), is(notNullValue()));
assertThat(cert.getAlternates().size(), is(2));
assertThat(cert.getAlternates().get(0), is(url("https://example.com/acme/alt-cert/1")));
assertThat(cert.getAlternates().get(1), is(url("https://example.com/acme/alt-cert/2")));
provider.close();
}
@ -138,6 +154,12 @@ public class CertificateTest {
assertThat(certRequested, is(true));
return originalCert;
}
@Override
public Collection<URI> getLinks(String relation) {
assertThat(relation, is("alternate"));
return null;
}
};
provider.putTestResource(Resource.REVOKE_CERT, resourceUrl);
@ -184,6 +206,12 @@ public class CertificateTest {
assertThat(certRequested, is(true));
return originalCert;
}
@Override
public Collection<URI> getLinks(String relation) {
assertThat(relation, is("alternate"));
return null;
}
};
provider.putTestResource(Resource.REVOKE_CERT, resourceUrl);

View File

@ -24,6 +24,9 @@ import java.io.OutputStreamWriter;
import java.io.Writer;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.security.KeyPair;
import java.security.Security;
import java.security.cert.CertificateEncodingException;
@ -286,6 +289,18 @@ public class AcmeUtilsTest {
assertThat(pemFile.toByteArray(), is(originalFile.toByteArray()));
}
/**
* Test {@link AcmeUtils#toURL(URI)}.
*/
@Test
public void testToURL() throws MalformedURLException {
URI testUri = URI.create("https://example.com/foo/123");
URL testUrl = testUri.toURL();
assertThat(AcmeUtils.toURL(testUri), is(testUrl));
assertThat(AcmeUtils.toURL(null), is(nullValue()));
}
/**
* Matches the given time.
*/