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.URL;
import java.security.KeyPair;
import java.security.Principal;
import java.security.Security;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
@ -134,6 +135,43 @@ public class Certificate extends AcmeResource {
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
* 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 org.assertj.core.api.Assertions.assertThat;
import static org.shredzone.acme4j.toolbox.TestUtils.getJSON;
import static org.shredzone.acme4j.toolbox.TestUtils.url;
import static org.shredzone.acme4j.toolbox.TestUtils.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@ -49,6 +48,8 @@ public class CertificateTest {
private final URL resourceUrl = url("http://example.com/acme/resource");
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.
@ -56,26 +57,32 @@ public class CertificateTest {
@Test
public void testDownload() throws Exception {
var originalCert = TestUtils.createCertificate("/cert.pem");
var alternateCert = TestUtils.createCertificate("/certid-cert.pem");
var provider = new TestableConnectionProvider() {
List<X509Certificate> sendCert;
@Override
public int sendCertificateRequest(URL url, Login login) {
assertThat(url).isEqualTo(locationUrl);
assertThat(url).isIn(locationUrl, alternate1Url, alternate2Url);
assertThat(login).isNotNull();
if (locationUrl.equals(url)) {
sendCert = originalCert;
} else {
sendCert = alternateCert;
}
return HttpURLConnection.HTTP_OK;
}
@Override
public List<X509Certificate> readCertificates() {
return originalCert;
return sendCert;
}
@Override
public Collection<URL> getLinks(String relation) {
assertThat(relation).isEqualTo("alternate");
return Arrays.asList(
url("https://example.com/acme/alt-cert/1"),
url("https://example.com/acme/alt-cert/2"));
return Arrays.asList(alternate1Url, alternate2Url);
}
};
@ -108,21 +115,33 @@ public class CertificateTest {
}
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()).hasSize(2);
assertThat(cert.getAlternates()).element(0).isEqualTo(url("https://example.com/acme/alt-cert/1"));
assertThat(cert.getAlternates()).element(1).isEqualTo(url("https://example.com/acme/alt-cert/2"));
assertThat(cert.getAlternates()).element(0).isEqualTo(alternate1Url);
assertThat(cert.getAlternates()).element(1).isEqualTo(alternate2Url);
assertThat(cert.getAlternateCertificates()).isNotNull();
assertThat(cert.getAlternateCertificates()).hasSize(2);
assertThat(cert.getAlternateCertificates())
.element(0)
.extracting(Certificate::getLocation)
.isEqualTo(url("https://example.com/acme/alt-cert/1"));
.isEqualTo(alternate1Url);
assertThat(cert.getAlternateCertificates())
.element(1)
.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();
}

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 CERT_ISSUER = "Pebble Intermediate CA 645fc5";
public static final NetworkSettings DEFAULT_NETWORK_SETTINGS = new NetworkSettings();
private TestUtils() {