mirror of https://github.com/shred/acme4j
Add alternate certificates support
parent
7d83ef0e80
commit
4c34f9afb5
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue