Skip to content

Commit 26a20c8

Browse files
authored
Merge pull request #520 from sigstore/validate-sct-fix
Fix validate SCTs when cert chain is just leaf
2 parents b852235 + 2e7b9bd commit 26a20c8

File tree

5 files changed

+58
-56
lines changed

5 files changed

+58
-56
lines changed

sigstore-java/src/main/java/dev/sigstore/KeylessSigner2.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,10 +307,9 @@ private void renewSigningCertificate()
307307
tokenInfo.getIdToken(),
308308
signer.sign(
309309
tokenInfo.getSubjectAlternativeName().getBytes(StandardCharsets.UTF_8))));
310-
fulcioVerifier.verifyCertChain(signingCert);
311310
// TODO: this signing workflow mandates SCTs, but fulcio itself doesn't, figure out a way to
312311
// allow that to be known
313-
fulcioVerifier.verifySct(signingCert);
312+
fulcioVerifier.verifySigningCertificate(signingCert);
314313
this.signingCert = signingCert;
315314
signingCertPemBytes = Certificates.toPemBytes(signingCert.getLeafCertificate());
316315
} finally {

sigstore-java/src/main/java/dev/sigstore/KeylessVerifier2.java

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -157,22 +157,14 @@ public void verify(byte[] artifactDigest, KeylessVerificationRequest request)
157157
+ Hex.toHexString(request.getKeylessSignature().getDigest()));
158158
}
159159

160-
// verify the certificate chains up to a trusted root (fulcio)
160+
// verify the certificate chains up to a trusted root (fulcio) and contains a valid SCT
161161
try {
162-
fulcioVerifier.verifyCertChain(signingCert);
162+
fulcioVerifier.verifySigningCertificate(signingCert);
163163
} catch (FulcioVerificationException | IOException ex) {
164164
throw new KeylessVerificationException(
165165
"Fulcio certificate was not valid: " + ex.getMessage(), ex);
166166
}
167167

168-
// make the sure a crt is signed by the certificate transparency log (embedded only)
169-
try {
170-
fulcioVerifier.verifySct(signingCert);
171-
} catch (FulcioVerificationException ex) {
172-
throw new KeylessVerificationException(
173-
"Fulcio certificate SCT was not valid: " + ex.getMessage(), ex);
174-
}
175-
176168
// verify the certificate identity if options are present
177169
if (request.getVerificationOptions().getCertificateIdentities().size() > 0) {
178170
try {

sigstore-java/src/main/java/dev/sigstore/fulcio/client/FulcioVerifier2.java

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package dev.sigstore.fulcio.client;
1717

18+
import com.google.common.annotations.VisibleForTesting;
1819
import dev.sigstore.encryption.certificates.Certificates;
1920
import dev.sigstore.encryption.certificates.transparency.CTLogInfo;
2021
import dev.sigstore.encryption.certificates.transparency.CTVerificationResult;
@@ -25,11 +26,13 @@
2526
import java.io.IOException;
2627
import java.security.InvalidAlgorithmParameterException;
2728
import java.security.NoSuchAlgorithmException;
29+
import java.security.cert.CertPath;
2830
import java.security.cert.CertPathValidator;
2931
import java.security.cert.CertPathValidatorException;
3032
import java.security.cert.CertificateEncodingException;
3133
import java.security.cert.CertificateException;
3234
import java.security.cert.PKIXParameters;
35+
import java.security.cert.X509Certificate;
3336
import java.security.spec.InvalidKeySpecException;
3437
import java.time.Instant;
3538
import java.util.ArrayList;
@@ -86,14 +89,9 @@ private FulcioVerifier2(
8689
this.ctVerifier = ctVerifier;
8790
}
8891

89-
/**
90-
* Verify that an SCT associated with a Singing Certificate is valid and signed by the configured
91-
* CT-log public key.
92-
*
93-
* @param signingCertificate containing the SCT metadata to verify
94-
* @throws FulcioVerificationException if verification fails for any reason
95-
*/
96-
public void verifySct(SigningCertificate signingCertificate) throws FulcioVerificationException {
92+
@VisibleForTesting
93+
void verifySct(SigningCertificate signingCertificate, CertPath rebuiltCert)
94+
throws FulcioVerificationException {
9795
if (ctLogs.size() == 0) {
9896
throw new FulcioVerificationException("No ct logs were provided to verifier");
9997
}
@@ -102,18 +100,17 @@ public void verifySct(SigningCertificate signingCertificate) throws FulcioVerifi
102100
throw new FulcioVerificationException(
103101
"Detached SCTs are not supported for validating certificates");
104102
} else if (signingCertificate.getEmbeddedSct().isPresent()) {
105-
verifyEmbeddedScts(signingCertificate);
103+
verifyEmbeddedScts(rebuiltCert);
106104
} else {
107105
throw new FulcioVerificationException("No valid SCTs were found during verification");
108106
}
109107
}
110108

111-
private void verifyEmbeddedScts(SigningCertificate signingCertificate)
112-
throws FulcioVerificationException {
113-
var certs = signingCertificate.getCertificates();
109+
private void verifyEmbeddedScts(CertPath rebuiltCert) throws FulcioVerificationException {
110+
@SuppressWarnings("unchecked")
111+
var certs = (List<X509Certificate>) rebuiltCert.getCertificates();
114112
CTVerificationResult result;
115113
try {
116-
// even though we're sending the whole chain, this method only checks SCTs on the leaf cert
117114
result = ctVerifier.verifySignedCertificateTimestamps(certs, null, null);
118115
} catch (CertificateEncodingException cee) {
119116
throw new FulcioVerificationException(
@@ -144,13 +141,25 @@ private void verifyEmbeddedScts(SigningCertificate signingCertificate)
144141

145142
/**
146143
* Verify that a cert chain is valid and chains up to the trust anchor (fulcio public key)
147-
* configured in this validator.
144+
* configured in this validator. Also verify that the leaf certificate contains at least one valid
145+
* SCT
148146
*
149147
* @param signingCertificate containing the certificate chain
150148
* @throws FulcioVerificationException if verification fails for any reason
151149
*/
152-
public void verifyCertChain(SigningCertificate signingCertificate)
150+
public void verifySigningCertificate(SigningCertificate signingCertificate)
153151
throws FulcioVerificationException, IOException {
152+
CertPath reconstructedCert = reconstructValidCertPath(signingCertificate);
153+
verifySct(signingCertificate, reconstructedCert);
154+
}
155+
156+
/**
157+
* Find a valid cert path that chains back up to the trusted root certs and reconstruct a
158+
* certificate path combining the provided un-trusted certs and a known set of trusted and
159+
* intermediate certs.
160+
*/
161+
CertPath reconstructValidCertPath(SigningCertificate signingCertificate)
162+
throws FulcioVerificationException {
154163
CertPathValidator cpv;
155164
try {
156165
cpv = CertPathValidator.getInstance("PKIX");
@@ -171,8 +180,6 @@ public void verifyCertChain(SigningCertificate signingCertificate)
171180

172181
Map<String, String> caVerificationFailure = new LinkedHashMap<>();
173182

174-
System.out.println(Certificates.toPemString(signingCertificate.getCertPath()));
175-
176183
for (var ca : validCAs) {
177184
PKIXParameters pkixParams;
178185
try {
@@ -190,10 +197,12 @@ public void verifyCertChain(SigningCertificate signingCertificate)
190197
new Date(signingCertificate.getLeafCertificate().getNotBefore().getTime());
191198
pkixParams.setDate(dateInValidityPeriod);
192199

200+
CertPath rebuiltCert;
193201
try {
194202
// build a cert chain with the root-chain in question and the provided leaf
195-
var rebuiltCert =
203+
rebuiltCert =
196204
Certificates.appendCertPath(ca.getCertPath(), signingCertificate.getLeafCertificate());
205+
197206
// a result is returned here, but we ignore it
198207
cpv.validate(rebuiltCert, pkixParams);
199208
} catch (CertPathValidatorException
@@ -203,8 +212,8 @@ public void verifyCertChain(SigningCertificate signingCertificate)
203212
// verification failed
204213
continue;
205214
}
215+
return rebuiltCert;
206216
// verification passed so just end this method
207-
return;
208217
}
209218
String errors =
210219
caVerificationFailure.entrySet().stream()

sigstore-java/src/test/java/dev/sigstore/fulcio/client/FulcioVerifier2Test.java

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@
1717

1818
import com.google.common.io.Resources;
1919
import com.google.protobuf.util.JsonFormat;
20+
import dev.sigstore.bundle.BundleFactory;
2021
import dev.sigstore.encryption.certificates.transparency.SerializationException;
2122
import dev.sigstore.proto.trustroot.v1.TrustedRoot;
2223
import dev.sigstore.trustroot.ImmutableLogId;
2324
import dev.sigstore.trustroot.ImmutableTransparencyLog;
2425
import dev.sigstore.trustroot.ImmutableTransparencyLogs;
2526
import dev.sigstore.trustroot.SigstoreTrustedRoot;
2627
import java.io.IOException;
28+
import java.io.StringReader;
2729
import java.nio.charset.StandardCharsets;
2830
import java.security.InvalidAlgorithmParameterException;
2931
import java.security.NoSuchAlgorithmException;
@@ -37,11 +39,8 @@
3739
public class FulcioVerifier2Test {
3840
private static String sctBase64;
3941
private static String certs;
40-
private static String certs2;
41-
private static byte[] fulcioRoot;
42-
private static byte[] ctfePub;
43-
private static byte[] badCtfePub;
4442
private static String certsWithEmbeddedSct;
43+
private static String bundleFile;
4544

4645
private static SigstoreTrustedRoot trustRoot;
4746

@@ -56,23 +55,14 @@ public static void loadResources() throws IOException {
5655
Resources.getResource("dev/sigstore/samples/fulcio-response/valid/cert.pem"),
5756
StandardCharsets.UTF_8);
5857

59-
certs2 =
58+
certsWithEmbeddedSct =
6059
Resources.toString(
61-
Resources.getResource("dev/sigstore/samples/certs/cert-githuboidc.pem"),
60+
Resources.getResource("dev/sigstore/samples/fulcio-response/valid/certWithSct.pem"),
6261
StandardCharsets.UTF_8);
6362

64-
fulcioRoot =
65-
Resources.toByteArray(
66-
Resources.getResource("dev/sigstore/samples/fulcio-response/valid/fulcio.crt.pem"));
67-
ctfePub =
68-
Resources.toByteArray(
69-
Resources.getResource("dev/sigstore/samples/fulcio-response/valid/ctfe.pub"));
70-
badCtfePub =
71-
Resources.toByteArray(Resources.getResource("dev/sigstore/samples/keys/test-rsa.pub"));
72-
73-
certsWithEmbeddedSct =
63+
bundleFile =
7464
Resources.toString(
75-
Resources.getResource("dev/sigstore/samples/fulcio-response/valid/certWithSct.pem"),
65+
Resources.getResource("dev/sigstore/samples/bundles/bundle-with-leaf-cert.sigstore"),
7666
StandardCharsets.UTF_8);
7767
}
7868

@@ -95,16 +85,18 @@ public void detachedSctNotSupported() throws Exception {
9585
var signingCertificate = SigningCertificate.newSigningCertificate(certs, sctBase64);
9686
var ex =
9787
Assertions.assertThrows(
98-
FulcioVerificationException.class, () -> fulcioVerifier.verifySct(signingCertificate));
88+
FulcioVerificationException.class,
89+
() -> fulcioVerifier.verifySct(signingCertificate, signingCertificate.getCertPath()));
9990
Assertions.assertEquals(
100-
ex.getMessage(), "Detached SCTs are not supported for validating certificates");
91+
"Detached SCTs are not supported for validating certificates", ex.getMessage());
10192
}
10293

10394
@Test
10495
public void testVerifySct_nullCtLogKey()
10596
throws IOException, SerializationException, CertificateException, InvalidKeySpecException,
10697
NoSuchAlgorithmException, InvalidAlgorithmParameterException {
107-
var signingCertificate = SigningCertificate.newSigningCertificate(certs, sctBase64);
98+
var signingCertificate =
99+
SigningCertificate.newSigningCertificate(certsWithEmbeddedSct, sctBase64);
108100
var fulcioVerifier =
109101
FulcioVerifier2.newFulcioVerifier(
110102
trustRoot.getCAs(),
@@ -113,7 +105,7 @@ public void testVerifySct_nullCtLogKey()
113105
.build());
114106

115107
try {
116-
fulcioVerifier.verifySct(signingCertificate);
108+
fulcioVerifier.verifySigningCertificate(signingCertificate);
117109
Assertions.fail();
118110
} catch (FulcioVerificationException fve) {
119111
Assertions.assertEquals("No ct logs were provided to verifier", fve.getMessage());
@@ -126,7 +118,7 @@ public void testVerifySct_noSct() throws Exception {
126118
var fulcioVerifier = FulcioVerifier2.newFulcioVerifier(trustRoot);
127119

128120
try {
129-
fulcioVerifier.verifySct(signingCertificate);
121+
fulcioVerifier.verifySct(signingCertificate, signingCertificate.getCertPath());
130122
Assertions.fail();
131123
} catch (FulcioVerificationException fve) {
132124
Assertions.assertEquals("No valid SCTs were found during verification", fve.getMessage());
@@ -138,8 +130,16 @@ public void validSigningCertAndEmbeddedSct() throws Exception {
138130
var signingCertificate = SigningCertificate.newSigningCertificate(certsWithEmbeddedSct, null);
139131
var fulcioVerifier = FulcioVerifier2.newFulcioVerifier(trustRoot);
140132

141-
fulcioVerifier.verifyCertChain(signingCertificate);
142-
fulcioVerifier.verifySct(signingCertificate);
133+
fulcioVerifier.verifySigningCertificate(signingCertificate);
134+
}
135+
136+
@Test
137+
public void validBundle() throws Exception {
138+
var bundle = BundleFactory.readBundle(new StringReader(bundleFile));
139+
var fulcioVerifier = FulcioVerifier2.newFulcioVerifier(trustRoot);
140+
141+
Assertions.assertEquals(1, bundle.getCertPath().getCertificates().size());
142+
fulcioVerifier.verifySigningCertificate(SigningCertificate.from(bundle.getCertPath()));
143143
}
144144

145145
@Test
@@ -161,7 +161,8 @@ public void invalidEmbeddedSct() throws Exception {
161161

162162
var fve =
163163
Assertions.assertThrows(
164-
FulcioVerificationException.class, () -> fulcioVerifier.verifySct(signingCertificate));
164+
FulcioVerificationException.class,
165+
() -> fulcioVerifier.verifySct(signingCertificate, signingCertificate.getCertPath()));
165166
Assertions.assertEquals("No valid SCTs were found, all(1) SCTs were invalid", fve.getMessage());
166167
}
167168
}

0 commit comments

Comments
 (0)