Find certificates by issuer

pull/140/head
Richard Körber 2023-05-20 17:20:13 +02:00
parent 7f20545e14
commit a648a513f6
No known key found for this signature in database
GPG Key ID: AAB9FD19C78AA3E0
3 changed files with 70 additions and 11 deletions

View File

@ -22,6 +22,7 @@ import java.io.Writer;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.Principal;
import java.security.Security; import java.security.Security;
import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
@ -134,6 +135,43 @@ public class Certificate extends AcmeResource {
return alternateCerts; return alternateCerts;
} }
/**
* Checks if this certificate was issued by the given issuer name.
*
* @param issuer
* Issuer name to check against, case-sensitive
* @return {@code true} if this issuer name was found in the certificate chain as
* issuer, {@code false} otherwise.
* @since 3.0.0
*/
public boolean isIssuedBy(String issuer) {
var issuerCn = "CN=" + issuer;
return getCertificateChain().stream()
.map(X509Certificate::getIssuerX500Principal)
.map(Principal::getName)
.anyMatch(issuerCn::equals);
}
/**
* Finds a {@link Certificate} that was issued by the given issuer name.
*
* @param issuer
* Issuer name to check against, case-sensitive
* @return Certificate that was issued by that issuer, or {@code empty} if there was
* none. The returned {@link Certificate} may be this instance, or one of the
* {@link #getAlternateCertificates()} instances. If multiple certificates are issued
* by that issuer, the first one that was found is returned.
* @since 3.0.0
*/
public Optional<Certificate> findCertificate(String issuer) {
if (isIssuedBy(issuer)) {
return Optional.of(this);
}
return getAlternateCertificates().stream()
.filter(c -> c.isIssuedBy(issuer))
.findFirst();
}
/** /**
* 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 certificates. * end-entity cert coming first, followed by the intermediate certificates.

View File

@ -15,8 +15,7 @@ package org.shredzone.acme4j;
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson; import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.shredzone.acme4j.toolbox.TestUtils.getJSON; import static org.shredzone.acme4j.toolbox.TestUtils.*;
import static org.shredzone.acme4j.toolbox.TestUtils.url;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
@ -49,6 +48,8 @@ public class CertificateTest {
private final URL resourceUrl = url("http://example.com/acme/resource"); private final URL resourceUrl = url("http://example.com/acme/resource");
private final URL locationUrl = url("http://example.com/acme/certificate"); private final URL locationUrl = url("http://example.com/acme/certificate");
private final URL alternate1Url = url("https://example.com/acme/alt-cert/1");
private final URL alternate2Url = url("https://example.com/acme/alt-cert/2");
/** /**
* Test that a certificate can be downloaded. * Test that a certificate can be downloaded.
@ -56,26 +57,32 @@ public class CertificateTest {
@Test @Test
public void testDownload() throws Exception { public void testDownload() throws Exception {
var originalCert = TestUtils.createCertificate("/cert.pem"); var originalCert = TestUtils.createCertificate("/cert.pem");
var alternateCert = TestUtils.createCertificate("/certid-cert.pem");
var provider = new TestableConnectionProvider() { var provider = new TestableConnectionProvider() {
List<X509Certificate> sendCert;
@Override @Override
public int sendCertificateRequest(URL url, Login login) { public int sendCertificateRequest(URL url, Login login) {
assertThat(url).isEqualTo(locationUrl); assertThat(url).isIn(locationUrl, alternate1Url, alternate2Url);
assertThat(login).isNotNull(); assertThat(login).isNotNull();
if (locationUrl.equals(url)) {
sendCert = originalCert;
} else {
sendCert = alternateCert;
}
return HttpURLConnection.HTTP_OK; return HttpURLConnection.HTTP_OK;
} }
@Override @Override
public List<X509Certificate> readCertificates() { public List<X509Certificate> readCertificates() {
return originalCert; return sendCert;
} }
@Override @Override
public Collection<URL> getLinks(String relation) { public Collection<URL> getLinks(String relation) {
assertThat(relation).isEqualTo("alternate"); assertThat(relation).isEqualTo("alternate");
return Arrays.asList( return Arrays.asList(alternate1Url, alternate2Url);
url("https://example.com/acme/alt-cert/1"),
url("https://example.com/acme/alt-cert/2"));
} }
}; };
@ -108,21 +115,33 @@ public class CertificateTest {
} }
assertThat(writtenPem).isEqualTo(originalPem); assertThat(writtenPem).isEqualTo(originalPem);
assertThat(cert.isIssuedBy("The ACME CA X1")).isFalse();
assertThat(cert.isIssuedBy(CERT_ISSUER)).isTrue();
assertThat(cert.getAlternates()).isNotNull(); assertThat(cert.getAlternates()).isNotNull();
assertThat(cert.getAlternates()).hasSize(2); assertThat(cert.getAlternates()).hasSize(2);
assertThat(cert.getAlternates()).element(0).isEqualTo(url("https://example.com/acme/alt-cert/1")); assertThat(cert.getAlternates()).element(0).isEqualTo(alternate1Url);
assertThat(cert.getAlternates()).element(1).isEqualTo(url("https://example.com/acme/alt-cert/2")); assertThat(cert.getAlternates()).element(1).isEqualTo(alternate2Url);
assertThat(cert.getAlternateCertificates()).isNotNull(); assertThat(cert.getAlternateCertificates()).isNotNull();
assertThat(cert.getAlternateCertificates()).hasSize(2); assertThat(cert.getAlternateCertificates()).hasSize(2);
assertThat(cert.getAlternateCertificates()) assertThat(cert.getAlternateCertificates())
.element(0) .element(0)
.extracting(Certificate::getLocation) .extracting(Certificate::getLocation)
.isEqualTo(url("https://example.com/acme/alt-cert/1")); .isEqualTo(alternate1Url);
assertThat(cert.getAlternateCertificates()) assertThat(cert.getAlternateCertificates())
.element(1) .element(1)
.extracting(Certificate::getLocation) .extracting(Certificate::getLocation)
.isEqualTo(url("https://example.com/acme/alt-cert/2")); .isEqualTo(alternate2Url);
assertThat(cert.findCertificate("The ACME CA X1")).
isEmpty();
assertThat(cert.findCertificate(CERT_ISSUER).orElseThrow())
.isSameAs(cert);
assertThat(cert.findCertificate("minica root ca 3a1356").orElseThrow())
.isSameAs(cert.getAlternateCertificates().get(0));
assertThat(cert.getAlternateCertificates().get(0).isIssuedBy("minica root ca 3a1356"))
.isTrue();
provider.close(); provider.close();
} }

View File

@ -74,6 +74,8 @@ public final class TestUtils {
public static final String DUMMY_NONCE = Base64.getUrlEncoder().withoutPadding().encodeToString("foo-nonce-foo".getBytes()); public static final String DUMMY_NONCE = Base64.getUrlEncoder().withoutPadding().encodeToString("foo-nonce-foo".getBytes());
public static final String CERT_ISSUER = "Pebble Intermediate CA 645fc5";
public static final NetworkSettings DEFAULT_NETWORK_SETTINGS = new NetworkSettings(); public static final NetworkSettings DEFAULT_NETWORK_SETTINGS = new NetworkSettings();
private TestUtils() { private TestUtils() {