Skip to content

Commit 2a3ca6b

Browse files
authored
Merge pull request #523 from sigstore/handle-leaf-certs
Update signing result to store leaf certs only
2 parents fcd554b + 1f94cff commit 2a3ca6b

File tree

15 files changed

+164
-439
lines changed

15 files changed

+164
-439
lines changed

fuzzing/src/main/java/fuzzing/FulcioVerifierFuzzer.java

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@
1818
import com.code_intelligence.jazzer.api.FuzzedDataProvider;
1919
import dev.sigstore.fulcio.client.FulcioVerificationException;
2020
import dev.sigstore.fulcio.client.FulcioVerifier;
21-
import dev.sigstore.fulcio.client.SigningCertificate;
2221
import java.io.ByteArrayInputStream;
2322
import java.io.IOException;
2423
import java.security.InvalidAlgorithmParameterException;
2524
import java.security.NoSuchAlgorithmException;
25+
import java.security.cert.CertPath;
2626
import java.security.cert.Certificate;
2727
import java.security.cert.CertificateException;
2828
import java.security.cert.CertificateFactory;
@@ -34,33 +34,19 @@
3434
public class FulcioVerifierFuzzer {
3535
public static void fuzzerTestOneInput(FuzzedDataProvider data) {
3636
try {
37-
int[] intArray = data.consumeInts(data.consumeInt(1, 10));
38-
3937
var cas = Tuf.certificateAuthoritiesFrom(data);
4038
var ctLogs = Tuf.transparencyLogsFrom(data);
4139

42-
byte[] byteArray = data.consumeRemainingAsBytes();
43-
List<Certificate> certList = new ArrayList<Certificate>();
40+
List<Certificate> certList = new ArrayList<>();
4441
CertificateFactory cf = CertificateFactory.getInstance("X.509");
45-
certList.add(cf.generateCertificate(new ByteArrayInputStream(byteArray)));
46-
certList.add(cf.generateCertificate(new ByteArrayInputStream(byteArray)));
42+
certList.add(cf.generateCertificate(new ByteArrayInputStream(data.consumeBytes(10240))));
43+
certList.add(
44+
cf.generateCertificate(new ByteArrayInputStream(data.consumeRemainingAsBytes())));
4745

48-
SigningCertificate sc = SigningCertificate.from(cf.generateCertPath(certList));
46+
CertPath sc = cf.generateCertPath(certList);
4947
FulcioVerifier fv = FulcioVerifier.newFulcioVerifier(cas, ctLogs);
5048

51-
for (int choice : intArray) {
52-
switch (choice % 4) {
53-
case 0:
54-
sc.getCertificates();
55-
break;
56-
case 1:
57-
sc.getLeafCertificate();
58-
break;
59-
case 3:
60-
fv.verifySigningCertificate(sc);
61-
break;
62-
}
63-
}
49+
fv.verifySigningCertificate(sc);
6450
} catch (CertificateException
6551
| FulcioVerificationException
6652
| InvalidKeySpecException

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@ public interface KeylessSignature {
2525
/** The sha256 hash digest of the artifact */
2626
byte[] getDigest();
2727

28-
/** The full certificate chain provided by fulcio for the public key used to sign the artifact */
28+
/**
29+
* The partial certificate chain provided by fulcio for the public key and identity used to sign
30+
* the artifact, this should NOT contain the trusted root or any trusted intermediates. But users
31+
* of this object should understand that older signatures may include the full chain.
32+
*/
2933
CertPath getCertPath();
3034

3135
/** The signature over the artifact */

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
import dev.sigstore.fulcio.client.FulcioClient;
3030
import dev.sigstore.fulcio.client.FulcioVerificationException;
3131
import dev.sigstore.fulcio.client.FulcioVerifier;
32-
import dev.sigstore.fulcio.client.SigningCertificate;
3332
import dev.sigstore.fulcio.client.UnsupportedAlgorithmException;
3433
import dev.sigstore.oidc.client.OidcClients;
3534
import dev.sigstore.oidc.client.OidcException;
@@ -47,6 +46,7 @@
4746
import java.security.InvalidKeyException;
4847
import java.security.NoSuchAlgorithmException;
4948
import java.security.SignatureException;
49+
import java.security.cert.CertPath;
5050
import java.security.cert.CertificateException;
5151
import java.security.spec.InvalidKeySpecException;
5252
import java.time.Duration;
@@ -82,7 +82,7 @@ public class KeylessSigner implements AutoCloseable {
8282

8383
/** The code signing certificate from Fulcio. */
8484
@GuardedBy("lock")
85-
private @Nullable SigningCertificate signingCert;
85+
private @Nullable CertPath signingCert;
8686

8787
/**
8888
* Representation {@link #signingCert} in PEM bytes format. This is used to avoid serializing the
@@ -244,7 +244,7 @@ public List<KeylessSignature> sign(List<byte[]> artifactDigests)
244244
// However, files might be large, and it might take time to talk to Rekor
245245
// so we check the certificate expiration here.
246246
renewSigningCertificate();
247-
SigningCertificate signingCert;
247+
CertPath signingCert;
248248
byte[] signingCertPemBytes;
249249
lock.readLock().lock();
250250
try {
@@ -266,7 +266,7 @@ public List<KeylessSignature> sign(List<byte[]> artifactDigests)
266266
result.add(
267267
KeylessSignature.builder()
268268
.digest(artifactDigest)
269-
.certPath(signingCert.getCertPath())
269+
.certPath(signingCert)
270270
.signature(signature)
271271
.entry(rekorResponse.getEntry())
272272
.build());
@@ -284,7 +284,7 @@ private void renewSigningCertificate()
284284
if (signingCert != null) {
285285
@SuppressWarnings("JavaUtilDate")
286286
long lifetimeLeft =
287-
signingCert.getLeafCertificate().getNotAfter().getTime() - System.currentTimeMillis();
287+
Certificates.getLeaf(signingCert).getNotAfter().getTime() - System.currentTimeMillis();
288288
if (lifetimeLeft > minSigningCertificateLifetime.toMillis()) {
289289
// The current certificate is fine, reuse it
290290
return;
@@ -300,7 +300,7 @@ private void renewSigningCertificate()
300300
signingCert = null;
301301
signingCertPemBytes = null;
302302
OidcToken tokenInfo = oidcClients.getIDToken();
303-
SigningCertificate signingCert =
303+
CertPath signingCert =
304304
fulcioClient.signingCertificate(
305305
CertificateRequest.newCertificateRequest(
306306
signer.getPublicKey(),
@@ -311,7 +311,7 @@ private void renewSigningCertificate()
311311
// allow that to be known
312312
fulcioVerifier.verifySigningCertificate(signingCert);
313313
this.signingCert = signingCert;
314-
signingCertPemBytes = Certificates.toPemBytes(signingCert.getLeafCertificate());
314+
signingCertPemBytes = Certificates.toPemBytes(signingCert);
315315
} finally {
316316
lock.writeLock().unlock();
317317
}

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import dev.sigstore.fulcio.client.FulcioCertificateVerifier;
2525
import dev.sigstore.fulcio.client.FulcioVerificationException;
2626
import dev.sigstore.fulcio.client.FulcioVerifier;
27-
import dev.sigstore.fulcio.client.SigningCertificate;
2827
import dev.sigstore.rekor.client.HashedRekordRequest;
2928
import dev.sigstore.rekor.client.RekorClient;
3029
import dev.sigstore.rekor.client.RekorEntry;
@@ -142,8 +141,8 @@ public void verify(Path artifact, KeylessVerificationRequest request)
142141
*/
143142
public void verify(byte[] artifactDigest, KeylessVerificationRequest request)
144143
throws KeylessVerificationException {
145-
var signingCert = SigningCertificate.from(request.getKeylessSignature().getCertPath());
146-
var leafCert = signingCert.getLeafCertificate();
144+
var signingCert = request.getKeylessSignature().getCertPath();
145+
var leafCert = Certificates.getLeaf(signingCert);
147146

148147
// this ensures the provided artifact digest matches what may have come from a bundle (in
149148
// keyless signature)

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,12 +140,14 @@ public static CertPath toCertPath(Certificate certificate) throws CertificateExc
140140
return cf.generateCertPath(Collections.singletonList(certificate));
141141
}
142142

143-
/** Appends a CertPath to another {@link CertPath} as children. */
144-
public static CertPath appendCertPath(CertPath parent, Certificate child)
145-
throws CertificateException {
143+
/** Appends an CertPath to another {@link CertPath} as children. */
144+
public static CertPath append(CertPath parent, CertPath child) throws CertificateException {
146145
CertificateFactory cf = CertificateFactory.getInstance("X.509");
147146
List<Certificate> certs =
148-
ImmutableList.<Certificate>builder().add(child).addAll(parent.getCertificates()).build();
147+
ImmutableList.<Certificate>builder()
148+
.addAll(child.getCertificates())
149+
.addAll(parent.getCertificates())
150+
.build();
149151
return cf.generateCertPath(certs);
150152
}
151153

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

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@
1717

1818
import static dev.sigstore.fulcio.v2.SigningCertificate.CertificateCase.SIGNED_CERTIFICATE_DETACHED_SCT;
1919

20+
import com.google.common.annotations.VisibleForTesting;
2021
import com.google.protobuf.ByteString;
22+
import dev.sigstore.encryption.certificates.Certificates;
2123
import dev.sigstore.fulcio.v2.CAGrpc;
24+
import dev.sigstore.fulcio.v2.CertificateChain;
2225
import dev.sigstore.fulcio.v2.CreateSigningCertificateRequest;
2326
import dev.sigstore.fulcio.v2.Credentials;
2427
import dev.sigstore.fulcio.v2.PublicKey;
@@ -28,7 +31,13 @@
2831
import dev.sigstore.http.ImmutableHttpParams;
2932
import dev.sigstore.trustroot.CertificateAuthority;
3033
import dev.sigstore.trustroot.SigstoreTrustedRoot;
34+
import java.io.ByteArrayInputStream;
35+
import java.security.cert.CertPath;
3136
import java.security.cert.CertificateException;
37+
import java.security.cert.CertificateFactory;
38+
import java.security.cert.CertificateParsingException;
39+
import java.security.cert.X509Certificate;
40+
import java.util.ArrayList;
3241
import java.util.Base64;
3342
import java.util.concurrent.TimeUnit;
3443

@@ -80,9 +89,9 @@ public FulcioClient build() {
8089
* Request a signing certificate from fulcio.
8190
*
8291
* @param request certificate request parameters
83-
* @return a {@link SigningCertificate} from fulcio
92+
* @return a {@link CertPath} from fulcio
8493
*/
85-
public SigningCertificate signingCertificate(CertificateRequest request)
94+
public CertPath signingCertificate(CertificateRequest request)
8695
throws InterruptedException, CertificateException {
8796
if (!certificateAuthority.isCurrent()) {
8897
throw new RuntimeException(
@@ -126,9 +135,27 @@ public SigningCertificate signingCertificate(CertificateRequest request)
126135
if (certs.getCertificateCase() == SIGNED_CERTIFICATE_DETACHED_SCT) {
127136
throw new CertificateException("Detached SCTs are not supported");
128137
}
129-
return SigningCertificate.newSigningCertificate(certs.getSignedCertificateEmbeddedSct());
138+
return Certificates.trimParent(
139+
decodeCerts(certs.getSignedCertificateEmbeddedSct().getChain()),
140+
certificateAuthority.getCertPath());
130141
} finally {
131142
channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
132143
}
133144
}
145+
146+
@VisibleForTesting
147+
CertPath decodeCerts(CertificateChain certChain) throws CertificateException {
148+
var certificateFactory = CertificateFactory.getInstance("X.509");
149+
var certs = new ArrayList<X509Certificate>();
150+
if (certChain.getCertificatesCount() == 0) {
151+
throw new CertificateParsingException(
152+
"no valid PEM certificates were found in response from Fulcio");
153+
}
154+
for (var cert : certChain.getCertificatesList().asByteStringList()) {
155+
certs.add(
156+
(X509Certificate)
157+
certificateFactory.generateCertificate(new ByteArrayInputStream(cert.toByteArray())));
158+
}
159+
return certificateFactory.generateCertPath(certs);
160+
}
134161
}

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

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
import java.util.Map;
4545
import java.util.stream.Collectors;
4646

47-
/** Verifier for fulcio {@link SigningCertificate}. */
47+
/** Verifier for fulcio generated signing cerificates */
4848
public class FulcioVerifier {
4949
private final CertificateAuthorities cas;
5050
private final TransparencyLogs ctLogs;
@@ -90,25 +90,21 @@ private FulcioVerifier(
9090
}
9191

9292
@VisibleForTesting
93-
void verifySct(SigningCertificate signingCertificate, CertPath rebuiltCert)
94-
throws FulcioVerificationException {
93+
void verifySct(CertPath fullCertPath) throws FulcioVerificationException {
9594
if (ctLogs.size() == 0) {
9695
throw new FulcioVerificationException("No ct logs were provided to verifier");
9796
}
9897

99-
if (signingCertificate.getDetachedSct().isPresent()) {
100-
throw new FulcioVerificationException(
101-
"Detached SCTs are not supported for validating certificates");
102-
} else if (signingCertificate.getEmbeddedSct().isPresent()) {
103-
verifyEmbeddedScts(rebuiltCert);
98+
if (Certificates.getEmbeddedSCTs(Certificates.getLeaf(fullCertPath)).isPresent()) {
99+
verifyEmbeddedScts(fullCertPath);
104100
} else {
105101
throw new FulcioVerificationException("No valid SCTs were found during verification");
106102
}
107103
}
108104

109-
private void verifyEmbeddedScts(CertPath rebuiltCert) throws FulcioVerificationException {
105+
private void verifyEmbeddedScts(CertPath certPath) throws FulcioVerificationException {
110106
@SuppressWarnings("unchecked")
111-
var certs = (List<X509Certificate>) rebuiltCert.getCertificates();
107+
var certs = (List<X509Certificate>) certPath.getCertificates();
112108
CTVerificationResult result;
113109
try {
114110
result = ctVerifier.verifySignedCertificateTimestamps(certs, null, null);
@@ -144,22 +140,23 @@ private void verifyEmbeddedScts(CertPath rebuiltCert) throws FulcioVerificationE
144140
* configured in this validator. Also verify that the leaf certificate contains at least one valid
145141
* SCT
146142
*
147-
* @param signingCertificate containing the certificate chain
143+
* @param signingCertificate containing a certificate chain, this chain should not contain any
144+
* trusted root or trusted intermediates
148145
* @throws FulcioVerificationException if verification fails for any reason
149146
*/
150-
public void verifySigningCertificate(SigningCertificate signingCertificate)
147+
public void verifySigningCertificate(CertPath signingCertificate)
151148
throws FulcioVerificationException, IOException {
152-
CertPath reconstructedCert = reconstructValidCertPath(signingCertificate);
153-
verifySct(signingCertificate, reconstructedCert);
149+
CertPath fullCertPath = validateCertPath(signingCertificate);
150+
verifySct(fullCertPath);
154151
}
155152

156153
/**
157154
* Find a valid cert path that chains back up to the trusted root certs and reconstruct a
158155
* certificate path combining the provided un-trusted certs and a known set of trusted and
159-
* intermediate certs.
156+
* intermediate certs. If a full certificate is provided with a self signed root, this should
157+
* attempt to match the root/intermediate with a trusted chain.
160158
*/
161-
CertPath reconstructValidCertPath(SigningCertificate signingCertificate)
162-
throws FulcioVerificationException {
159+
CertPath validateCertPath(CertPath signingCertificate) throws FulcioVerificationException {
163160
CertPathValidator cpv;
164161
try {
165162
cpv = CertPathValidator.getInstance("PKIX");
@@ -170,7 +167,7 @@ CertPath reconstructValidCertPath(SigningCertificate signingCertificate)
170167
e);
171168
}
172169

173-
var leaf = signingCertificate.getLeafCertificate();
170+
var leaf = Certificates.getLeaf(signingCertificate);
174171
var validCAs = cas.find(leaf.getNotBefore().toInstant());
175172

176173
if (validCAs.size() == 0) {
@@ -194,25 +191,35 @@ CertPath reconstructValidCertPath(SigningCertificate signingCertificate)
194191
// these certs are only valid for 15 minutes, so find a time in the validity period
195192
@SuppressWarnings("JavaUtilDate")
196193
Date dateInValidityPeriod =
197-
new Date(signingCertificate.getLeafCertificate().getNotBefore().getTime());
194+
new Date(Certificates.getLeaf(signingCertificate).getNotBefore().getTime());
198195
pkixParams.setDate(dateInValidityPeriod);
199196

200-
CertPath rebuiltCert;
197+
CertPath fullCertPath;
201198
try {
202-
// build a cert chain with the root-chain in question and the provided leaf
203-
rebuiltCert =
204-
Certificates.appendCertPath(ca.getCertPath(), signingCertificate.getLeafCertificate());
199+
if (Certificates.isSelfSigned(signingCertificate)) {
200+
if (Certificates.containsParent(signingCertificate, ca.getCertPath())) {
201+
fullCertPath = signingCertificate;
202+
} else {
203+
// verification failed because we didn't match to a trusted root
204+
caVerificationFailure.put(
205+
ca.getUri().toString(), "Trusted root in chain does not match");
206+
continue;
207+
}
208+
} else {
209+
// build a cert chain with the root-chain in question and the provided signing certificate
210+
fullCertPath = Certificates.append(ca.getCertPath(), signingCertificate);
211+
}
205212

206213
// a result is returned here, but we ignore it
207-
cpv.validate(rebuiltCert, pkixParams);
214+
cpv.validate(fullCertPath, pkixParams);
208215
} catch (CertPathValidatorException
209216
| InvalidAlgorithmParameterException
210217
| CertificateException ve) {
211218
caVerificationFailure.put(ca.getUri().toString(), ve.getMessage());
212219
// verification failed
213220
continue;
214221
}
215-
return rebuiltCert;
222+
return fullCertPath;
216223
// verification passed so just end this method
217224
}
218225
String errors =

0 commit comments

Comments
 (0)