Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add some new helpers to Certificates #524

Merged
merged 2 commits into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,13 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;

public class Certificates {

private static final String SCT_X509_OID = "1.3.6.1.4.1.11129.2.4.2";

/** Convert a certificate to a PEM encoded certificate. */
public static String toPemString(Certificate cert) throws IOException {
var certWriter = new StringWriter();
Expand Down Expand Up @@ -137,15 +140,69 @@ public static CertPath toCertPath(Certificate certificate) throws CertificateExc
return cf.generateCertPath(Collections.singletonList(certificate));
}

/** Appends an X509Certificate to a {@link CertPath} as a leaf. */
public static CertPath appendCertPath(CertPath root, Certificate certificate)
/** Appends a CertPath to another {@link CertPath} as children. */
public static CertPath appendCertPath(CertPath parent, Certificate child)
throws CertificateException {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
List<Certificate> certs =
ImmutableList.<Certificate>builder()
.add(certificate)
.addAll(root.getCertificates())
.build();
ImmutableList.<Certificate>builder().add(child).addAll(parent.getCertificates()).build();
return cf.generateCertPath(certs);
}

/**
* Trims a parent CertPath from a provided CertPath. This is intended to be used to trim trusted
* root and intermediates from a full CertPath to reveal just the untrusted parts which can be
* distributed as part of a signature tuple or bundle.
*
* @param certPath a certificate path to trim from
* @param parentPath the parent certPath to trim off the full certPath
* @return a trimmed path
* @throws IllegalArgumentException if the trimPath is not a parent of the certPath or if they are
* the same length
* @throws CertificateException if an error occurs during CertPath construction
*/
public static CertPath trimParent(CertPath certPath, CertPath parentPath)
throws CertificateException {
if (!containsParent(certPath, parentPath)) {
throw new IllegalArgumentException("trim path was not the parent of the provider chain");
}
var certs = certPath.getCertificates();
var parent = parentPath.getCertificates();
CertificateFactory cf = CertificateFactory.getInstance("X.509");
return cf.generateCertPath(certs.subList(0, certs.size() - parent.size()));
}

/** Check if a parent certpath is the suffix of a certpath */
public static boolean containsParent(CertPath certPath, CertPath parentPath) {
var certs = certPath.getCertificates();
var parent = parentPath.getCertificates();
return parent.size() <= certs.size()
&& certs.subList(certs.size() - parent.size(), certs.size()).equals(parent);
}

/**
* Find and return any SCTs embedded in a certificate.
*
* @param certificate the certificate with embedded scts
* @return a byte array containing any number of embedded scts
*/
public static Optional<byte[]> getEmbeddedSCTs(Certificate certificate) {
return Optional.ofNullable(((X509Certificate) certificate).getExtensionValue(SCT_X509_OID));
}

/** Check if a certificate is self-signed. */
public static boolean isSelfSigned(Certificate certificate) {
return ((X509Certificate) certificate)
.getIssuerX500Principal()
.equals(((X509Certificate) certificate).getSubjectX500Principal());
}

/** Check if the root of a CertPath is self-signed */
public static boolean isSelfSigned(CertPath certPath) {
return isSelfSigned(certPath.getCertificates().get(certPath.getCertificates().size() - 1));
}

public static X509Certificate getLeaf(CertPath certPath) {
return (X509Certificate) certPath.getCertificates().get(0);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.ArrayList;
import java.util.List;
import org.bouncycastle.util.encoders.Base64;
Expand Down Expand Up @@ -134,16 +135,55 @@ public void toCertPath() throws Exception {

@Test
public void appendCertPath() throws Exception {
var certPath =
var parent =
Certificates.fromPemChain(Resources.toByteArray(Resources.getResource(CERT_CHAIN)));
var cert = Certificates.fromPem(Resources.toByteArray(Resources.getResource(CERT_GH)));
var child = Certificates.fromPem(Resources.toByteArray(Resources.getResource(CERT_GH)));

Assertions.assertEquals(2, certPath.getCertificates().size());
var appended = Certificates.appendCertPath(certPath, cert);
Assertions.assertEquals(2, parent.getCertificates().size());
var appended = Certificates.appendCertPath(parent, child);

Assertions.assertEquals(3, appended.getCertificates().size());
Assertions.assertEquals(cert, appended.getCertificates().get(0));
Assertions.assertEquals(certPath.getCertificates().get(0), appended.getCertificates().get(1));
Assertions.assertEquals(certPath.getCertificates().get(1), appended.getCertificates().get(2));
Assertions.assertEquals(child, appended.getCertificates().get(0));
Assertions.assertEquals(parent.getCertificates().get(0), appended.getCertificates().get(1));
Assertions.assertEquals(parent.getCertificates().get(1), appended.getCertificates().get(2));
}

@Test
public void trimParent() throws Exception {
var certPath =
Certificates.fromPemChain(Resources.toByteArray(Resources.getResource(CERT_CHAIN)));
var parent =
CertificateFactory.getInstance("X.509")
.generateCertPath(List.of(certPath.getCertificates().get(1)));

var trimmed = Certificates.trimParent(certPath, parent);

Assertions.assertEquals(1, trimmed.getCertificates().size());
Assertions.assertEquals(certPath.getCertificates().get(0), trimmed.getCertificates().get(0));
}

@Test
public void containsParent() throws Exception {
var certPath =
Certificates.fromPemChain(Resources.toByteArray(Resources.getResource(CERT_CHAIN)));
var parent =
CertificateFactory.getInstance("X.509")
.generateCertPath(List.of(certPath.getCertificates().get(1)));
var cert = Certificates.fromPemChain(Resources.toByteArray(Resources.getResource(CERT)));

Assertions.assertTrue(Certificates.containsParent(certPath, parent));
Assertions.assertFalse(Certificates.containsParent(cert, certPath));
Assertions.assertTrue(Certificates.containsParent(certPath, certPath));
Assertions.assertTrue(Certificates.containsParent(cert, cert));
}

@Test
public void isSelfSigned() throws Exception {
var certPath =
Certificates.fromPemChain(Resources.toByteArray(Resources.getResource(CERT_CHAIN)));
var cert = Certificates.fromPem(Resources.toByteArray(Resources.getResource(CERT)));

Assertions.assertTrue(Certificates.isSelfSigned(certPath));
Assertions.assertFalse(Certificates.isSelfSigned(cert));
}
}