Skip to content

Commit

Permalink
Merge pull request #524 from sigstore/addCertHelpers
Browse files Browse the repository at this point in the history
Add some new helpers to Certificates
  • Loading branch information
loosebazooka authored Sep 19, 2023
2 parents afcac83 + 03c4f97 commit 5d82c81
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 13 deletions.
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));
}
}

0 comments on commit 5d82c81

Please sign in to comment.