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