mirror of https://github.com/shred/acme4j
				
				
				
			Add alternate certificates support
							parent
							
								
									7d83ef0e80
								
							
						
					
					
						commit
						4c34f9afb5
					
				| 
						 | 
					@ -14,14 +14,18 @@
 | 
				
			||||||
package org.shredzone.acme4j;
 | 
					package org.shredzone.acme4j;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import static java.util.Collections.unmodifiableList;
 | 
					import static java.util.Collections.unmodifiableList;
 | 
				
			||||||
 | 
					import static java.util.stream.Collectors.toCollection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.io.IOException;
 | 
					import java.io.IOException;
 | 
				
			||||||
import java.io.Writer;
 | 
					import java.io.Writer;
 | 
				
			||||||
import java.net.HttpURLConnection;
 | 
					import java.net.HttpURLConnection;
 | 
				
			||||||
 | 
					import java.net.URI;
 | 
				
			||||||
import java.net.URL;
 | 
					import java.net.URL;
 | 
				
			||||||
import java.security.cert.CertificateEncodingException;
 | 
					import java.security.cert.CertificateEncodingException;
 | 
				
			||||||
import java.security.cert.X509Certificate;
 | 
					import java.security.cert.X509Certificate;
 | 
				
			||||||
import java.util.ArrayList;
 | 
					import java.util.ArrayList;
 | 
				
			||||||
 | 
					import java.util.Collection;
 | 
				
			||||||
 | 
					import java.util.Collections;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.shredzone.acme4j.connector.Connection;
 | 
					import org.shredzone.acme4j.connector.Connection;
 | 
				
			||||||
| 
						 | 
					@ -36,12 +40,16 @@ import org.slf4j.LoggerFactory;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Represents a certificate and its certificate chain.
 | 
					 * 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 {
 | 
					public class Certificate extends AcmeResource {
 | 
				
			||||||
    private static final long serialVersionUID = 7381527770159084201L;
 | 
					    private static final long serialVersionUID = 7381527770159084201L;
 | 
				
			||||||
    private static final Logger LOG = LoggerFactory.getLogger(Certificate.class);
 | 
					    private static final Logger LOG = LoggerFactory.getLogger(Certificate.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private ArrayList<X509Certificate> certChain = null;
 | 
					    private ArrayList<X509Certificate> certChain = null;
 | 
				
			||||||
 | 
					    private ArrayList<URL> alternates = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected Certificate(Session session, URL certUrl) {
 | 
					    protected Certificate(Session session, URL certUrl) {
 | 
				
			||||||
        super(session);
 | 
					        super(session);
 | 
				
			||||||
| 
						 | 
					@ -73,6 +81,14 @@ public class Certificate extends AcmeResource {
 | 
				
			||||||
            try (Connection conn = getSession().provider().connect()) {
 | 
					            try (Connection conn = getSession().provider().connect()) {
 | 
				
			||||||
                conn.sendRequest(getLocation(), getSession());
 | 
					                conn.sendRequest(getLocation(), getSession());
 | 
				
			||||||
                conn.accept(HttpURLConnection.HTTP_OK);
 | 
					                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());
 | 
					                certChain = new ArrayList<>(conn.readCertificates());
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					@ -100,6 +116,20 @@ public class Certificate extends AcmeResource {
 | 
				
			||||||
        return unmodifiableList(certChain);
 | 
					        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
 | 
					     * 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.
 | 
					     * end-entity cert coming first, followed by the intermediate ceritificates.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,13 +14,12 @@
 | 
				
			||||||
package org.shredzone.acme4j.connector;
 | 
					package org.shredzone.acme4j.connector;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import static java.util.stream.Collectors.toList;
 | 
					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.IOException;
 | 
				
			||||||
import java.io.InputStream;
 | 
					import java.io.InputStream;
 | 
				
			||||||
import java.io.OutputStream;
 | 
					import java.io.OutputStream;
 | 
				
			||||||
import java.net.HttpURLConnection;
 | 
					import java.net.HttpURLConnection;
 | 
				
			||||||
import java.net.MalformedURLException;
 | 
					 | 
				
			||||||
import java.net.URI;
 | 
					import java.net.URI;
 | 
				
			||||||
import java.net.URISyntaxException;
 | 
					import java.net.URISyntaxException;
 | 
				
			||||||
import java.net.URL;
 | 
					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.UnsupportedEncodingException;
 | 
				
			||||||
import java.io.Writer;
 | 
					import java.io.Writer;
 | 
				
			||||||
import java.net.IDN;
 | 
					import java.net.IDN;
 | 
				
			||||||
 | 
					import java.net.MalformedURLException;
 | 
				
			||||||
 | 
					import java.net.URI;
 | 
				
			||||||
 | 
					import java.net.URL;
 | 
				
			||||||
import java.security.MessageDigest;
 | 
					import java.security.MessageDigest;
 | 
				
			||||||
import java.security.NoSuchAlgorithmException;
 | 
					import java.security.NoSuchAlgorithmException;
 | 
				
			||||||
import java.time.Instant;
 | 
					import java.time.Instant;
 | 
				
			||||||
| 
						 | 
					@ -273,4 +276,21 @@ public final class AcmeUtils {
 | 
				
			||||||
        out.append("\n-----END ").append(label.toString()).append("-----\n");
 | 
					        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.InputStream;
 | 
				
			||||||
import java.io.OutputStreamWriter;
 | 
					import java.io.OutputStreamWriter;
 | 
				
			||||||
import java.net.HttpURLConnection;
 | 
					import java.net.HttpURLConnection;
 | 
				
			||||||
 | 
					import java.net.URI;
 | 
				
			||||||
import java.net.URL;
 | 
					import java.net.URL;
 | 
				
			||||||
import java.security.cert.X509Certificate;
 | 
					import java.security.cert.X509Certificate;
 | 
				
			||||||
 | 
					import java.util.Arrays;
 | 
				
			||||||
 | 
					import java.util.Collection;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.junit.Test;
 | 
					import org.junit.Test;
 | 
				
			||||||
| 
						 | 
					@ -66,6 +69,14 @@ public class CertificateTest {
 | 
				
			||||||
            public List<X509Certificate> readCertificates() throws AcmeException {
 | 
					            public List<X509Certificate> readCertificates() throws AcmeException {
 | 
				
			||||||
                return originalCert;
 | 
					                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);
 | 
					        Certificate cert = new Certificate(provider.createSession(), locationUrl);
 | 
				
			||||||
| 
						 | 
					@ -99,6 +110,11 @@ public class CertificateTest {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        assertThat(writtenPem, is(originalPem));
 | 
					        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();
 | 
					        provider.close();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -138,6 +154,12 @@ public class CertificateTest {
 | 
				
			||||||
                assertThat(certRequested, is(true));
 | 
					                assertThat(certRequested, is(true));
 | 
				
			||||||
                return originalCert;
 | 
					                return originalCert;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            @Override
 | 
				
			||||||
 | 
					            public Collection<URI> getLinks(String relation) {
 | 
				
			||||||
 | 
					                assertThat(relation, is("alternate"));
 | 
				
			||||||
 | 
					                return null;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        provider.putTestResource(Resource.REVOKE_CERT, resourceUrl);
 | 
					        provider.putTestResource(Resource.REVOKE_CERT, resourceUrl);
 | 
				
			||||||
| 
						 | 
					@ -184,6 +206,12 @@ public class CertificateTest {
 | 
				
			||||||
                assertThat(certRequested, is(true));
 | 
					                assertThat(certRequested, is(true));
 | 
				
			||||||
                return originalCert;
 | 
					                return originalCert;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            @Override
 | 
				
			||||||
 | 
					            public Collection<URI> getLinks(String relation) {
 | 
				
			||||||
 | 
					                assertThat(relation, is("alternate"));
 | 
				
			||||||
 | 
					                return null;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        provider.putTestResource(Resource.REVOKE_CERT, resourceUrl);
 | 
					        provider.putTestResource(Resource.REVOKE_CERT, resourceUrl);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,6 +24,9 @@ import java.io.OutputStreamWriter;
 | 
				
			||||||
import java.io.Writer;
 | 
					import java.io.Writer;
 | 
				
			||||||
import java.lang.reflect.Constructor;
 | 
					import java.lang.reflect.Constructor;
 | 
				
			||||||
import java.lang.reflect.Modifier;
 | 
					import java.lang.reflect.Modifier;
 | 
				
			||||||
 | 
					import java.net.MalformedURLException;
 | 
				
			||||||
 | 
					import java.net.URI;
 | 
				
			||||||
 | 
					import java.net.URL;
 | 
				
			||||||
import java.security.KeyPair;
 | 
					import java.security.KeyPair;
 | 
				
			||||||
import java.security.Security;
 | 
					import java.security.Security;
 | 
				
			||||||
import java.security.cert.CertificateEncodingException;
 | 
					import java.security.cert.CertificateEncodingException;
 | 
				
			||||||
| 
						 | 
					@ -286,6 +289,18 @@ public class AcmeUtilsTest {
 | 
				
			||||||
        assertThat(pemFile.toByteArray(), is(originalFile.toByteArray()));
 | 
					        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.
 | 
					     * Matches the given time.
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue