Skip to content

Commit 5d82c81

Browse files
authored
Merge pull request #524 from sigstore/addCertHelpers
Add some new helpers to Certificates
2 parents afcac83 + 03c4f97 commit 5d82c81

File tree

2 files changed

+110
-13
lines changed

2 files changed

+110
-13
lines changed

sigstore-java/src/main/java/dev/sigstore/encryption/certificates/Certificates.java

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,13 @@
2626
import java.util.ArrayList;
2727
import java.util.Collections;
2828
import java.util.List;
29+
import java.util.Optional;
2930
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
3031

3132
public class Certificates {
3233

34+
private static final String SCT_X509_OID = "1.3.6.1.4.1.11129.2.4.2";
35+
3336
/** Convert a certificate to a PEM encoded certificate. */
3437
public static String toPemString(Certificate cert) throws IOException {
3538
var certWriter = new StringWriter();
@@ -137,15 +140,69 @@ public static CertPath toCertPath(Certificate certificate) throws CertificateExc
137140
return cf.generateCertPath(Collections.singletonList(certificate));
138141
}
139142

140-
/** Appends an X509Certificate to a {@link CertPath} as a leaf. */
141-
public static CertPath appendCertPath(CertPath root, Certificate certificate)
143+
/** Appends a CertPath to another {@link CertPath} as children. */
144+
public static CertPath appendCertPath(CertPath parent, Certificate child)
142145
throws CertificateException {
143146
CertificateFactory cf = CertificateFactory.getInstance("X.509");
144147
List<Certificate> certs =
145-
ImmutableList.<Certificate>builder()
146-
.add(certificate)
147-
.addAll(root.getCertificates())
148-
.build();
148+
ImmutableList.<Certificate>builder().add(child).addAll(parent.getCertificates()).build();
149149
return cf.generateCertPath(certs);
150150
}
151+
152+
/**
153+
* Trims a parent CertPath from a provided CertPath. This is intended to be used to trim trusted
154+
* root and intermediates from a full CertPath to reveal just the untrusted parts which can be
155+
* distributed as part of a signature tuple or bundle.
156+
*
157+
* @param certPath a certificate path to trim from
158+
* @param parentPath the parent certPath to trim off the full certPath
159+
* @return a trimmed path
160+
* @throws IllegalArgumentException if the trimPath is not a parent of the certPath or if they are
161+
* the same length
162+
* @throws CertificateException if an error occurs during CertPath construction
163+
*/
164+
public static CertPath trimParent(CertPath certPath, CertPath parentPath)
165+
throws CertificateException {
166+
if (!containsParent(certPath, parentPath)) {
167+
throw new IllegalArgumentException("trim path was not the parent of the provider chain");
168+
}
169+
var certs = certPath.getCertificates();
170+
var parent = parentPath.getCertificates();
171+
CertificateFactory cf = CertificateFactory.getInstance("X.509");
172+
return cf.generateCertPath(certs.subList(0, certs.size() - parent.size()));
173+
}
174+
175+
/** Check if a parent certpath is the suffix of a certpath */
176+
public static boolean containsParent(CertPath certPath, CertPath parentPath) {
177+
var certs = certPath.getCertificates();
178+
var parent = parentPath.getCertificates();
179+
return parent.size() <= certs.size()
180+
&& certs.subList(certs.size() - parent.size(), certs.size()).equals(parent);
181+
}
182+
183+
/**
184+
* Find and return any SCTs embedded in a certificate.
185+
*
186+
* @param certificate the certificate with embedded scts
187+
* @return a byte array containing any number of embedded scts
188+
*/
189+
public static Optional<byte[]> getEmbeddedSCTs(Certificate certificate) {
190+
return Optional.ofNullable(((X509Certificate) certificate).getExtensionValue(SCT_X509_OID));
191+
}
192+
193+
/** Check if a certificate is self-signed. */
194+
public static boolean isSelfSigned(Certificate certificate) {
195+
return ((X509Certificate) certificate)
196+
.getIssuerX500Principal()
197+
.equals(((X509Certificate) certificate).getSubjectX500Principal());
198+
}
199+
200+
/** Check if the root of a CertPath is self-signed */
201+
public static boolean isSelfSigned(CertPath certPath) {
202+
return isSelfSigned(certPath.getCertificates().get(certPath.getCertificates().size() - 1));
203+
}
204+
205+
public static X509Certificate getLeaf(CertPath certPath) {
206+
return (X509Certificate) certPath.getCertificates().get(0);
207+
}
151208
}

sigstore-java/src/test/java/dev/sigstore/encryption/certificates/CertificatesTest.java

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.io.IOException;
2020
import java.nio.charset.StandardCharsets;
2121
import java.security.cert.CertificateException;
22+
import java.security.cert.CertificateFactory;
2223
import java.util.ArrayList;
2324
import java.util.List;
2425
import org.bouncycastle.util.encoders.Base64;
@@ -134,16 +135,55 @@ public void toCertPath() throws Exception {
134135

135136
@Test
136137
public void appendCertPath() throws Exception {
137-
var certPath =
138+
var parent =
138139
Certificates.fromPemChain(Resources.toByteArray(Resources.getResource(CERT_CHAIN)));
139-
var cert = Certificates.fromPem(Resources.toByteArray(Resources.getResource(CERT_GH)));
140+
var child = Certificates.fromPem(Resources.toByteArray(Resources.getResource(CERT_GH)));
140141

141-
Assertions.assertEquals(2, certPath.getCertificates().size());
142-
var appended = Certificates.appendCertPath(certPath, cert);
142+
Assertions.assertEquals(2, parent.getCertificates().size());
143+
var appended = Certificates.appendCertPath(parent, child);
143144

144145
Assertions.assertEquals(3, appended.getCertificates().size());
145-
Assertions.assertEquals(cert, appended.getCertificates().get(0));
146-
Assertions.assertEquals(certPath.getCertificates().get(0), appended.getCertificates().get(1));
147-
Assertions.assertEquals(certPath.getCertificates().get(1), appended.getCertificates().get(2));
146+
Assertions.assertEquals(child, appended.getCertificates().get(0));
147+
Assertions.assertEquals(parent.getCertificates().get(0), appended.getCertificates().get(1));
148+
Assertions.assertEquals(parent.getCertificates().get(1), appended.getCertificates().get(2));
149+
}
150+
151+
@Test
152+
public void trimParent() throws Exception {
153+
var certPath =
154+
Certificates.fromPemChain(Resources.toByteArray(Resources.getResource(CERT_CHAIN)));
155+
var parent =
156+
CertificateFactory.getInstance("X.509")
157+
.generateCertPath(List.of(certPath.getCertificates().get(1)));
158+
159+
var trimmed = Certificates.trimParent(certPath, parent);
160+
161+
Assertions.assertEquals(1, trimmed.getCertificates().size());
162+
Assertions.assertEquals(certPath.getCertificates().get(0), trimmed.getCertificates().get(0));
163+
}
164+
165+
@Test
166+
public void containsParent() throws Exception {
167+
var certPath =
168+
Certificates.fromPemChain(Resources.toByteArray(Resources.getResource(CERT_CHAIN)));
169+
var parent =
170+
CertificateFactory.getInstance("X.509")
171+
.generateCertPath(List.of(certPath.getCertificates().get(1)));
172+
var cert = Certificates.fromPemChain(Resources.toByteArray(Resources.getResource(CERT)));
173+
174+
Assertions.assertTrue(Certificates.containsParent(certPath, parent));
175+
Assertions.assertFalse(Certificates.containsParent(cert, certPath));
176+
Assertions.assertTrue(Certificates.containsParent(certPath, certPath));
177+
Assertions.assertTrue(Certificates.containsParent(cert, cert));
178+
}
179+
180+
@Test
181+
public void isSelfSigned() throws Exception {
182+
var certPath =
183+
Certificates.fromPemChain(Resources.toByteArray(Resources.getResource(CERT_CHAIN)));
184+
var cert = Certificates.fromPem(Resources.toByteArray(Resources.getResource(CERT)));
185+
186+
Assertions.assertTrue(Certificates.isSelfSigned(certPath));
187+
Assertions.assertFalse(Certificates.isSelfSigned(cert));
148188
}
149189
}

0 commit comments

Comments
 (0)