Skip to content

Commit 6dbfb38

Browse files
authored
Merge pull request #1024 from sigstore/timestamp-keyless
fix: Re-add timestamp verification in KeylessVerifier
2 parents f8a9d50 + 7446d2c commit 6dbfb38

File tree

2 files changed

+121
-26
lines changed

2 files changed

+121
-26
lines changed

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@
4242
import dev.sigstore.rekor.client.RekorVerifier;
4343
import dev.sigstore.rekor.dsse.v0_0_1.Dsse;
4444
import dev.sigstore.rekor.dsse.v0_0_1.PayloadHash;
45+
import dev.sigstore.timestamp.client.ImmutableTimestampResponse;
46+
import dev.sigstore.timestamp.client.TimestampException;
47+
import dev.sigstore.timestamp.client.TimestampVerificationException;
4548
import dev.sigstore.timestamp.client.TimestampVerifier;
4649
import dev.sigstore.trustroot.SigstoreConfigurationException;
4750
import dev.sigstore.tuf.SigstoreTufClient;
@@ -54,6 +57,8 @@
5457
import java.security.SignatureException;
5558
import java.security.cert.CertificateEncodingException;
5659
import java.security.cert.CertificateException;
60+
import java.security.cert.CertificateExpiredException;
61+
import java.security.cert.CertificateNotYetValidException;
5762
import java.security.cert.X509Certificate;
5863
import java.security.spec.InvalidKeySpecException;
5964
import java.util.Arrays;
@@ -176,13 +181,41 @@ public void verify(byte[] artifactDigest, Bundle bundle, VerificationOptions opt
176181
signature = dsseEnvelope.getSignature();
177182
}
178183

184+
verifyTimestamps(leafCert, bundle.getTimestamps(), signature);
185+
179186
try {
180187
rekorVerifier.verifyEntry(rekorEntry);
181188
} catch (RekorVerificationException ex) {
182189
throw new KeylessVerificationException("Transparency log entry could not be verified", ex);
183190
}
184191
}
185192

193+
private void verifyTimestamps(
194+
X509Certificate leafCert, List<Bundle.Timestamp> timestamps, byte[] signature)
195+
throws KeylessVerificationException {
196+
if (timestamps == null || timestamps.isEmpty()) {
197+
return;
198+
}
199+
for (Bundle.Timestamp timestamp : timestamps) {
200+
byte[] tsBytes = timestamp.getRfc3161Timestamp();
201+
if (tsBytes == null || tsBytes.length == 0) {
202+
throw new KeylessVerificationException(
203+
"Found an empty or null RFC3161 timestamp in bundle");
204+
}
205+
try {
206+
var tsResp = ImmutableTimestampResponse.builder().encoded(tsBytes).build();
207+
timestampVerifier.verify(tsResp, signature);
208+
leafCert.checkValidity(tsResp.getGenTime());
209+
} catch (TimestampException
210+
| CertificateNotYetValidException
211+
| CertificateExpiredException
212+
| TimestampVerificationException e) {
213+
throw new KeylessVerificationException(
214+
"RFC3161 timestamp verification failed: " + e.getMessage(), e);
215+
}
216+
}
217+
}
218+
186219
@VisibleForTesting
187220
void checkCertificateMatchers(X509Certificate cert, List<CertificateMatcher> matchers)
188221
throws KeylessVerificationException {

sigstore-java/src/test/java/dev/sigstore/KeylessVerifierTest.java

Lines changed: 88 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,94 @@ public void testVerify_unsupportedRekorVersion_rekorV2() throws Exception {
553553
Assertions.assertEquals("Unsupported hashedrekord version", ex.getMessage());
554554
}
555555

556+
@Test
557+
public void testVerify_validRfc3161Timestamp() throws Exception {
558+
var artifactUrl = Resources.getResource("dev/sigstore/samples/bundles/artifact.txt");
559+
var artifactBytes = Resources.toByteArray(artifactUrl);
560+
var artifactDigest = Hashing.sha256().hashBytes(artifactBytes).asBytes();
561+
562+
var bundleFile =
563+
Resources.toString(
564+
Resources.getResource("dev/sigstore/samples/bundles/bundle-with-timestamp.sigstore"),
565+
StandardCharsets.UTF_8);
566+
var verifier = KeylessVerifier.builder().sigstoreStagingDefaults().build();
567+
568+
Assertions.assertDoesNotThrow(
569+
() ->
570+
verifier.verify(
571+
artifactDigest,
572+
Bundle.from(new StringReader(bundleFile)),
573+
VerificationOptions.empty()));
574+
}
575+
576+
@Test
577+
public void testVerify_invalidRfc3161Timestamp() throws Exception {
578+
var tsRespBytesInvalid =
579+
Resources.toByteArray(
580+
Resources.getResource(
581+
"dev/sigstore/samples/timestamp-response/invalid/sigstore_tsa_response_invalid.tsr"));
582+
583+
var artifactUrl = Resources.getResource("dev/sigstore/samples/bundles/artifact.txt");
584+
var artifactBytes = Resources.toByteArray(artifactUrl);
585+
var artifactDigest = Hashing.sha256().hashBytes(artifactBytes).asBytes();
586+
587+
var bundleFile =
588+
Resources.toString(
589+
Resources.getResource("dev/sigstore/samples/bundles/bundle.v3.sigstore"),
590+
StandardCharsets.UTF_8);
591+
592+
var verifier = KeylessVerifier.builder().sigstorePublicDefaults().build();
593+
594+
var baseBundle = Bundle.from(new StringReader(bundleFile));
595+
var testBundle =
596+
ImmutableBundle.builder()
597+
.from(baseBundle)
598+
.timestamps(List.of(createTimestamp(tsRespBytesInvalid)))
599+
.build();
600+
var ex =
601+
Assertions.assertThrows(
602+
KeylessVerificationException.class,
603+
() -> verifier.verify(artifactDigest, testBundle, VerificationOptions.empty()));
604+
MatcherAssert.assertThat(
605+
ex.getMessage(),
606+
CoreMatchers.equalTo(
607+
"RFC3161 timestamp verification failed: Failed to parse TimeStampResponse"));
608+
}
609+
610+
@Test
611+
public void testVerify_invalidTimestampGenTime() throws Exception {
612+
var tsRespBytesInvalidGenTime =
613+
Resources.toByteArray(
614+
Resources.getResource(
615+
"dev/sigstore/samples/timestamp-response/valid/sigstore_tsa_response_with_embedded_certs.tsr"));
616+
617+
var artifactResourcePath = "dev/sigstore/samples/bundles/artifact.txt";
618+
var artifactBytes = Resources.toByteArray(Resources.getResource(artifactResourcePath));
619+
var artifactDigest = Hashing.sha256().hashBytes(artifactBytes).asBytes();
620+
621+
var bundleFileContent =
622+
Resources.toString(
623+
Resources.getResource("dev/sigstore/samples/bundles/bundle.v3.sigstore"),
624+
StandardCharsets.UTF_8);
625+
var verifier = KeylessVerifier.builder().sigstorePublicDefaults().build();
626+
627+
var baseBundle = Bundle.from(new StringReader(bundleFileContent));
628+
var testBundle =
629+
ImmutableBundle.builder()
630+
.from(baseBundle)
631+
.timestamps(List.of(createTimestamp(tsRespBytesInvalidGenTime)))
632+
.build();
633+
634+
var ex =
635+
Assertions.assertThrows(
636+
KeylessVerificationException.class,
637+
() -> verifier.verify(artifactDigest, testBundle, VerificationOptions.empty()));
638+
MatcherAssert.assertThat(
639+
ex.getMessage(),
640+
CoreMatchers.startsWith(
641+
"RFC3161 timestamp verification failed: Certificate was not verifiable against TSAs"));
642+
}
643+
556644
@Test
557645
public void testVerify_validRfc3161Timestamp_rekorV1() throws Exception {
558646
var artifact = Resources.getResource("dev/sigstore/samples/bundles/artifact.txt").getPath();
@@ -642,32 +730,6 @@ public void testVerify_invalidSet_validRfc3161Timestamp_rekorV1() throws Excepti
642730
ex.getCause().getMessage(), CoreMatchers.equalTo("Entry SET was not valid"));
643731
}
644732

645-
@Test
646-
public void testVerify_validSet_invalidRfc3161Timestamp_rekorV1() throws Exception {
647-
var bundleFile =
648-
Resources.toString(
649-
Resources.getResource("dev/sigstore/samples/bundles/bundle.v3.sigstore"),
650-
StandardCharsets.UTF_8);
651-
var baseBundle = Bundle.from(new StringReader(bundleFile));
652-
653-
var tsRespBytesInvalid =
654-
Resources.toByteArray(
655-
Resources.getResource(
656-
"dev/sigstore/samples/timestamp-response/invalid/sigstore_tsa_response_invalid.tsr"));
657-
658-
var testBundle =
659-
ImmutableBundle.builder()
660-
.from(baseBundle)
661-
.timestamps(List.of(createTimestamp(tsRespBytesInvalid)))
662-
.build();
663-
664-
var artifact = Resources.getResource("dev/sigstore/samples/bundles/artifact.txt").getPath();
665-
var verifier = KeylessVerifier.builder().sigstorePublicDefaults().build();
666-
667-
Assertions.assertDoesNotThrow(
668-
() -> verifier.verify(Path.of(artifact), testBundle, VerificationOptions.empty()));
669-
}
670-
671733
private Bundle.Timestamp createTimestamp(byte[] rfc3161Bytes) {
672734
return () -> rfc3161Bytes;
673735
}

0 commit comments

Comments
 (0)