From 447baf183f681f5e5cc5c065a9cf15fe24cb66a2 Mon Sep 17 00:00:00 2001 From: Anthony Dahanne Date: Thu, 23 Jul 2026 17:56:40 -0400 Subject: [PATCH 1/5] Close #1251: Allow no transparency-log entries * Add support for verifying bundles without transparency-log entries, relying on signed RFC 3161 timestamps. Signed-off-by: Anthony Dahanne --- .../main/java/dev/sigstore/cli/Verify.java | 13 ++++ .../java/dev/sigstore/KeylessVerifier.java | 62 ++++++++++++++++--- .../dev/sigstore/VerificationOptions.java | 19 ++++++ .../dev/sigstore/bundle/BundleReader.java | 8 ++- .../fulcio/client/FulcioVerifier.java | 23 +++++++ 5 files changed, 112 insertions(+), 13 deletions(-) diff --git a/sigstore-cli/src/main/java/dev/sigstore/cli/Verify.java b/sigstore-cli/src/main/java/dev/sigstore/cli/Verify.java index 779bd0561..4e7898782 100644 --- a/sigstore-cli/src/main/java/dev/sigstore/cli/Verify.java +++ b/sigstore-cli/src/main/java/dev/sigstore/cli/Verify.java @@ -113,6 +113,16 @@ static class Policy { required = false) Path workingDirectory; + @Option( + names = {"--allow-non-transparency-log"}, + description = + "verify a bundle that has no transparency-log entry, relying on a signed RFC 3161 " + + "timestamp instead (reduced assurance; for instances that do not publish to a " + + "transparency log, e.g. GitHub's Sigstore instance for private repositories)", + required = false, + defaultValue = "false") + Boolean allowNonTransparencyLog; + @Override public Integer call() throws Exception { byte[] digest; @@ -142,6 +152,9 @@ public Integer call() throws Exception { .subjectAlternativeName(StringMatcher.string(policy.certificateSan)) .build()); } + if (allowNonTransparencyLog) { + verificationOptionsBuilder.allowNonTransparencyLogVerification(true); + } var verificationOptions = verificationOptionsBuilder.build(); KeylessVerifier verifier; diff --git a/sigstore-java/src/main/java/dev/sigstore/KeylessVerifier.java b/sigstore-java/src/main/java/dev/sigstore/KeylessVerifier.java index 8c8fa0619..f4cfcf61a 100644 --- a/sigstore-java/src/main/java/dev/sigstore/KeylessVerifier.java +++ b/sigstore-java/src/main/java/dev/sigstore/KeylessVerifier.java @@ -132,6 +132,18 @@ public Builder trustedRootProvider(TrustedRootProvider trustedRootProvider) { private AlgorithmRegistry.HashAlgorithm findHashAlgorithm(Bundle bundle) throws KeylessVerificationException { try { + // When the bundle has no transparency-log entry (e.g. a signed-timestamp-only bundle), + // derive the hash algorithm without one: DSSE bundles use SHA-256 (as in the dsse:0.0.1 + // case below), otherwise fall back to the signing certificate's algorithm. Whether such a + // bundle is permitted at all is enforced in verify(...) via + // VerificationOptions#allowNonTransparencyLogVerification. + if (bundle.getEntries().isEmpty()) { + if (bundle.getDsseEnvelope().isPresent()) { + return AlgorithmRegistry.HashAlgorithm.SHA2_256; + } + var publicKey = Certificates.getLeaf(bundle.getCertPath()).getPublicKey(); + return AlgorithmRegistry.getSigningAlgorithm(publicKey).getHashAlgorithm(); + } var rekorEntry = bundle.getEntries().get(0); var body = rekorEntry.getBodyDecoded(); if ("0.0.1".equals(body.getApiVersion())) { @@ -199,7 +211,8 @@ public void verify(byte[] artifactDigest, Bundle bundle, VerificationOptions opt // a trusted CT log try { - fulcioVerifier.verifySigningCertificate(signingCert); + fulcioVerifier.verifySigningCertificate( + signingCert, options.allowNonTransparencyLogVerification()); } catch (FulcioVerificationException | IOException ex) { throw new KeylessVerificationException( "Fulcio certificate was not valid: " + ex.getMessage(), ex); @@ -209,7 +222,20 @@ public void verify(byte[] artifactDigest, Bundle bundle, VerificationOptions opt checkCertificateMatchers(leafCert, options.getCertificateMatchers()); var hashAlgorithm = findHashAlgorithm(bundle); - var rekorEntry = bundle.getEntries().get(0); + + // A transparency-log entry is required by default. Bundles from instances that do not publish + // to a log (e.g. GitHub's Sigstore instance for private repositories) carry only a signed + // timestamp; verifying those requires opting in and relies on the RFC 3161 timestamp (verified + // below) for trusted time. + var entries = bundle.getEntries(); + if (entries.isEmpty() && !options.allowNonTransparencyLogVerification()) { + throw new KeylessVerificationException( + "Bundle does not contain a transparency log entry. If it is from an instance that does " + + "not publish to a transparency log, set " + + "VerificationOptions.allowNonTransparencyLogVerification(true) to verify using its " + + "signed timestamp instead."); + } + var rekorEntry = entries.isEmpty() ? null : entries.get(0); byte[] signature; if (bundle.getMessageSignature().isPresent()) { // hashedrekord @@ -222,16 +248,20 @@ public void verify(byte[] artifactDigest, Bundle bundle, VerificationOptions opt signature = dsseEnvelope.getSignature(); } - try { - rekorVerifier.verifyEntry(rekorEntry); - } catch (RekorVerificationException ex) { - throw new KeylessVerificationException("Transparency log entry could not be verified", ex); + // A transparency-log entry that is present is always verified; opting in only relaxes the + // requirement that one exists, never the verification of one that is provided. + Instant entryTime = null; + if (rekorEntry != null) { + try { + rekorVerifier.verifyEntry(rekorEntry); + } catch (RekorVerificationException ex) { + throw new KeylessVerificationException("Transparency log entry could not be verified", ex); + } + // if entry was verified and has a SET, get time from it + var set = rekorEntry.getVerification().getSignedEntryTimestamp(); + entryTime = set != null ? rekorEntry.getIntegratedTimeInstant() : null; } - // if entry was verified and has a SET, get time from it - var set = rekorEntry.getVerification().getSignedEntryTimestamp(); - var entryTime = set != null ? rekorEntry.getIntegratedTimeInstant() : null; - verifyTimestamps(leafCert, bundle.getTimestamps(), entryTime, signature); } @@ -325,6 +355,12 @@ private void checkMessageSignature( "Signature could not be processed: " + ex.getMessage(), ex); } + // No transparency-log entry (allowed via VerificationOptions): the artifact digest and + // signature have been verified above, and there is nothing to cross-check against. + if (rekorEntry == null) { + return; + } + // recreate the log entry and check if it matches what was provided in the entry String version; try { @@ -469,6 +505,12 @@ private void checkDsseEnvelope( throw new KeylessVerificationException("Signature could not be processed", se); } + // No transparency-log entry (allowed via VerificationOptions): the payload subject digest and + // DSSE signature have been verified above, and there is nothing to cross-check against. + if (rekorEntry == null) { + return; + } + RekorEntryBody entryBody; try { entryBody = rekorEntry.getBodyDecoded(); diff --git a/sigstore-java/src/main/java/dev/sigstore/VerificationOptions.java b/sigstore-java/src/main/java/dev/sigstore/VerificationOptions.java index c7b6379e0..f3fa357fb 100644 --- a/sigstore-java/src/main/java/dev/sigstore/VerificationOptions.java +++ b/sigstore-java/src/main/java/dev/sigstore/VerificationOptions.java @@ -20,6 +20,7 @@ import java.security.cert.X509Certificate; import java.util.List; import java.util.function.Predicate; +import org.immutables.value.Value.Default; import org.immutables.value.Value.Immutable; @Immutable(singleton = true) @@ -28,6 +29,24 @@ public interface VerificationOptions { /** An allow list of certificate identities to match with. */ List getCertificateMatchers(); + /** + * Whether to allow verifying a bundle that contains no transparency-log entry, relying solely on + * a signed RFC 3161 timestamp for trusted time. Defaults to {@code false}, matching the Sigstore + * public-good instance where a transparency-log entry is always present and required. + * + *

Enabling this weakens the guarantees: without a transparency-log entry the signature is not + * publicly monitorable/auditable. Only enable it for bundles from instances that intentionally do + * not publish to a transparency log (for example, GitHub's Sigstore instance for private + * repositories), and pair it with a pinned trusted root. A signed timestamp is still required. + * + *

This only relaxes the requirement that a transparency-log entry exists; a + * transparency-log entry that is present in the bundle is still fully verified. + */ + @Default + default boolean allowNonTransparencyLogVerification() { + return false; + } + /** * An interface for allowing matching of certificates. Use {@link #fulcio()} to instantiate the * default {@link FulcioCertificateMatcher} implementation. Custom implementations may throw diff --git a/sigstore-java/src/main/java/dev/sigstore/bundle/BundleReader.java b/sigstore-java/src/main/java/dev/sigstore/bundle/BundleReader.java index 3bcd184da..cf9dd7e65 100644 --- a/sigstore-java/src/main/java/dev/sigstore/bundle/BundleReader.java +++ b/sigstore-java/src/main/java/dev/sigstore/bundle/BundleReader.java @@ -50,9 +50,11 @@ static Bundle readBundle(Reader jsonReader) throws BundleParseException { bundleBuilder.mediaType(protoBundle.getMediaType()); - if (protoBundle.getVerificationMaterial().getTlogEntriesCount() == 0) { - throw new BundleParseException("Could not find any tlog entries in bundle json"); - } + // A bundle may legitimately carry no transparency-log entry (for example, GitHub's Sigstore + // instance for private repositories, which relies on a signed RFC 3161 timestamp instead). + // Such bundles are parsed here; whether their absence is acceptable is a verification-time + // policy decision (see VerificationOptions#allowNonTransparencyLogVerification), not a parse + // error. Any tlog entries that ARE present must still be well-formed (inclusion proof required). for (var bundleEntry : protoBundle.getVerificationMaterial().getTlogEntriesList()) { if (!bundleEntry.hasInclusionProof()) { // all consumed bundles must have an inclusion proof diff --git a/sigstore-java/src/main/java/dev/sigstore/fulcio/client/FulcioVerifier.java b/sigstore-java/src/main/java/dev/sigstore/fulcio/client/FulcioVerifier.java index bf1812b32..8ea417f58 100644 --- a/sigstore-java/src/main/java/dev/sigstore/fulcio/client/FulcioVerifier.java +++ b/sigstore-java/src/main/java/dev/sigstore/fulcio/client/FulcioVerifier.java @@ -150,7 +150,30 @@ private void verifyEmbeddedScts(CertPath certPath) throws FulcioVerificationExce */ public void verifySigningCertificate(CertPath signingCertificate) throws FulcioVerificationException, IOException { + verifySigningCertificate(signingCertificate, false); + } + + /** + * Verify a signing certificate, optionally tolerating a trust root that provides no certificate + * transparency logs. + * + * @param signingCertificate the chain to verify + * @param allowEmptyCtLogs when {@code true} and the configured trust root declares no CT + * logs, SCT verification is skipped. This is required for Sigstore instances that do not use + * certificate transparency (for example, GitHub's Sigstore instance for private repositories, + * whose trust root carries zero CT logs and whose certificates embed no SCT). When {@code + * false} (the default) a missing CT log is an error, preserving the strict behavior for the + * public-good instance. If the trust root does provide CT logs, SCTs are always + * verified regardless of this flag. + */ + public void verifySigningCertificate(CertPath signingCertificate, boolean allowEmptyCtLogs) + throws FulcioVerificationException, IOException { CertPath fullCertPath = validateCertPath(signingCertificate); + if (ctLogs.isEmpty() && allowEmptyCtLogs) { + // The trust root declares no certificate-transparency logs, so there is nothing to verify an + // SCT against. Trusted time is established elsewhere (a signed RFC 3161 timestamp). + return; + } verifySct(fullCertPath); } From 8da4a47bf4768728c6a03a8bf3ccb3b0b35d233c Mon Sep 17 00:00:00 2001 From: Anthony Dahanne Date: Fri, 24 Jul 2026 15:41:41 -0400 Subject: [PATCH 2/5] Add tests for verifying GitHub private bundles * bundles without transparency log entries Signed-off-by: Anthony Dahanne --- .../dev/sigstore/bundle/BundleReader.java | 3 +- .../dev/sigstore/KeylessVerifierTest.java | 140 +++++++++++++++++- .../bundles/bundle-github-private-no-tlog.zip | Bin 0 -> 11488 bytes 3 files changed, 139 insertions(+), 4 deletions(-) create mode 100644 sigstore-java/src/test/resources/dev/sigstore/samples/bundles/bundle-github-private-no-tlog.zip diff --git a/sigstore-java/src/main/java/dev/sigstore/bundle/BundleReader.java b/sigstore-java/src/main/java/dev/sigstore/bundle/BundleReader.java index cf9dd7e65..d3acd4632 100644 --- a/sigstore-java/src/main/java/dev/sigstore/bundle/BundleReader.java +++ b/sigstore-java/src/main/java/dev/sigstore/bundle/BundleReader.java @@ -54,7 +54,8 @@ static Bundle readBundle(Reader jsonReader) throws BundleParseException { // instance for private repositories, which relies on a signed RFC 3161 timestamp instead). // Such bundles are parsed here; whether their absence is acceptable is a verification-time // policy decision (see VerificationOptions#allowNonTransparencyLogVerification), not a parse - // error. Any tlog entries that ARE present must still be well-formed (inclusion proof required). + // error. Any tlog entries that ARE present must still be well-formed (inclusion proof + // required). for (var bundleEntry : protoBundle.getVerificationMaterial().getTlogEntriesList()) { if (!bundleEntry.hasInclusionProof()) { // all consumed bundles must have an inclusion proof diff --git a/sigstore-java/src/test/java/dev/sigstore/KeylessVerifierTest.java b/sigstore-java/src/test/java/dev/sigstore/KeylessVerifierTest.java index 04ebd68cd..197e44afd 100644 --- a/sigstore-java/src/test/java/dev/sigstore/KeylessVerifierTest.java +++ b/sigstore-java/src/test/java/dev/sigstore/KeylessVerifierTest.java @@ -17,6 +17,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.hash.Hashing; +import com.google.common.io.BaseEncoding; import com.google.common.io.Resources; import com.google.gson.JsonParser; import dev.sigstore.VerificationOptions.CertificateMatcher; @@ -34,10 +35,13 @@ import java.security.cert.X509Certificate; import java.util.List; import java.util.stream.Stream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; import org.hamcrest.CoreMatchers; import org.hamcrest.MatcherAssert; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -519,9 +523,15 @@ public void testVerify_noTlogEntries_rekorV1() throws Exception { var artifact = Resources.getResource("dev/sigstore/samples/bundles/artifact.txt").getPath(); var verifier = KeylessVerifier.builder().sigstoreStagingDefaults().build(); - Assertions.assertThrows( - IndexOutOfBoundsException.class, - () -> verifier.verify(Path.of(artifact), testBundle, VerificationOptions.empty())); + // By default a transparency-log entry is required, so a bundle without one is rejected with a + // clear error (rather than the incidental IndexOutOfBoundsException it used to raise). + var ex = + Assertions.assertThrows( + KeylessVerificationException.class, + () -> verifier.verify(Path.of(artifact), testBundle, VerificationOptions.empty())); + MatcherAssert.assertThat( + ex.getMessage(), + CoreMatchers.startsWith("Bundle does not contain a transparency log entry")); } @Test @@ -871,4 +881,128 @@ public void testVerify_wrongArtifactHashAlgorithm() throws Exception { CoreMatchers.startsWith( "Provided artifact digest does not match digest used for verification")); } + + // The fixture below is a real SLSA build-provenance attestation produced by GitHub Actions for a + // *private* repository. Attestations for private repositories are signed by GitHub's own Sigstore + // instance, which (unlike the public-good instance) does not publish to a transparency log and + // does not use certificate transparency: the bundle carries zero tlog entries and relies on a + // signed RFC 3161 timestamp for trusted time, and the trust root that GitHub distributes for it + // (via `gh attestation trusted-root`) contains zero CT logs. Verifying such a bundle therefore + // requires opting in via VerificationOptions.allowNonTransparencyLogVerification(true). + // + // The zip is the exact release asset (unmodified) and contains two files: + // - attestation.sigstore.json : the sigstore bundle (DSSE, in-toto SLSA provenance predicate) + // - trusted_root.jsonl : the trust roots as JSON Lines. `gh attestation trusted-root` + // emits more than one (public-good and GitHub); sigstore-java + // consumes a single trust root, so the tests below select the + // GitHub one (the line describing the fulcio.githubapp.com CA). + private static final String GH_PRIVATE_ATTESTATION_ZIP = + "dev/sigstore/samples/bundles/bundle-github-private-no-tlog.zip"; + private static final String GH_PRIVATE_SIGNER_SAN = + "https://github.com/neverendingsupport/slsa-attestations/.github/workflows/attest.yml@9f6d9dc1bfc02986955721eb15f89ad618f1cedb"; + private static final String GH_PRIVATE_OIDC_ISSUER = + "https://token.actions.githubusercontent.com"; + // sha256 of one of the artifacts recorded as a subject in the attestation. + private static final String GH_PRIVATE_SUBJECT_SHA256 = + "caa015ef69e9bc31a41322d6c71563ed9600c75bb988e4d639b1edc578580551"; + + @Test + public void testVerify_noTransparencyLog_gitHubPrivateInstance(@TempDir Path tempDir) + throws Exception { + var bundle = gitHubPrivateBundle(); + var verifier = gitHubPrivateVerifier(tempDir); + var options = + VerificationOptions.builder() + .allowNonTransparencyLogVerification(true) + .addCertificateMatchers( + CertificateMatcher.fulcio() + .subjectAlternativeName(StringMatcher.string(GH_PRIVATE_SIGNER_SAN)) + .issuer(StringMatcher.string(GH_PRIVATE_OIDC_ISSUER)) + .build()) + .build(); + + Assertions.assertDoesNotThrow( + () -> + verifier.verify( + BaseEncoding.base16().lowerCase().decode(GH_PRIVATE_SUBJECT_SHA256), + bundle, + options)); + } + + @Test + public void testVerify_noTransparencyLog_rejectedWithoutOptIn(@TempDir Path tempDir) + throws Exception { + var bundle = gitHubPrivateBundle(); + var verifier = gitHubPrivateVerifier(tempDir); + + // Default options do not permit skipping the transparency log, so the bundle is rejected. + Assertions.assertThrows( + KeylessVerificationException.class, + () -> + verifier.verify( + BaseEncoding.base16().lowerCase().decode(GH_PRIVATE_SUBJECT_SHA256), + bundle, + VerificationOptions.empty())); + } + + @Test + public void testVerify_noTransparencyLog_optInStillChecksArtifactDigest(@TempDir Path tempDir) + throws Exception { + var bundle = gitHubPrivateBundle(); + var verifier = gitHubPrivateVerifier(tempDir); + var options = VerificationOptions.builder().allowNonTransparencyLogVerification(true).build(); + + // Opting in only relaxes the transparency-log requirement; the artifact digest must still be + // one + // of the attested subjects. + var badArtifactDigest = + Hashing.sha256().hashString("nonsense", StandardCharsets.UTF_8).asBytes(); + var ex = + Assertions.assertThrows( + KeylessVerificationException.class, + () -> verifier.verify(badArtifactDigest, bundle, options)); + MatcherAssert.assertThat( + ex.getMessage(), + CoreMatchers.startsWith( + "Provided artifact digest does not match any subject sha256 digests in DSSE payload")); + } + + private static Bundle gitHubPrivateBundle() throws Exception { + return Bundle.from( + new StringReader( + new String( + readZipEntry(GH_PRIVATE_ATTESTATION_ZIP, "attestation.sigstore.json"), + StandardCharsets.UTF_8))); + } + + private static KeylessVerifier gitHubPrivateVerifier(Path tempDir) throws Exception { + var jsonl = + new String( + readZipEntry(GH_PRIVATE_ATTESTATION_ZIP, "trusted_root.jsonl"), StandardCharsets.UTF_8); + var gitHubTrustedRoot = + jsonl + .lines() + .filter(line -> line.contains("fulcio.githubapp.com")) + .findFirst() + .orElseThrow( + () -> + new IllegalStateException("no GitHub trust root found in trusted_root.jsonl")); + var trustedRootPath = tempDir.resolve("github-trusted-root.json"); + Files.writeString(trustedRootPath, gitHubTrustedRoot); + return KeylessVerifier.builder() + .trustedRootProvider(TrustedRootProvider.from(trustedRootPath)) + .build(); + } + + private static byte[] readZipEntry(String zipResource, String entryName) throws Exception { + try (var zis = new ZipInputStream(Resources.getResource(zipResource).openStream())) { + ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { + if (entry.getName().equals(entryName)) { + return zis.readAllBytes(); + } + } + } + throw new IllegalStateException("entry '" + entryName + "' not found in " + zipResource); + } } diff --git a/sigstore-java/src/test/resources/dev/sigstore/samples/bundles/bundle-github-private-no-tlog.zip b/sigstore-java/src/test/resources/dev/sigstore/samples/bundles/bundle-github-private-no-tlog.zip new file mode 100644 index 0000000000000000000000000000000000000000..0ef54ee3d85d1455565478a1d41f2ad502de2428 GIT binary patch literal 11488 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 literal 0 HcmV?d00001 From 8de44323784990c4f9593af6518a927f79d61677 Mon Sep 17 00:00:00 2001 From: Anthony Dahanne Date: Fri, 24 Jul 2026 16:07:07 -0400 Subject: [PATCH 3/5] PR #1252 review suggestions fixed Signed-off-by: Anthony Dahanne --- .../main/java/dev/sigstore/cli/Verify.java | 13 ---- .../java/dev/sigstore/KeylessVerifier.java | 77 ++++++++----------- .../dev/sigstore/VerificationOptions.java | 46 +++++++---- .../dev/sigstore/bundle/BundleReader.java | 7 +- .../fulcio/client/FulcioVerifier.java | 30 +++----- .../dev/sigstore/KeylessVerifierTest.java | 38 +++++---- 6 files changed, 99 insertions(+), 112 deletions(-) diff --git a/sigstore-cli/src/main/java/dev/sigstore/cli/Verify.java b/sigstore-cli/src/main/java/dev/sigstore/cli/Verify.java index 4e7898782..779bd0561 100644 --- a/sigstore-cli/src/main/java/dev/sigstore/cli/Verify.java +++ b/sigstore-cli/src/main/java/dev/sigstore/cli/Verify.java @@ -113,16 +113,6 @@ static class Policy { required = false) Path workingDirectory; - @Option( - names = {"--allow-non-transparency-log"}, - description = - "verify a bundle that has no transparency-log entry, relying on a signed RFC 3161 " - + "timestamp instead (reduced assurance; for instances that do not publish to a " - + "transparency log, e.g. GitHub's Sigstore instance for private repositories)", - required = false, - defaultValue = "false") - Boolean allowNonTransparencyLog; - @Override public Integer call() throws Exception { byte[] digest; @@ -152,9 +142,6 @@ public Integer call() throws Exception { .subjectAlternativeName(StringMatcher.string(policy.certificateSan)) .build()); } - if (allowNonTransparencyLog) { - verificationOptionsBuilder.allowNonTransparencyLogVerification(true); - } var verificationOptions = verificationOptionsBuilder.build(); KeylessVerifier verifier; diff --git a/sigstore-java/src/main/java/dev/sigstore/KeylessVerifier.java b/sigstore-java/src/main/java/dev/sigstore/KeylessVerifier.java index f4cfcf61a..c2f89bccd 100644 --- a/sigstore-java/src/main/java/dev/sigstore/KeylessVerifier.java +++ b/sigstore-java/src/main/java/dev/sigstore/KeylessVerifier.java @@ -132,43 +132,31 @@ public Builder trustedRootProvider(TrustedRootProvider trustedRootProvider) { private AlgorithmRegistry.HashAlgorithm findHashAlgorithm(Bundle bundle) throws KeylessVerificationException { try { - // When the bundle has no transparency-log entry (e.g. a signed-timestamp-only bundle), - // derive the hash algorithm without one: DSSE bundles use SHA-256 (as in the dsse:0.0.1 - // case below), otherwise fall back to the signing certificate's algorithm. Whether such a - // bundle is permitted at all is enforced in verify(...) via - // VerificationOptions#allowNonTransparencyLogVerification. - if (bundle.getEntries().isEmpty()) { - if (bundle.getDsseEnvelope().isPresent()) { - return AlgorithmRegistry.HashAlgorithm.SHA2_256; - } - var publicKey = Certificates.getLeaf(bundle.getCertPath()).getPublicKey(); - return AlgorithmRegistry.getSigningAlgorithm(publicKey).getHashAlgorithm(); - } - var rekorEntry = bundle.getEntries().get(0); - var body = rekorEntry.getBodyDecoded(); - if ("0.0.1".equals(body.getApiVersion())) { - if ("hashedrekord".equals(body.getKind())) { - var entry = RekorTypes.getHashedRekordV001(rekorEntry); - switch (entry.getData().getHash().getAlgorithm()) { - case SHA_256: - return AlgorithmRegistry.HashAlgorithm.SHA2_256; - case SHA_384: - return AlgorithmRegistry.HashAlgorithm.SHA2_384; - case SHA_512: - return AlgorithmRegistry.HashAlgorithm.SHA2_512; + // rekor 0.0.1 hashedrekord entries encode the hash algorithm in the entry itself. Others + // (rekor 0.0.2, a bundle with no transparency-log entry) derive it from the signing + // certificate's public key. + if (!bundle.getEntries().isEmpty()) { + var rekorEntry = bundle.getEntries().get(0); + var body = rekorEntry.getBodyDecoded(); + if ("0.0.1".equals(body.getApiVersion())) { + if ("hashedrekord".equals(body.getKind())) { + var entry = RekorTypes.getHashedRekordV001(rekorEntry); + switch (entry.getData().getHash().getAlgorithm()) { + case SHA_256: + return AlgorithmRegistry.HashAlgorithm.SHA2_256; + case SHA_384: + return AlgorithmRegistry.HashAlgorithm.SHA2_384; + case SHA_512: + return AlgorithmRegistry.HashAlgorithm.SHA2_512; + } + } else if ("dsse".equals(body.getKind())) { + // dsse:0.0.1 is always sha256 + return AlgorithmRegistry.HashAlgorithm.SHA2_256; } - } else if ("dsse".equals(body.getKind())) { - // dsse:0.0.1 is always sha256 - return AlgorithmRegistry.HashAlgorithm.SHA2_256; } - } else if ("0.0.2".equals(body.getApiVersion())) { - // rekor v2 entries must conform to the Algorithm Registry - var publicKey = Certificates.getLeaf(bundle.getCertPath()).getPublicKey(); - var signingAlgorithm = AlgorithmRegistry.getSigningAlgorithm(publicKey); - return signingAlgorithm.getHashAlgorithm(); } - throw new KeylessVerificationException( - "Unsupported entry type: '" + body.getKind() + ":" + body.getApiVersion() + "'"); + var publicKey = Certificates.getLeaf(bundle.getCertPath()).getPublicKey(); + return AlgorithmRegistry.getSigningAlgorithm(publicKey).getHashAlgorithm(); } catch (JsonParseException | RekorTypeException | UnsupportedAlgorithmException ex) { throw new KeylessVerificationException( "Could not determine hash algorithm from sigstore bundle", ex); @@ -211,8 +199,7 @@ public void verify(byte[] artifactDigest, Bundle bundle, VerificationOptions opt // a trusted CT log try { - fulcioVerifier.verifySigningCertificate( - signingCert, options.allowNonTransparencyLogVerification()); + fulcioVerifier.verifySigningCertificate(signingCert, options.getCtLogOptions()); } catch (FulcioVerificationException | IOException ex) { throw new KeylessVerificationException( "Fulcio certificate was not valid: " + ex.getMessage(), ex); @@ -223,17 +210,17 @@ public void verify(byte[] artifactDigest, Bundle bundle, VerificationOptions opt var hashAlgorithm = findHashAlgorithm(bundle); - // A transparency-log entry is required by default. Bundles from instances that do not publish - // to a log (e.g. GitHub's Sigstore instance for private repositories) carry only a signed - // timestamp; verifying those requires opting in and relies on the RFC 3161 timestamp (verified - // below) for trusted time. + // A transparency-log entry is required unless disabled (e.g. a private deployment that verifies + // via a signed timestamp instead). We also refuse to ignore an entry that is present, so a + // disabled policy cannot silently skip verifying a log entry the bundle actually contains. var entries = bundle.getEntries(); - if (entries.isEmpty() && !options.allowNonTransparencyLogVerification()) { + if (entries.isEmpty() && options.getTLogOptions().isEnabled()) { + throw new KeylessVerificationException( + "No transparency log entry found and transparency-log verification is required"); + } + if (!entries.isEmpty() && !options.getTLogOptions().isEnabled()) { throw new KeylessVerificationException( - "Bundle does not contain a transparency log entry. If it is from an instance that does " - + "not publish to a transparency log, set " - + "VerificationOptions.allowNonTransparencyLogVerification(true) to verify using its " - + "signed timestamp instead."); + "Bundle contains a transparency log entry but transparency-log verification is disabled"); } var rekorEntry = entries.isEmpty() ? null : entries.get(0); diff --git a/sigstore-java/src/main/java/dev/sigstore/VerificationOptions.java b/sigstore-java/src/main/java/dev/sigstore/VerificationOptions.java index f3fa357fb..0e2f8c404 100644 --- a/sigstore-java/src/main/java/dev/sigstore/VerificationOptions.java +++ b/sigstore-java/src/main/java/dev/sigstore/VerificationOptions.java @@ -29,22 +29,38 @@ public interface VerificationOptions { /** An allow list of certificate identities to match with. */ List getCertificateMatchers(); - /** - * Whether to allow verifying a bundle that contains no transparency-log entry, relying solely on - * a signed RFC 3161 timestamp for trusted time. Defaults to {@code false}, matching the Sigstore - * public-good instance where a transparency-log entry is always present and required. - * - *

Enabling this weakens the guarantees: without a transparency-log entry the signature is not - * publicly monitorable/auditable. Only enable it for bundles from instances that intentionally do - * not publish to a transparency log (for example, GitHub's Sigstore instance for private - * repositories), and pair it with a pinned trusted root. A signed timestamp is still required. - * - *

This only relaxes the requirement that a transparency-log entry exists; a - * transparency-log entry that is present in the bundle is still fully verified. - */ + /** Transparency-log (rekor) verification policy; defaults to requiring an entry. */ @Default - default boolean allowNonTransparencyLogVerification() { - return false; + default TLogOptions getTLogOptions() { + return TLogOptions.builder().isEnabled(true).build(); + } + + /** Certificate-transparency (SCT) verification policy; defaults to requiring an SCT. */ + @Default + default CTLogOptions getCtLogOptions() { + return CTLogOptions.builder().isEnabled(true).build(); + } + + /** Transparency-log verification options. */ + @Immutable + interface TLogOptions { + /** Whether a transparency-log entry is required and verified. */ + boolean isEnabled(); + + static ImmutableTLogOptions.Builder builder() { + return ImmutableTLogOptions.builder(); + } + } + + /** Certificate-transparency verification options. */ + @Immutable + interface CTLogOptions { + /** Whether a certificate-transparency log entry (SCT) is required and verified. */ + boolean isEnabled(); + + static ImmutableCTLogOptions.Builder builder() { + return ImmutableCTLogOptions.builder(); + } } /** diff --git a/sigstore-java/src/main/java/dev/sigstore/bundle/BundleReader.java b/sigstore-java/src/main/java/dev/sigstore/bundle/BundleReader.java index d3acd4632..305741232 100644 --- a/sigstore-java/src/main/java/dev/sigstore/bundle/BundleReader.java +++ b/sigstore-java/src/main/java/dev/sigstore/bundle/BundleReader.java @@ -50,12 +50,7 @@ static Bundle readBundle(Reader jsonReader) throws BundleParseException { bundleBuilder.mediaType(protoBundle.getMediaType()); - // A bundle may legitimately carry no transparency-log entry (for example, GitHub's Sigstore - // instance for private repositories, which relies on a signed RFC 3161 timestamp instead). - // Such bundles are parsed here; whether their absence is acceptable is a verification-time - // policy decision (see VerificationOptions#allowNonTransparencyLogVerification), not a parse - // error. Any tlog entries that ARE present must still be well-formed (inclusion proof - // required). + // A bundle may skip tlog entries in private environments that don't require them. for (var bundleEntry : protoBundle.getVerificationMaterial().getTlogEntriesList()) { if (!bundleEntry.hasInclusionProof()) { // all consumed bundles must have an inclusion proof diff --git a/sigstore-java/src/main/java/dev/sigstore/fulcio/client/FulcioVerifier.java b/sigstore-java/src/main/java/dev/sigstore/fulcio/client/FulcioVerifier.java index 8ea417f58..fd7253355 100644 --- a/sigstore-java/src/main/java/dev/sigstore/fulcio/client/FulcioVerifier.java +++ b/sigstore-java/src/main/java/dev/sigstore/fulcio/client/FulcioVerifier.java @@ -16,6 +16,7 @@ package dev.sigstore.fulcio.client; import com.google.common.annotations.VisibleForTesting; +import dev.sigstore.VerificationOptions.CTLogOptions; import dev.sigstore.encryption.certificates.Certificates; import dev.sigstore.encryption.certificates.transparency.CTLogInfo; import dev.sigstore.encryption.certificates.transparency.CTVerificationResult; @@ -44,7 +45,7 @@ import java.util.Map; import java.util.stream.Collectors; -/** Verifier for fulcio generated signing cerificates */ +/** Verifier for fulcio generated signing certificates */ public class FulcioVerifier { private final List cas; private final List ctLogs; @@ -150,28 +151,17 @@ private void verifyEmbeddedScts(CertPath certPath) throws FulcioVerificationExce */ public void verifySigningCertificate(CertPath signingCertificate) throws FulcioVerificationException, IOException { - verifySigningCertificate(signingCertificate, false); + verifySigningCertificate(signingCertificate, CTLogOptions.builder().isEnabled(true).build()); } /** - * Verify a signing certificate, optionally tolerating a trust root that provides no certificate - * transparency logs. - * - * @param signingCertificate the chain to verify - * @param allowEmptyCtLogs when {@code true} and the configured trust root declares no CT - * logs, SCT verification is skipped. This is required for Sigstore instances that do not use - * certificate transparency (for example, GitHub's Sigstore instance for private repositories, - * whose trust root carries zero CT logs and whose certificates embed no SCT). When {@code - * false} (the default) a missing CT log is an error, preserving the strict behavior for the - * public-good instance. If the trust root does provide CT logs, SCTs are always - * verified regardless of this flag. + * Verify a signing certificate, applying the given certificate-transparency policy. When SCT + * verification is disabled (ex: a private deployment without CT logs) the SCT check is skipped. */ - public void verifySigningCertificate(CertPath signingCertificate, boolean allowEmptyCtLogs) + public void verifySigningCertificate(CertPath signingCertificate, CTLogOptions ctLogOptions) throws FulcioVerificationException, IOException { CertPath fullCertPath = validateCertPath(signingCertificate); - if (ctLogs.isEmpty() && allowEmptyCtLogs) { - // The trust root declares no certificate-transparency logs, so there is nothing to verify an - // SCT against. Trusted time is established elsewhere (a signed RFC 3161 timestamp). + if (!ctLogOptions.isEnabled()) { return; } verifySct(fullCertPath); @@ -200,7 +190,8 @@ CertPath validateCertPath(CertPath signingCertificate) throws FulcioVerification } catch (NoSuchAlgorithmException e) { // throw new RuntimeException( - "No PKIX CertPathValidator, we probably shouldn't be here, but this seems to be a system library error not a program control flow issue", + "No PKIX CertPathValidator, we probably shouldn't be here, but this seems to be a system" + + " library error not a program control flow issue", e); } @@ -220,7 +211,8 @@ CertPath validateCertPath(CertPath signingCertificate) throws FulcioVerification pkixParams = new PKIXParameters(Collections.singleton(ca.asTrustAnchor())); } catch (InvalidAlgorithmParameterException | CertificateException e) { throw new RuntimeException( - "Can't create PKIX parameters for fulcioRoot. This should have been checked when generating a verifier instance", + "Can't create PKIX parameters for fulcioRoot. This should have been checked when" + + " generating a verifier instance", e); } pkixParams.setRevocationEnabled(false); diff --git a/sigstore-java/src/test/java/dev/sigstore/KeylessVerifierTest.java b/sigstore-java/src/test/java/dev/sigstore/KeylessVerifierTest.java index 197e44afd..fb85c0f0c 100644 --- a/sigstore-java/src/test/java/dev/sigstore/KeylessVerifierTest.java +++ b/sigstore-java/src/test/java/dev/sigstore/KeylessVerifierTest.java @@ -20,7 +20,9 @@ import com.google.common.io.BaseEncoding; import com.google.common.io.Resources; import com.google.gson.JsonParser; +import dev.sigstore.VerificationOptions.CTLogOptions; import dev.sigstore.VerificationOptions.CertificateMatcher; +import dev.sigstore.VerificationOptions.TLogOptions; import dev.sigstore.bundle.Bundle; import dev.sigstore.bundle.ImmutableBundle; import dev.sigstore.encryption.signers.Signers; @@ -419,7 +421,9 @@ public void verifyCertificateMatches_noneMatch() throws Exception { .issuer(StringMatcher.string("not-match-again")) .build()))); Assertions.assertEquals( - "No provided certificate identities matched values in certificate: [{issuer:'String: not-match',san:'String: not-match'},{issuer:'String: not-match-again',san:'String: not-match-again'}]", + "No provided certificate identities matched values in certificate: [{issuer:'String:" + + " not-match',san:'String: not-match'},{issuer:'String: not-match-again',san:'String:" + + " not-match-again'}]", ex.getMessage()); } @@ -530,8 +534,7 @@ public void testVerify_noTlogEntries_rekorV1() throws Exception { KeylessVerificationException.class, () -> verifier.verify(Path.of(artifact), testBundle, VerificationOptions.empty())); MatcherAssert.assertThat( - ex.getMessage(), - CoreMatchers.startsWith("Bundle does not contain a transparency log entry")); + ex.getMessage(), CoreMatchers.startsWith("No transparency log entry found")); } @Test @@ -559,7 +562,8 @@ public void testVerify_dsseWrongPayloadType_rekorV1() throws Exception { Bundle.from(new StringReader(invalidBundleFile)), VerificationOptions.empty())); Assertions.assertEquals( - "DSSE envelope must have payload type application/vnd.in-toto+json, but found 'application/json'", + "DSSE envelope must have payload type application/vnd.in-toto+json, but found" + + " 'application/json'", ex.getMessage()); } @@ -590,7 +594,7 @@ public void testVerify_unsupportedRekorVersion_rekorV2() throws Exception { Path.of(artifact), Bundle.from(new StringReader(invalidBundleFile)), VerificationOptions.empty())); - Assertions.assertEquals("Unsupported entry type: 'hashedrekord:0.0.3'", ex.getMessage()); + Assertions.assertEquals("Unsupported hashedrekord version: 0.0.3", ex.getMessage()); } @Test @@ -761,7 +765,8 @@ public void testVerify_noRfc3161Timestamps_rekorV2() throws Exception { var validRfc3161Timestamps = "\"rfc3161Timestamps\": [{\n" - + " \"signedTimestamp\": \"MIIC1DADAgEAMIICywYJKoZIhvcNAQcCoIICvDCCArgCAQMxDTALBglghkgBZQMEAgEwgcIGCyqGSIb3DQEJEAEEoIGyBIGvMIGsAgEBBgkrBgEEAYO/MAIwMTANBglghkgBZQMEAgEFAAQgtF/jtMHzKroX8UjkT/BViNcIlC5Y7za/y4n5cjasXD0CFQCxAdEfFrtmTuy4mcW7QjLeeOAUXhgPMjAyNTA3MDIxODUxNTFaMAMCAQECCE0pZTAXtl1eoDKkMDAuMRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxFTATBgNVBAMTDHNpZ3N0b3JlLXRzYaAAMYIB2zCCAdcCAQEwUTA5MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxIDAeBgNVBAMTF3NpZ3N0b3JlLXRzYS1zZWxmc2lnbmVkAhQKNaEGYdXiQXPGiZan8n3yfgN8pzALBglghkgBZQMEAgGggfwwGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNTA3MDIxODUxNTFaMC8GCSqGSIb3DQEJBDEiBCCiICYObTnM098xx9niiVyPo+gxp4FTvN94z4K6gH/LgjCBjgYLKoZIhvcNAQkQAi8xfzB9MHsweQQgBvT/4Ef+s1mZtzOw16MjUBz8GOTAM2aoRdd1NudLJ0QwVTA9pDswOTEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MSAwHgYDVQQDExdzaWdzdG9yZS10c2Etc2VsZnNpZ25lZAIUCjWhBmHV4kFzxomWp/J98n4DfKcwCgYIKoZIzj0EAwIEZzBlAjB0/hfB4Hm4GO5SZYuDzCtgnNdXQkETtx/QTtILM09awUdez2kQNmCAkLtPBf8ojB8CMQDfRBy5WNohfrWNDh0o+NBR7Yj67vsUyBS1WK5nT5QatouJQ3PbtSJN3Kk8xsiVxFc=\"\n" + + " \"signedTimestamp\":" + + " \"MIIC1DADAgEAMIICywYJKoZIhvcNAQcCoIICvDCCArgCAQMxDTALBglghkgBZQMEAgEwgcIGCyqGSIb3DQEJEAEEoIGyBIGvMIGsAgEBBgkrBgEEAYO/MAIwMTANBglghkgBZQMEAgEFAAQgtF/jtMHzKroX8UjkT/BViNcIlC5Y7za/y4n5cjasXD0CFQCxAdEfFrtmTuy4mcW7QjLeeOAUXhgPMjAyNTA3MDIxODUxNTFaMAMCAQECCE0pZTAXtl1eoDKkMDAuMRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxFTATBgNVBAMTDHNpZ3N0b3JlLXRzYaAAMYIB2zCCAdcCAQEwUTA5MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxIDAeBgNVBAMTF3NpZ3N0b3JlLXRzYS1zZWxmc2lnbmVkAhQKNaEGYdXiQXPGiZan8n3yfgN8pzALBglghkgBZQMEAgGggfwwGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNTA3MDIxODUxNTFaMC8GCSqGSIb3DQEJBDEiBCCiICYObTnM098xx9niiVyPo+gxp4FTvN94z4K6gH/LgjCBjgYLKoZIhvcNAQkQAi8xfzB9MHsweQQgBvT/4Ef+s1mZtzOw16MjUBz8GOTAM2aoRdd1NudLJ0QwVTA9pDswOTEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MSAwHgYDVQQDExdzaWdzdG9yZS10c2Etc2VsZnNpZ25lZAIUCjWhBmHV4kFzxomWp/J98n4DfKcwCgYIKoZIzj0EAwIEZzBlAjB0/hfB4Hm4GO5SZYuDzCtgnNdXQkETtx/QTtILM09awUdez2kQNmCAkLtPBf8ojB8CMQDfRBy5WNohfrWNDh0o+NBR7Yj67vsUyBS1WK5nT5QatouJQ3PbtSJN3Kk8xsiVxFc=\"\n" + " }]"; var noRfc3161Timestamps = "\"rfc3161Timestamps\": []"; var invalidBundleFile = bundleFile.replace(validRfc3161Timestamps, noRfc3161Timestamps); @@ -888,9 +893,9 @@ public void testVerify_wrongArtifactHashAlgorithm() throws Exception { // does not use certificate transparency: the bundle carries zero tlog entries and relies on a // signed RFC 3161 timestamp for trusted time, and the trust root that GitHub distributes for it // (via `gh attestation trusted-root`) contains zero CT logs. Verifying such a bundle therefore - // requires opting in via VerificationOptions.allowNonTransparencyLogVerification(true). + // requires a policy that disables both transparency-log and certificate-transparency checks. // - // The zip is the exact release asset (unmodified) and contains two files: + // The zip file contains two files: // - attestation.sigstore.json : the sigstore bundle (DSSE, in-toto SLSA provenance predicate) // - trusted_root.jsonl : the trust roots as JSON Lines. `gh attestation trusted-root` // emits more than one (public-good and GitHub); sigstore-java @@ -912,8 +917,7 @@ public void testVerify_noTransparencyLog_gitHubPrivateInstance(@TempDir Path tem var bundle = gitHubPrivateBundle(); var verifier = gitHubPrivateVerifier(tempDir); var options = - VerificationOptions.builder() - .allowNonTransparencyLogVerification(true) + gitHubPrivateOptions() .addCertificateMatchers( CertificateMatcher.fulcio() .subjectAlternativeName(StringMatcher.string(GH_PRIVATE_SIGNER_SAN)) @@ -950,11 +954,10 @@ public void testVerify_noTransparencyLog_optInStillChecksArtifactDigest(@TempDir throws Exception { var bundle = gitHubPrivateBundle(); var verifier = gitHubPrivateVerifier(tempDir); - var options = VerificationOptions.builder().allowNonTransparencyLogVerification(true).build(); + var options = gitHubPrivateOptions().build(); - // Opting in only relaxes the transparency-log requirement; the artifact digest must still be - // one - // of the attested subjects. + // Relaxing the tlog/CT requirements does not relax the rest: the artifact digest must still be + // one of the attested subjects. var badArtifactDigest = Hashing.sha256().hashString("nonsense", StandardCharsets.UTF_8).asBytes(); var ex = @@ -967,6 +970,13 @@ public void testVerify_noTransparencyLog_optInStillChecksArtifactDigest(@TempDir "Provided artifact digest does not match any subject sha256 digests in DSSE payload")); } + // A private-deployment policy: neither a transparency-log entry nor an SCT is required. + private static ImmutableVerificationOptions.Builder gitHubPrivateOptions() { + return VerificationOptions.builder() + .tLogOptions(TLogOptions.builder().isEnabled(false).build()) + .ctLogOptions(CTLogOptions.builder().isEnabled(false).build()); + } + private static Bundle gitHubPrivateBundle() throws Exception { return Bundle.from( new StringReader( From 50de35f08f7cff870c2e20acd956529909a91b5e Mon Sep 17 00:00:00 2001 From: Anthony Dahanne Date: Tue, 28 Jul 2026 11:51:55 -0400 Subject: [PATCH 4/5] PR #1252 review suggestions fixed * Only when there are no CT Logs (private deployment) we can skip SCT verification Signed-off-by: Anthony Dahanne --- .../java/dev/sigstore/KeylessVerifier.java | 5 ++--- .../dev/sigstore/bundle/BundleReader.java | 1 - .../fulcio/client/FulcioVerifier.java | 21 ++++++++++++------- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/sigstore-java/src/main/java/dev/sigstore/KeylessVerifier.java b/sigstore-java/src/main/java/dev/sigstore/KeylessVerifier.java index c2f89bccd..2f57d30fb 100644 --- a/sigstore-java/src/main/java/dev/sigstore/KeylessVerifier.java +++ b/sigstore-java/src/main/java/dev/sigstore/KeylessVerifier.java @@ -132,9 +132,8 @@ public Builder trustedRootProvider(TrustedRootProvider trustedRootProvider) { private AlgorithmRegistry.HashAlgorithm findHashAlgorithm(Bundle bundle) throws KeylessVerificationException { try { - // rekor 0.0.1 hashedrekord entries encode the hash algorithm in the entry itself. Others - // (rekor 0.0.2, a bundle with no transparency-log entry) derive it from the signing - // certificate's public key. + // special case hashedrekord:0.0.1 and dsse:0.0.1 which may contain legacy public key to hash + // algorithm mappings if (!bundle.getEntries().isEmpty()) { var rekorEntry = bundle.getEntries().get(0); var body = rekorEntry.getBodyDecoded(); diff --git a/sigstore-java/src/main/java/dev/sigstore/bundle/BundleReader.java b/sigstore-java/src/main/java/dev/sigstore/bundle/BundleReader.java index 305741232..8962b6473 100644 --- a/sigstore-java/src/main/java/dev/sigstore/bundle/BundleReader.java +++ b/sigstore-java/src/main/java/dev/sigstore/bundle/BundleReader.java @@ -50,7 +50,6 @@ static Bundle readBundle(Reader jsonReader) throws BundleParseException { bundleBuilder.mediaType(protoBundle.getMediaType()); - // A bundle may skip tlog entries in private environments that don't require them. for (var bundleEntry : protoBundle.getVerificationMaterial().getTlogEntriesList()) { if (!bundleEntry.hasInclusionProof()) { // all consumed bundles must have an inclusion proof diff --git a/sigstore-java/src/main/java/dev/sigstore/fulcio/client/FulcioVerifier.java b/sigstore-java/src/main/java/dev/sigstore/fulcio/client/FulcioVerifier.java index fd7253355..4887e4702 100644 --- a/sigstore-java/src/main/java/dev/sigstore/fulcio/client/FulcioVerifier.java +++ b/sigstore-java/src/main/java/dev/sigstore/fulcio/client/FulcioVerifier.java @@ -96,7 +96,17 @@ private FulcioVerifier( @VisibleForTesting void verifySct(CertPath fullCertPath) throws FulcioVerificationException { - if (ctLogs.size() == 0) { + verifySct(fullCertPath, CTLogOptions.builder().isEnabled(true).build()); + } + + @VisibleForTesting + void verifySct(CertPath fullCertPath, CTLogOptions ctLogOptions) + throws FulcioVerificationException { + if (ctLogs.isEmpty()) { + // Only when there are no CT Logs (private deployment) we can skip SCT verification + if (!ctLogOptions.isEnabled()) { + return; + } throw new FulcioVerificationException("No ct logs were provided to verifier"); } @@ -155,16 +165,13 @@ public void verifySigningCertificate(CertPath signingCertificate) } /** - * Verify a signing certificate, applying the given certificate-transparency policy. When SCT - * verification is disabled (ex: a private deployment without CT logs) the SCT check is skipped. + * Verify a signing certificate, applying the given certificate-transparency policy (see {@link + * #verifySct(CertPath, CTLogOptions)}). */ public void verifySigningCertificate(CertPath signingCertificate, CTLogOptions ctLogOptions) throws FulcioVerificationException, IOException { CertPath fullCertPath = validateCertPath(signingCertificate); - if (!ctLogOptions.isEnabled()) { - return; - } - verifySct(fullCertPath); + verifySct(fullCertPath, ctLogOptions); } public CertPath trimTrustedParent(CertPath signingCertificate) From 400f6769f0f62adbcf844b82e8f889241108925a Mon Sep 17 00:00:00 2001 From: Anthony Dahanne Date: Wed, 29 Jul 2026 16:58:02 -0400 Subject: [PATCH 5/5] PR #1252 review suggestions fixed * no need for zipped file * remve un needed method Signed-off-by: Anthony Dahanne --- .../fulcio/client/FulcioVerifier.java | 5 -- .../dev/sigstore/KeylessVerifierTest.java | 47 ++++------------ .../fulcio/client/FulcioVerifierTest.java | 12 ++++- .../bundles/bundle-github-private-no-tlog.zip | Bin 11488 -> 0 bytes .../bundles/github-private-no-tlog/README.md | 51 ++++++++++++++++++ .../attestation.sigstore.json | 1 + .../github-private-no-tlog/trusted_root.jsonl | 2 + 7 files changed, 74 insertions(+), 44 deletions(-) delete mode 100644 sigstore-java/src/test/resources/dev/sigstore/samples/bundles/bundle-github-private-no-tlog.zip create mode 100644 sigstore-java/src/test/resources/dev/sigstore/samples/bundles/github-private-no-tlog/README.md create mode 100644 sigstore-java/src/test/resources/dev/sigstore/samples/bundles/github-private-no-tlog/attestation.sigstore.json create mode 100644 sigstore-java/src/test/resources/dev/sigstore/samples/bundles/github-private-no-tlog/trusted_root.jsonl diff --git a/sigstore-java/src/main/java/dev/sigstore/fulcio/client/FulcioVerifier.java b/sigstore-java/src/main/java/dev/sigstore/fulcio/client/FulcioVerifier.java index 4887e4702..aa0946dbb 100644 --- a/sigstore-java/src/main/java/dev/sigstore/fulcio/client/FulcioVerifier.java +++ b/sigstore-java/src/main/java/dev/sigstore/fulcio/client/FulcioVerifier.java @@ -94,11 +94,6 @@ private FulcioVerifier( this.ctVerifier = ctVerifier; } - @VisibleForTesting - void verifySct(CertPath fullCertPath) throws FulcioVerificationException { - verifySct(fullCertPath, CTLogOptions.builder().isEnabled(true).build()); - } - @VisibleForTesting void verifySct(CertPath fullCertPath, CTLogOptions ctLogOptions) throws FulcioVerificationException { diff --git a/sigstore-java/src/test/java/dev/sigstore/KeylessVerifierTest.java b/sigstore-java/src/test/java/dev/sigstore/KeylessVerifierTest.java index fb85c0f0c..075610582 100644 --- a/sigstore-java/src/test/java/dev/sigstore/KeylessVerifierTest.java +++ b/sigstore-java/src/test/java/dev/sigstore/KeylessVerifierTest.java @@ -37,8 +37,6 @@ import java.security.cert.X509Certificate; import java.util.List; import java.util.stream.Stream; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; import org.hamcrest.CoreMatchers; import org.hamcrest.MatcherAssert; import org.junit.jupiter.api.Assertions; @@ -421,9 +419,7 @@ public void verifyCertificateMatches_noneMatch() throws Exception { .issuer(StringMatcher.string("not-match-again")) .build()))); Assertions.assertEquals( - "No provided certificate identities matched values in certificate: [{issuer:'String:" - + " not-match',san:'String: not-match'},{issuer:'String: not-match-again',san:'String:" - + " not-match-again'}]", + "No provided certificate identities matched values in certificate: [{issuer:'String: not-match',san:'String: not-match'},{issuer:'String: not-match-again',san:'String: not-match-again'}]", ex.getMessage()); } @@ -887,22 +883,11 @@ public void testVerify_wrongArtifactHashAlgorithm() throws Exception { "Provided artifact digest does not match digest used for verification")); } - // The fixture below is a real SLSA build-provenance attestation produced by GitHub Actions for a - // *private* repository. Attestations for private repositories are signed by GitHub's own Sigstore - // instance, which (unlike the public-good instance) does not publish to a transparency log and - // does not use certificate transparency: the bundle carries zero tlog entries and relies on a - // signed RFC 3161 timestamp for trusted time, and the trust root that GitHub distributes for it - // (via `gh attestation trusted-root`) contains zero CT logs. Verifying such a bundle therefore - // requires a policy that disables both transparency-log and certificate-transparency checks. - // - // The zip file contains two files: - // - attestation.sigstore.json : the sigstore bundle (DSSE, in-toto SLSA provenance predicate) - // - trusted_root.jsonl : the trust roots as JSON Lines. `gh attestation trusted-root` - // emits more than one (public-good and GitHub); sigstore-java - // consumes a single trust root, so the tests below select the - // GitHub one (the line describing the fulcio.githubapp.com CA). - private static final String GH_PRIVATE_ATTESTATION_ZIP = - "dev/sigstore/samples/bundles/bundle-github-private-no-tlog.zip"; + // A real SLSA build-provenance attestation from a GitHub *private* repository: no + // transparency-log entry, no CT log, signed RFC 3161 timestamp only. See the README in the + // fixture directory for provenance and how it was obtained. + private static final String GH_PRIVATE_DIR = + "dev/sigstore/samples/bundles/github-private-no-tlog/"; private static final String GH_PRIVATE_SIGNER_SAN = "https://github.com/neverendingsupport/slsa-attestations/.github/workflows/attest.yml@9f6d9dc1bfc02986955721eb15f89ad618f1cedb"; private static final String GH_PRIVATE_OIDC_ISSUER = @@ -980,15 +965,15 @@ private static ImmutableVerificationOptions.Builder gitHubPrivateOptions() { private static Bundle gitHubPrivateBundle() throws Exception { return Bundle.from( new StringReader( - new String( - readZipEntry(GH_PRIVATE_ATTESTATION_ZIP, "attestation.sigstore.json"), + Resources.toString( + Resources.getResource(GH_PRIVATE_DIR + "attestation.sigstore.json"), StandardCharsets.UTF_8))); } private static KeylessVerifier gitHubPrivateVerifier(Path tempDir) throws Exception { var jsonl = - new String( - readZipEntry(GH_PRIVATE_ATTESTATION_ZIP, "trusted_root.jsonl"), StandardCharsets.UTF_8); + Resources.toString( + Resources.getResource(GH_PRIVATE_DIR + "trusted_root.jsonl"), StandardCharsets.UTF_8); var gitHubTrustedRoot = jsonl .lines() @@ -1003,16 +988,4 @@ private static KeylessVerifier gitHubPrivateVerifier(Path tempDir) throws Except .trustedRootProvider(TrustedRootProvider.from(trustedRootPath)) .build(); } - - private static byte[] readZipEntry(String zipResource, String entryName) throws Exception { - try (var zis = new ZipInputStream(Resources.getResource(zipResource).openStream())) { - ZipEntry entry; - while ((entry = zis.getNextEntry()) != null) { - if (entry.getName().equals(entryName)) { - return zis.readAllBytes(); - } - } - } - throw new IllegalStateException("entry '" + entryName + "' not found in " + zipResource); - } } diff --git a/sigstore-java/src/test/java/dev/sigstore/fulcio/client/FulcioVerifierTest.java b/sigstore-java/src/test/java/dev/sigstore/fulcio/client/FulcioVerifierTest.java index 52deedb40..ffbb0db10 100644 --- a/sigstore-java/src/test/java/dev/sigstore/fulcio/client/FulcioVerifierTest.java +++ b/sigstore-java/src/test/java/dev/sigstore/fulcio/client/FulcioVerifierTest.java @@ -16,6 +16,8 @@ package dev.sigstore.fulcio.client; import com.google.common.io.Resources; +import dev.sigstore.ImmutableCTLogOptions; +import dev.sigstore.VerificationOptions.CTLogOptions; import dev.sigstore.bundle.Bundle; import dev.sigstore.encryption.certificates.Certificates; import dev.sigstore.trustroot.ImmutableLogId; @@ -26,6 +28,7 @@ import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.List; +import org.jspecify.annotations.NonNull; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -81,7 +84,7 @@ public void testVerifySct_noSct() throws Exception { var fulcioVerifier = FulcioVerifier.newFulcioVerifier(trustRoot); try { - fulcioVerifier.verifySct(signingCertificate); + fulcioVerifier.verifySct(signingCertificate, enabledCTLog()); Assertions.fail(); } catch (FulcioVerificationException fve) { Assertions.assertEquals("No valid SCTs were found during verification", fve.getMessage()); @@ -122,7 +125,12 @@ public void invalidEmbeddedSct() throws Exception { var fve = Assertions.assertThrows( - FulcioVerificationException.class, () -> fulcioVerifier.verifySct(signingCertificate)); + FulcioVerificationException.class, + () -> fulcioVerifier.verifySct(signingCertificate, enabledCTLog())); Assertions.assertEquals("No valid SCTs were found, all(1) SCTs were invalid", fve.getMessage()); } + + private static @NonNull ImmutableCTLogOptions enabledCTLog() { + return CTLogOptions.builder().isEnabled(true).build(); + } } diff --git a/sigstore-java/src/test/resources/dev/sigstore/samples/bundles/bundle-github-private-no-tlog.zip b/sigstore-java/src/test/resources/dev/sigstore/samples/bundles/bundle-github-private-no-tlog.zip deleted file mode 100644 index 0ef54ee3d85d1455565478a1d41f2ad502de2428..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11488 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 diff --git a/sigstore-java/src/test/resources/dev/sigstore/samples/bundles/github-private-no-tlog/README.md b/sigstore-java/src/test/resources/dev/sigstore/samples/bundles/github-private-no-tlog/README.md new file mode 100644 index 000000000..e1cbe34c1 --- /dev/null +++ b/sigstore-java/src/test/resources/dev/sigstore/samples/bundles/github-private-no-tlog/README.md @@ -0,0 +1,51 @@ +# GitHub private-repo attestation (no transparency log) + +`attestation.sigstore.json` and `trusted_root.jsonl` are a real SLSA build-provenance +attestation produced by GitHub Actions for a **private** repository. + +Attestations for private repositories are signed by GitHub's own Sigstore instance, +which — unlike the public-good instance — does **not** publish to a transparency log and +does **not** use certificate transparency: + +- the bundle carries **zero** transparency-log entries and relies on a signed RFC 3161 + timestamp for trusted time; +- the trust root GitHub distributes for it contains **zero** CT logs. + +Verifying such a bundle therefore requires a policy that disables both transparency-log +and certificate-transparency checks (`VerificationOptions` with `TLogOptions` / +`CTLogOptions` `isEnabled(false)`). See `KeylessVerifierTest`. + +## Files + +- `attestation.sigstore.json` — the Sigstore bundle (DSSE envelope, in-toto SLSA + provenance predicate). +- `trusted_root.jsonl` — trust roots as JSON Lines. `gh attestation trusted-root` emits + more than one (public-good first, then GitHub). sigstore-java consumes a single trust + root, so the test selects the GitHub one (the line describing the `fulcio.githubapp.com` + CA). + +## How this fixture was obtained + +Both files are the unmodified output of the `gh` CLI, run against a **private** +repository whose build had already produced a SLSA build-provenance attestation for a +released artifact. + +Download the attestation bundle for the built artifact, scoping the lookup to its private +repository: + +```sh +gh attestation download --repo / +``` + +This writes a `sha256-.jsonl` file in the current directory containing the +Sigstore bundle; it is copied verbatim to `attestation.sigstore.json`. + +Fetch the matching trust roots (public-good instance first, then GitHub's instance): + +```sh +gh attestation trusted-root > trusted_root.jsonl +``` + +Any private repository with a build-provenance attestation reproduces an equivalent +fixture: because the repository is private, GitHub signs it with its own Sigstore +instance, so the resulting bundle carries no transparency-log entries. diff --git a/sigstore-java/src/test/resources/dev/sigstore/samples/bundles/github-private-no-tlog/attestation.sigstore.json b/sigstore-java/src/test/resources/dev/sigstore/samples/bundles/github-private-no-tlog/attestation.sigstore.json new file mode 100644 index 000000000..a4246f5f6 --- /dev/null +++ b/sigstore-java/src/test/resources/dev/sigstore/samples/bundles/github-private-no-tlog/attestation.sigstore.json @@ -0,0 +1 @@ +{"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json","verificationMaterial":{"certificate":{"rawBytes":"MIIHQDCCBsagAwIBAgIUa39WPiLHvriy0BbZ+r6W6E9QoX8wCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwyMB4XDTI2MDcyNDE1MzAyN1oXDTI2MDcyNDE1NDAyN1owADBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCzR6pDNd61vOrLV+lssLkX83Dw/zNGKEprrEfInxjWcASil70eYKlWm5UdtMpb9rYKMGU2s2mlx56bQZzuJsl2jggXkMIIF4DAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHQYDVR0OBBYEFA5tlV/Yh7mAI5usmY0iLk9s+ErMMB8GA1UdIwQYMBaAFIHjTsG/Xu4ILqbvj2S+p69FoJvPMIGMBgNVHREBAf8EgYEwf4Z9aHR0cHM6Ly9naXRodWIuY29tL25ldmVyZW5kaW5nc3VwcG9ydC9zbHNhLWF0dGVzdGF0aW9ucy8uZ2l0aHViL3dvcmtmbG93cy9hdHRlc3QueW1sQDlmNmQ5ZGMxYmZjMDI5ODY5NTU3MjFlYjE1Zjg5YWQ2MThmMWNlZGIwOQYKKwYBBAGDvzABAQQraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50LmNvbTASBgorBgEEAYO/MAECBARwdXNoMDYGCisGAQQBg78wAQMEKGNhMzQyZTU2Nzg3OTg2Y2Q0ZThkZmMwYzg4MzA2OTY3MTZkM2I0NDEwFQYKKwYBBAGDvzABBAQHUmVsZWFzZTA0BgorBgEEAYO/MAEFBCZuZXZlcmVuZGluZ3N1cHBvcnQvbmVzLWphdmEtdGVzdC1saWItYTAjBgorBgEEAYO/MAEGBBVyZWZzL3RhZ3MvbmVzLXYxLjAuMTIwOwYKKwYBBAGDvzABCAQtDCtodHRwczovL3Rva2VuLmFjdGlvbnMuZ2l0aHVidXNlcmNvbnRlbnQuY29tMIGNBgorBgEEAYO/MAEJBH8MfWh0dHBzOi8vZ2l0aHViLmNvbS9uZXZlcmVuZGluZ3N1cHBvcnQvc2xzYS1hdHRlc3RhdGlvbnMvLmdpdGh1Yi93b3JrZmxvd3MvYXR0ZXN0LnltbEA5ZjZkOWRjMWJmYzAyOTg2OTU1NzIxZWIxNWY4OWFkNjE4ZjFjZWRiMDgGCisGAQQBg78wAQoEKgwoOWY2ZDlkYzFiZmMwMjk4Njk1NTcyMWViMTVmODlhZDYxOGYxY2VkYjAdBgorBgEEAYO/MAELBA8MDWdpdGh1Yi1ob3N0ZWQwSQYKKwYBBAGDvzABDAQ7DDlodHRwczovL2dpdGh1Yi5jb20vbmV2ZXJlbmRpbmdzdXBwb3J0L25lcy1qYXZhLXRlc3QtbGliLWEwOAYKKwYBBAGDvzABDQQqDChjYTM0MmU1Njc4Nzk4NmNkNGU4ZGZjMGM4ODMwNjk2NzE2ZDNiNDQxMCUGCisGAQQBg78wAQ4EFwwVcmVmcy90YWdzL25lcy12MS4wLjEyMBoGCisGAQQBg78wAQ8EDAwKMTI5NjQyODQxNzA1BgorBgEEAYO/MAEQBCcMJWh0dHBzOi8vZ2l0aHViLmNvbS9uZXZlcmVuZGluZ3N1cHBvcnQwGQYKKwYBBAGDvzABEQQLDAkxMTMzOTQzOTYwgYEGCisGAQQBg78wARIEcwxxaHR0cHM6Ly9naXRodWIuY29tL25ldmVyZW5kaW5nc3VwcG9ydC9uZXMtamF2YS10ZXN0LWxpYi1hLy5naXRodWIvd29ya2Zsb3dzL25lcy1yZWxlYXNlLnltbEByZWZzL3RhZ3MvbmVzLXYxLjAuMTIwOAYKKwYBBAGDvzABEwQqDChjYTM0MmU1Njc4Nzk4NmNkNGU4ZGZjMGM4ODMwNjk2NzE2ZDNiNDQxMBQGCisGAQQBg78wARQEBgwEcHVzaDBtBgorBgEEAYO/MAEVBF8MXWh0dHBzOi8vZ2l0aHViLmNvbS9uZXZlcmVuZGluZ3N1cHBvcnQvbmVzLWphdmEtdGVzdC1saWItYS9hY3Rpb25zL3J1bnMvMzAwNjY2OTg5NzUvYXR0ZW1wdHMvMzAXBgorBgEEAYO/MAEWBAkMB3ByaXZhdGUwVQYKKwYBBAGDvzABGARHDEVyZXBvOm5ldmVyZW5kaW5nc3VwcG9ydC9uZXMtamF2YS10ZXN0LWxpYi1hOnJlZjpyZWZzL3RhZ3MvbmVzLXYxLjAuMTIwCgYIKoZIzj0EAwMDaAAwZQIxAJ6aTFDtBXArjgKmtzRRnqGgoPgGG2/0O38NJQtadgIiZebfw188PcS9TeHVUncQgQIwQEyyaxjimHq5q6BWFQeyNholRT4XJ6qyTcyogoy1oIDF8LmH6KfQoQHGUynIcMF/"},"timestampVerificationData":{"rfc3161Timestamps":[{"signedTimestamp":"MIIC0TADAgEAMIICyAYJKoZIhvcNAQcCoIICuTCCArUCAQMxDTALBglghkgBZQMEAgIwgbsGCyqGSIb3DQEJEAEEoIGrBIGoMIGlAgEBBgkrBgEEAYO/MAIwMTANBglghkgBZQMEAgEFAAQg9wRSwDiSoN/9BVtwwSPSLO0VhDm7CDR9dC2EqM3UH1oCFFcFv2bliQEgqQF2Vgm7oJaQMxN1GA8yMDI2MDcyNDE1MzAyN1owAwIBAaA2pDQwMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgVGltZXN0YW1waW5noAAxggHfMIIB2wIBATBKMDIxFTATBgNVBAoTDEdpdEh1YiwgSW5jLjEZMBcGA1UEAxMQVFNBIGludGVybWVkaWF0ZQIUFzPb1un+DvofqFPzAXiB5slHM1IwCwYJYIZIAWUDBAICoIIBBTAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwHAYJKoZIhvcNAQkFMQ8XDTI2MDcyNDE1MzAyN1owPwYJKoZIhvcNAQkEMTIEML0bFLAXQTUhJhhV64ysYiaE+dW8+W9to5DmKcWO+rvX76ZmjMmuwm3OJBi9YKlQGzCBhwYLKoZIhvcNAQkQAi8xeDB2MHQwcgQgRBbIrnMUamOhN8LlW+jecOPJrzBzvzmLqrqo2wgXztcwTjA2pDQwMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlAhQXM9vW6f4O+h+oU/MBeIHmyUczUjAKBggqhkjOPQQDAwRoMGYCMQCwdP5T8aUvaO0OUpW5m4gTHVjKe9XfCm0nU+xzBFRuuCclWgGdHVfgL1uEF/7TzoYCMQCr8X65t64LlX6sMFfsmeskPlxglNomGSgiEVTBpiJk91WTgGdM717fc2+u8C83Dko="}]}},"dsseEnvelope":{"payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoiYS1tb2R1bGUtMS0xLjAuMC1uZXMtamF2YS10ZXN0LWxpYi1hLTEuMC4xMi1zb3VyY2VzLmphciIsImRpZ2VzdCI6eyJzaGEyNTYiOiIxMzgyODBmZDMzMDJiOTcwNjU5NjYyMTc2MTk0NWExYWU5NThmOTNhMTVlNDM3MmRkYWU0ZmMwMGE5M2IwOWEyIn19LHsibmFtZSI6ImEtbW9kdWxlLTEtMS4wLjAtbmVzLWphdmEtdGVzdC1saWItYS0xLjAuMTIuamFyIiwiZGlnZXN0Ijp7InNoYTI1NiI6ImNhYTAxNWVmNjllOWJjMzFhNDEzMjJkNmM3MTU2M2VkOTYwMGM3NWJiOTg4ZTRkNjM5YjFlZGM1Nzg1ODA1NTEifX0seyJuYW1lIjoiYS1tb2R1bGUtMS0xLjAuMC1uZXMtamF2YS10ZXN0LWxpYi1hLTEuMC4xMi5wb20iLCJkaWdlc3QiOnsic2hhMjU2IjoiYWZlOGIxMzZlY2VhZjRkNzgxYWM4MDYwNGRiZTc0OGVlNDZhNDA3NDYyZjNjM2EzOTUyYjFhOGM2MzA3ZGQ0NSJ9fSx7Im5hbWUiOiJhLW1vZHVsZS0yLTEuMC4wLW5lcy1qYXZhLXRlc3QtbGliLWEtMS4wLjEyLXNvdXJjZXMuamFyIiwiZGlnZXN0Ijp7InNoYTI1NiI6Ijk4NzBmZWZlYjE5OWU2ZWVhN2VlNWQ2Yjc5NjNiODg5NzEyZDAzNTk0Y2I4YmYxZDg2NjE1OTczYmIzMWM5YTcifX0seyJuYW1lIjoiYS1tb2R1bGUtMi0xLjAuMC1uZXMtamF2YS10ZXN0LWxpYi1hLTEuMC4xMi5qYXIiLCJkaWdlc3QiOnsic2hhMjU2IjoiMjY3ZmQ1NzUzMjExYzM2MjVhMDA3NWVlODFlMzM5MmI0OWQxNmJkMjNmNTk1OTIyYzQwNDI0OWQ3MWQwMzllMCJ9fSx7Im5hbWUiOiJhLW1vZHVsZS0yLTEuMC4wLW5lcy1qYXZhLXRlc3QtbGliLWEtMS4wLjEyLnBvbSIsImRpZ2VzdCI6eyJzaGEyNTYiOiI0NmQwN2NkOTA2NGI4NzFlMTY2ZjYwMDQ3MmMwMDc1YzEwMzMyNTc5NjlkYjIwODdmODNiYzFiNTRjZGM4NzMzIn19LHsibmFtZSI6ImEtbW9kdWxlLTMtMS4wLjAtbmVzLWphdmEtdGVzdC1saWItYS0xLjAuMTItc291cmNlcy5qYXIiLCJkaWdlc3QiOnsic2hhMjU2IjoiMGQyYzEwNjEwN2ZjM2ViOTdjZTZlMjFjNWVkMTkxMmJmZTY3NzRlNGNkNTc4Yjc0NGFmMzQzNTYxYjVkNTk3MSJ9fSx7Im5hbWUiOiJhLW1vZHVsZS0zLTEuMC4wLW5lcy1qYXZhLXRlc3QtbGliLWEtMS4wLjEyLmphciIsImRpZ2VzdCI6eyJzaGEyNTYiOiI0N2I2ZTA0YTFkYzQ5YjBiMzUzYmNiODA2NjQxMWQwMDE0MWViYWVkMGZhMjA4NmE2NTJjOTUzNDJhOGJiMTRhIn19LHsibmFtZSI6ImEtbW9kdWxlLTMtMS4wLjAtbmVzLWphdmEtdGVzdC1saWItYS0xLjAuMTIucG9tIiwiZGlnZXN0Ijp7InNoYTI1NiI6IjQ4NGNmMzE4ODYwYmUwODFjZDVlNGM4YzkwMmRkNGRmYjlhMWViN2NjZDBhZTliMzQ0MzFlZjEwMGUxNzIxZWIifX0seyJuYW1lIjoibmVzLWphdmEtdGVzdC1saWItYS0xLjAuMC1uZXMtamF2YS10ZXN0LWxpYi1hLTEuMC4xMi5wb20iLCJkaWdlc3QiOnsic2hhMjU2IjoiNWI4NTExNDExNTFkYmM5MzVlMzMyZDQ0ZTMwNTcxOTJkODc5YmNiYWQ3ZTA1Zjc5MjZkMzVlMzdjOTVmOTg0NiJ9fV0sInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjEiLCJwcmVkaWNhdGUiOnsiYnVpbGREZWZpbml0aW9uIjp7ImJ1aWxkVHlwZSI6Imh0dHBzOi8vYWN0aW9ucy5naXRodWIuaW8vYnVpbGR0eXBlcy93b3JrZmxvdy92MSIsImV4dGVybmFsUGFyYW1ldGVycyI6eyJ3b3JrZmxvdyI6eyJyZWYiOiJyZWZzL3RhZ3MvbmVzLXYxLjAuMTIiLCJyZXBvc2l0b3J5IjoiaHR0cHM6Ly9naXRodWIuY29tL25ldmVyZW5kaW5nc3VwcG9ydC9uZXMtamF2YS10ZXN0LWxpYi1hIiwicGF0aCI6Ii5naXRodWIvd29ya2Zsb3dzL25lcy1yZWxlYXNlLnltbCJ9fSwiaW50ZXJuYWxQYXJhbWV0ZXJzIjp7ImdpdGh1YiI6eyJldmVudF9uYW1lIjoicHVzaCIsInJlcG9zaXRvcnlfaWQiOiIxMjk2NDI4NDE3IiwicmVwb3NpdG9yeV9vd25lcl9pZCI6IjExMzM5NDM5NiIsInJ1bm5lcl9lbnZpcm9ubWVudCI6ImdpdGh1Yi1ob3N0ZWQifX0sInJlc29sdmVkRGVwZW5kZW5jaWVzIjpbeyJ1cmkiOiJnaXQraHR0cHM6Ly9naXRodWIuY29tL25ldmVyZW5kaW5nc3VwcG9ydC9uZXMtamF2YS10ZXN0LWxpYi1hQHJlZnMvdGFncy9uZXMtdjEuMC4xMiIsImRpZ2VzdCI6eyJnaXRDb21taXQiOiJjYTM0MmU1Njc4Nzk4NmNkNGU4ZGZjMGM4ODMwNjk2NzE2ZDNiNDQxIn19XX0sInJ1bkRldGFpbHMiOnsiYnVpbGRlciI6eyJpZCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9uZXZlcmVuZGluZ3N1cHBvcnQvc2xzYS1hdHRlc3RhdGlvbnMvLmdpdGh1Yi93b3JrZmxvd3MvYXR0ZXN0LnltbEA5ZjZkOWRjMWJmYzAyOTg2OTU1NzIxZWIxNWY4OWFkNjE4ZjFjZWRiIn0sIm1ldGFkYXRhIjp7Imludm9jYXRpb25JZCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9uZXZlcmVuZGluZ3N1cHBvcnQvbmVzLWphdmEtdGVzdC1saWItYS9hY3Rpb25zL3J1bnMvMzAwNjY2OTg5NzUvYXR0ZW1wdHMvMyJ9fX19","payloadType":"application/vnd.in-toto+json","signatures":[{"sig":"MEYCIQCdMQAky8sN1kNzf0XmENcKFvUbANBpcUL1Dnx+0nV7NwIhAOz95buyD0Px3atfL85VsJTYehZQAsRC/8HLvTEdAhlL"}]}} diff --git a/sigstore-java/src/test/resources/dev/sigstore/samples/bundles/github-private-no-tlog/trusted_root.jsonl b/sigstore-java/src/test/resources/dev/sigstore/samples/bundles/github-private-no-tlog/trusted_root.jsonl new file mode 100644 index 000000000..d80ae4489 --- /dev/null +++ b/sigstore-java/src/test/resources/dev/sigstore/samples/bundles/github-private-no-tlog/trusted_root.jsonl @@ -0,0 +1,2 @@ +{"mediaType":"application/vnd.dev.sigstore.trustedroot+json;version=0.1","tlogs":[{"baseUrl":"https://rekor.sigstore.dev","hashAlgorithm":"SHA2_256","publicKey":{"rawBytes":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2G2Y+2tabdTV5BcGiBIx0a9fAFwrkBbmLSGtks4L3qX6yYY0zufBnhC8Ur/iy55GhWP/9A/bY2LhC30M9+RYtw==","keyDetails":"PKIX_ECDSA_P256_SHA_256","validFor":{"start":"2021-01-12T11:53:27Z"}},"logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="}},{"baseUrl":"https://log2025-1.rekor.sigstore.dev","hashAlgorithm":"SHA2_256","publicKey":{"rawBytes":"MCowBQYDK2VwAyEAt8rlp1knGwjfbcXAYPYAkn0XiLz1x8O4t0YkEhie244=","keyDetails":"PKIX_ED25519","validFor":{"start":"2025-09-23T00:00:00Z"}},"logId":{"keyId":"zxGZFVvd0FEmjR8WrFwMdcAJ9vtaY/QXf44Y1wUeP6A="}}],"certificateAuthorities":[{"subject":{"organization":"sigstore.dev","commonName":"sigstore"},"uri":"https://fulcio.sigstore.dev","certChain":{"certificates":[{"rawBytes":"MIIB+DCCAX6gAwIBAgITNVkDZoCiofPDsy7dfm6geLbuhzAKBggqhkjOPQQDAzAqMRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIxMDMwNzAzMjAyOVoXDTMxMDIyMzAzMjAyOVowKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLSyA7Ii5k+pNO8ZEWY0ylemWDowOkNa3kL+GZE5Z5GWehL9/A9bRNA3RbrsZ5i0JcastaRL7Sp5fp/jD5dxqc/UdTVnlvS16an+2Yfswe/QuLolRUCrcOE2+2iA5+tzd6NmMGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFMjFHQBBmiQpMlEk6w2uSu1KBtPsMB8GA1UdIwQYMBaAFMjFHQBBmiQpMlEk6w2uSu1KBtPsMAoGCCqGSM49BAMDA2gAMGUCMH8liWJfMui6vXXBhjDgY4MwslmN/TJxVe/83WrFomwmNf056y1X48F9c4m3a3ozXAIxAKjRay5/aj/jsKKGIkmQatjI8uupHr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ=="}]},"validFor":{"start":"2021-03-07T03:20:29Z","end":"2022-12-31T23:59:59.999Z"}},{"subject":{"organization":"sigstore.dev","commonName":"sigstore"},"uri":"https://fulcio.sigstore.dev","certChain":{"certificates":[{"rawBytes":"MIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV77LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjpKFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZIzj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJRnZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsPmygUY7Ii2zbdCdliiow="},{"rawBytes":"MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxexX69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92jYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRYwB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQKsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCMWP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ"}]},"validFor":{"start":"2022-04-13T20:06:15Z"}}],"ctlogs":[{"baseUrl":"https://ctfe.sigstore.dev/test","hashAlgorithm":"SHA2_256","publicKey":{"rawBytes":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbfwR+RJudXscgRBRpKX1XFDy3PyudDxz/SfnRi1fT8ekpfBd2O1uoz7jr3Z8nKzxA69EUQ+eFCFI3zeubPWU7w==","keyDetails":"PKIX_ECDSA_P256_SHA_256","validFor":{"start":"2021-03-14T00:00:00Z","end":"2022-10-31T23:59:59.999Z"}},"logId":{"keyId":"CGCS8ChS/2hF0dFrJ4ScRWcYrBY9wzjSbea8IgY2b3I="}},{"baseUrl":"https://ctfe.sigstore.dev/2022","hashAlgorithm":"SHA2_256","publicKey":{"rawBytes":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEiPSlFi0CmFTfEjCUqF9HuCEcYXNKAaYalIJmBZ8yyezPjTqhxrKBpMnaocVtLJBI1eM3uXnQzQGAJdJ4gs9Fyw==","keyDetails":"PKIX_ECDSA_P256_SHA_256","validFor":{"start":"2022-10-20T00:00:00Z"}},"logId":{"keyId":"3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4="}}],"timestampAuthorities":[{"subject":{"organization":"sigstore.dev","commonName":"sigstore-tsa-selfsigned"},"uri":"https://timestamp.sigstore.dev/api/v1/timestamp","certChain":{"certificates":[{"rawBytes":"MIICEDCCAZagAwIBAgIUOhNULwyQYe68wUMvy4qOiyojiwwwCgYIKoZIzj0EAwMwOTEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MSAwHgYDVQQDExdzaWdzdG9yZS10c2Etc2VsZnNpZ25lZDAeFw0yNTA0MDgwNjU5NDNaFw0zNTA0MDYwNjU5NDNaMC4xFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEVMBMGA1UEAxMMc2lnc3RvcmUtdHNhMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE4ra2Z8hKNig2T9kFjCAToGG30jky+WQv3BzL+mKvh1SKNR/UwuwsfNCg4sryoYAd8E6isovVA3M4aoNdm9QDi50Z8nTEyvqgfDPtTIwXItfiW/AFf1V7uwkbkAoj0xxco2owaDAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYEFIn9eUOHz9BlRsMCRscsc1t9tOsDMB8GA1UdIwQYMBaAFJjsAe9/u1H/1JUeb4qImFMHic6/MBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMAoGCCqGSM49BAMDA2gAMGUCMDtpsV/6KaO0qyF/UMsX2aSUXKQFdoGTptQGc0ftq1csulHPGG6dsmyMNd3JB+G3EQIxAOajvBcjpJmKb4Nv+2Taoj8Uc5+b6ih6FXCCKraSqupe07zqswMcXJTe1cExvHvvlw=="},{"rawBytes":"MIIB9zCCAXygAwIBAgIUV7f0GLDOoEzIh8LXSW80OJiUp14wCgYIKoZIzj0EAwMwOTEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MSAwHgYDVQQDExdzaWdzdG9yZS10c2Etc2VsZnNpZ25lZDAeFw0yNTA0MDgwNjU5NDNaFw0zNTA0MDYwNjU5NDNaMDkxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEgMB4GA1UEAxMXc2lnc3RvcmUtdHNhLXNlbGZzaWduZWQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQUQNtfRT/ou3YATa6wB/kKTe70cfJwyRIBovMnt8RcJph/COE82uyS6FmppLLL1VBPGcPfpQPYJNXzWwi8icwhKQ6W/Qe2h3oebBb2FHpwNJDqo+TMaC/tdfkv/ElJB72jRTBDMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBSY7AHvf7tR/9SVHm+KiJhTB4nOvzAKBggqhkjOPQQDAwNpADBmAjEAwGEGrfGZR1cen1R8/DTVMI943LssZmJRtDp/i7SfGHmGRP6gRbuj9vOK3b67Z0QQAjEAuT2H673LQEaHTcyQSZrkp4mX7WwkmF+sVbkYY5mXN+RMH13KUEHHOqASaemYWK/E"}]},"validFor":{"start":"2025-07-04T00:00:00Z"}}]} +{"mediaType":"application/vnd.dev.sigstore.trustedroot+json;version=0.1","certificateAuthorities":[{"subject":{"organization":"GitHub, Inc.","commonName":"Internal Services Root"},"uri":"fulcio.githubapp.com","certChain":{"certificates":[{"rawBytes":"MIICKjCCAbCgAwIBAgIUW3TJVeOvr+NSvJXdOw8nEEn7HhQwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwxMB4XDTIzMDkxMjE0MDY1NFoXDTI0MDkxMTE0MDY1NFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEsosodObhuHG6Pr5vp5y+pmnKawS1h2hwv3r3hBwqh3ZHJAw64mhDnDs9fw4jKkZEBYRSVyOHyZppz4day8hgpTIDwdj44Oan4RDb+wmj04jfhVLjLsQ4Q/X4K/ynRgNXo3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUm0vkDkQZ29hutYdayJobIRmf/iMwHwYDVR0jBBgwFoAUwOG4UqRLTz7eejgRBs9JjqFFmzMwCgYIKoZIzj0EAwMDaAAwZQIwIBl93E7vkWTvdeIm1WSIM4qNsj0ApE8LCj3k1vrY5x6/7yhAZs7QlO3/FBCoEeaZAjEAlJcNr37uZq9BYHODHBeO/gP+6EfbzsNaLV22ASBlhF/a9y83ESLuqCNN7IxGxmWT"},{"rawBytes":"MIICFTCCAZugAwIBAgIUD3Jlqt4qhrcZI4UnGfPGrEq/pjQwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDkxMTEyMDAwMFoXDTI4MDkwOTEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE7X7nK0wC7uEmDjW+on0sXIX3FacL3hhcrhneA+M/kl1OtvQiPmFrH9lbUQqOj/AfspJ8uGY3jaq8WuSg6ghatzYfuuzLAJIK4nGpCBafncF8EynOssPq64/Dz+JUWXqlo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUwOG4UqRLTz7eejgRBs9JjqFFmzMwHwYDVR0jBBgwFoAUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaAAwZQIxAI8HWLrke7uzhOpwlD1cNixPmoX9XFKe7bEPozo0D+vKi0Gt6VlC7xPedFIw4/AypAIwQP+FGRWvfx0IAH5/n0aRiN7/LVpyFA5RkJASZOVOib2Y8pNuhXa9V3ZbWO6v6kW/"},{"rawBytes":"MIIB9TCCAXqgAwIBAgIUNFryA06EHDIcd5EIbe8swbl9OY4wCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTMzMDgwNDEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEXYaXx4H0oNuVP/2cfydA3oaafvvkkkgb5hbL8/j/BO25S7uTmDOCA5e4QLLWCKFuc+xp2j14tCH4WmHzMUDvf2tXtInVliY5wZgQMM9L6klo/IwA9x4omdcjnT+kKJAjo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaQAwZgIxAPzXsV+eokrqOHSQZH/XhhHE1slOscKy3DQpYpYJ1AWmJ2lJu/XOmubBX5s7apllUwIxALw2Ts8CDACiK42UymC8fk6sbNfoXUAWqdyKTVt2Lst+wNdkRniGvx7jT65BKTkcsQ=="}]},"validFor":{"start":"2023-10-27T16:30:00Z","end":"2024-05-25T00:00:00Z"}},{"subject":{"organization":"GitHub, Inc.","commonName":"Internal Services Root"},"uri":"fulcio.githubapp.com","certChain":{"certificates":[{"rawBytes":"MIICKzCCAbCgAwIBAgIUOpyw2HaZefsj/4SPXutGof8E2CkwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwxMB4XDTI0MDUxMzAwMDAwMFoXDTI1MDUxMzAwMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEJNJWvW8fckfk/oQmh+qCeIlFXl9YLEkKSjZCgcVB92Fi1HQnvmpCiyqpvP91SmT1/G6QbrmTGV7MmIQlDnBWHNUT+jwZ3elGu/yfr/v8U0uhZTIli/BMj5Y4ICHK/j4do3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUD0fF3cs+ldPyiWohHJ3JmO91V7gwHwYDVR0jBBgwFoAUwOG4UqRLTz7eejgRBs9JjqFFmzMwCgYIKoZIzj0EAwMDaQAwZgIxAO7BRC9i7oGUHjjlcHU/bfqk2NLy7t6wm3K5W+jBLFbAj6sVjYcY+rrYhop/OjclbQIxALafBKLPIPjoCI29BUHwLBFP6e92ZlyaoFtoqccceXAevRaDjXFvb5+M7wnD6AuAJw=="},{"rawBytes":"MIICFTCCAZugAwIBAgIUD3Jlqt4qhrcZI4UnGfPGrEq/pjQwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDkxMTEyMDAwMFoXDTI4MDkwOTEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE7X7nK0wC7uEmDjW+on0sXIX3FacL3hhcrhneA+M/kl1OtvQiPmFrH9lbUQqOj/AfspJ8uGY3jaq8WuSg6ghatzYfuuzLAJIK4nGpCBafncF8EynOssPq64/Dz+JUWXqlo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUwOG4UqRLTz7eejgRBs9JjqFFmzMwHwYDVR0jBBgwFoAUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaAAwZQIxAI8HWLrke7uzhOpwlD1cNixPmoX9XFKe7bEPozo0D+vKi0Gt6VlC7xPedFIw4/AypAIwQP+FGRWvfx0IAH5/n0aRiN7/LVpyFA5RkJASZOVOib2Y8pNuhXa9V3ZbWO6v6kW/"},{"rawBytes":"MIIB9TCCAXqgAwIBAgIUNFryA06EHDIcd5EIbe8swbl9OY4wCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTMzMDgwNDEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEXYaXx4H0oNuVP/2cfydA3oaafvvkkkgb5hbL8/j/BO25S7uTmDOCA5e4QLLWCKFuc+xp2j14tCH4WmHzMUDvf2tXtInVliY5wZgQMM9L6klo/IwA9x4omdcjnT+kKJAjo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaQAwZgIxAPzXsV+eokrqOHSQZH/XhhHE1slOscKy3DQpYpYJ1AWmJ2lJu/XOmubBX5s7apllUwIxALw2Ts8CDACiK42UymC8fk6sbNfoXUAWqdyKTVt2Lst+wNdkRniGvx7jT65BKTkcsQ=="}]},"validFor":{"start":"2024-05-13T00:00:00Z","end":"2024-10-25T00:00:00Z"}},{"subject":{"organization":"GitHub, Inc.","commonName":"Internal Services Root"},"uri":"fulcio.githubapp.com","certChain":{"certificates":[{"rawBytes":"MIICKzCCAbCgAwIBAgIUQeyd9UH06yZ63pDuqjgUZ58CnpMwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwxMB4XDTI0MTAwMzEyMDAwMFoXDTI1MTAwMzEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEwvbET2w+j9j9j50iTInH1gb9GSXkpsCvWz5orX1zgme+/Qh/5gMkpfmgfOSLV2ZRgT1hzujYmnKQvP2mCxYnbwQELAkAf+VhEY/7Uw3zZvguGQSdF1cxzRHiMTOha5eFo3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUMib9z4ZYBcQANTVvVCa3KoTGbBUwHwYDVR0jBBgwFoAUwOG4UqRLTz7eejgRBs9JjqFFmzMwCgYIKoZIzj0EAwMDaQAwZgIxAPIU/zlJiJrxn6oTWNdEAD/YBSnhyxcvpq1D2DzFy8E8hbkEfMZPErYL7HyoL/BkdwIxAN9KDEKyktEUBrfHehfcLAzI2kERJx+8DSslXswOIbLaeqYfWsmrQAt5C0X/nOWxXA=="},{"rawBytes":"MIICFTCCAZugAwIBAgIUD3Jlqt4qhrcZI4UnGfPGrEq/pjQwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDkxMTEyMDAwMFoXDTI4MDkwOTEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE7X7nK0wC7uEmDjW+on0sXIX3FacL3hhcrhneA+M/kl1OtvQiPmFrH9lbUQqOj/AfspJ8uGY3jaq8WuSg6ghatzYfuuzLAJIK4nGpCBafncF8EynOssPq64/Dz+JUWXqlo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUwOG4UqRLTz7eejgRBs9JjqFFmzMwHwYDVR0jBBgwFoAUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaAAwZQIxAI8HWLrke7uzhOpwlD1cNixPmoX9XFKe7bEPozo0D+vKi0Gt6VlC7xPedFIw4/AypAIwQP+FGRWvfx0IAH5/n0aRiN7/LVpyFA5RkJASZOVOib2Y8pNuhXa9V3ZbWO6v6kW/"},{"rawBytes":"MIIB9TCCAXqgAwIBAgIUNFryA06EHDIcd5EIbe8swbl9OY4wCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTMzMDgwNDEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEXYaXx4H0oNuVP/2cfydA3oaafvvkkkgb5hbL8/j/BO25S7uTmDOCA5e4QLLWCKFuc+xp2j14tCH4WmHzMUDvf2tXtInVliY5wZgQMM9L6klo/IwA9x4omdcjnT+kKJAjo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaQAwZgIxAPzXsV+eokrqOHSQZH/XhhHE1slOscKy3DQpYpYJ1AWmJ2lJu/XOmubBX5s7apllUwIxALw2Ts8CDACiK42UymC8fk6sbNfoXUAWqdyKTVt2Lst+wNdkRniGvx7jT65BKTkcsQ=="}]},"validFor":{"start":"2024-10-07T00:00:00Z","end":"2025-06-19T00:00:00Z"}},{"subject":{"organization":"GitHub, Inc.","commonName":"Internal Services Root"},"uri":"fulcio.githubapp.com","certChain":{"certificates":[{"rawBytes":"MIICKTCCAbCgAwIBAgIUU7Y2PESb8FK88XCRQvI2rKDB5skwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwxMB4XDTI1MDUyNzAwMDAwMFoXDTI2MDUyNzAwMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEevMuOTGccTTXLu65xinRJP4gxkOemyAPCmUXEvhXKtLd+aDgN87PI0QxutOmPuQD2dLZVfxQmt5lvwJpUDIVYkKpH3mDxYbF6MJPMbq+A/EhkMaOS2+mcJRJdY2uam0Vo3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU8tE1L3b7FV3qJKzSqy1w97vPt6AwHwYDVR0jBBgwFoAUwOG4UqRLTz7eejgRBs9JjqFFmzMwCgYIKoZIzj0EAwMDZwAwZAIwSIsnfcDIvp+9SGBe9Es4xLDgZ6/B3wtODu+jtNUznQFPw1/WHa3iBMp3vYPkIqZDAjBBD4AKu166Hg7tDd5E8ckSCNNDkXD0Q7Xdl4yujgqv0goVMeOgRA1pKDGszhunjAQ="},{"rawBytes":"MIICFTCCAZugAwIBAgIUD3Jlqt4qhrcZI4UnGfPGrEq/pjQwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDkxMTEyMDAwMFoXDTI4MDkwOTEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE7X7nK0wC7uEmDjW+on0sXIX3FacL3hhcrhneA+M/kl1OtvQiPmFrH9lbUQqOj/AfspJ8uGY3jaq8WuSg6ghatzYfuuzLAJIK4nGpCBafncF8EynOssPq64/Dz+JUWXqlo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUwOG4UqRLTz7eejgRBs9JjqFFmzMwHwYDVR0jBBgwFoAUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaAAwZQIxAI8HWLrke7uzhOpwlD1cNixPmoX9XFKe7bEPozo0D+vKi0Gt6VlC7xPedFIw4/AypAIwQP+FGRWvfx0IAH5/n0aRiN7/LVpyFA5RkJASZOVOib2Y8pNuhXa9V3ZbWO6v6kW/"},{"rawBytes":"MIIB9TCCAXqgAwIBAgIUNFryA06EHDIcd5EIbe8swbl9OY4wCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTMzMDgwNDEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEXYaXx4H0oNuVP/2cfydA3oaafvvkkkgb5hbL8/j/BO25S7uTmDOCA5e4QLLWCKFuc+xp2j14tCH4WmHzMUDvf2tXtInVliY5wZgQMM9L6klo/IwA9x4omdcjnT+kKJAjo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaQAwZgIxAPzXsV+eokrqOHSQZH/XhhHE1slOscKy3DQpYpYJ1AWmJ2lJu/XOmubBX5s7apllUwIxALw2Ts8CDACiK42UymC8fk6sbNfoXUAWqdyKTVt2Lst+wNdkRniGvx7jT65BKTkcsQ=="}]},"validFor":{"start":"2025-05-27T00:00:00Z","end":"2025-12-09T00:00:00Z"}},{"subject":{"organization":"GitHub, Inc.","commonName":"Internal Services Root"},"uri":"fulcio.githubapp.com","certChain":{"certificates":[{"rawBytes":"MIICKjCCAbCgAwIBAgIUNansGfOzaol+jXgh/wsz0X36zTMwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwxMB4XDTI1MTExMzAwMDAwMFoXDTI2MTExMzAwMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE24Un0srydTqXkWtrVrNdgPRNa1cCZqBRpFImYqItuVs/mszkn+RSlwcMzg/LwphU84qwA8Xv2z8ZaIETHspoQgMO4dP5zhJ5W8M+9crL1kR4Co2vS3TJZF9Qpsg+HiTro3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUGO6KtZB8c9gbXS2uNF3tXCtFNhEwHwYDVR0jBBgwFoAUwOG4UqRLTz7eejgRBs9JjqFFmzMwCgYIKoZIzj0EAwMDaAAwZQIxALmOdTRy/SiOc+QSf+eAPz24xWGhMryvNpyNvBZDeyqthC9KaMKmX+kHwJ0A6/bTXQIwE1mPbkH6tBXuWxlR/2Bj7UZ3MADlHSYGQUjpHMdQqJk17UVBywad5U6uA/f0pFdU"},{"rawBytes":"MIICFTCCAZugAwIBAgIUD3Jlqt4qhrcZI4UnGfPGrEq/pjQwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDkxMTEyMDAwMFoXDTI4MDkwOTEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE7X7nK0wC7uEmDjW+on0sXIX3FacL3hhcrhneA+M/kl1OtvQiPmFrH9lbUQqOj/AfspJ8uGY3jaq8WuSg6ghatzYfuuzLAJIK4nGpCBafncF8EynOssPq64/Dz+JUWXqlo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUwOG4UqRLTz7eejgRBs9JjqFFmzMwHwYDVR0jBBgwFoAUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaAAwZQIxAI8HWLrke7uzhOpwlD1cNixPmoX9XFKe7bEPozo0D+vKi0Gt6VlC7xPedFIw4/AypAIwQP+FGRWvfx0IAH5/n0aRiN7/LVpyFA5RkJASZOVOib2Y8pNuhXa9V3ZbWO6v6kW/"},{"rawBytes":"MIIB9TCCAXqgAwIBAgIUNFryA06EHDIcd5EIbe8swbl9OY4wCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTMzMDgwNDEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEXYaXx4H0oNuVP/2cfydA3oaafvvkkkgb5hbL8/j/BO25S7uTmDOCA5e4QLLWCKFuc+xp2j14tCH4WmHzMUDvf2tXtInVliY5wZgQMM9L6klo/IwA9x4omdcjnT+kKJAjo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaQAwZgIxAPzXsV+eokrqOHSQZH/XhhHE1slOscKy3DQpYpYJ1AWmJ2lJu/XOmubBX5s7apllUwIxALw2Ts8CDACiK42UymC8fk6sbNfoXUAWqdyKTVt2Lst+wNdkRniGvx7jT65BKTkcsQ=="}]},"validFor":{"start":"2025-11-13T00:00:00Z","end":"2026-07-13T00:00:00Z"}},{"subject":{"organization":"GitHub, Inc.","commonName":"Internal Services Root"},"uri":"fulcio.githubapp.com","certChain":{"certificates":[{"rawBytes":"MIICKjCCAbCgAwIBAgIUCZdc6761A2K2ny11d+8tvDIlBuAwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwxMB4XDTI2MDYxMjAwMDAwMFoXDTI3MDYxMjAwMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8K+awztD1Q48OQsI2PzffEwHJxdHoKneYSSRgnG6VCk0RlZWeVHM7EwpsDMgMMz98IrRqVBAjxSt7fs39rz6NUKG3T26Khv0gmzuJ1xG0j0BmK0j0kkfRvzYpQKv108to3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUgeNOwb9e7ggupu+PZL6nr0Wgm88wHwYDVR0jBBgwFoAUwOG4UqRLTz7eejgRBs9JjqFFmzMwCgYIKoZIzj0EAwMDaAAwZQIweEGv0C3qXaA2+oPahLsBzRA2C5+oWNE5Jq1Qpqrn0QeDCSY5eCKzwFjVa5eICks5AjEAsifahLxtAiHc0I2SWHYX3s3sfOLPArgK6oBM0hKbIMCOokEUhss67N06KOuV2rqQ"},{"rawBytes":"MIICFTCCAZugAwIBAgIUD3Jlqt4qhrcZI4UnGfPGrEq/pjQwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDkxMTEyMDAwMFoXDTI4MDkwOTEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE7X7nK0wC7uEmDjW+on0sXIX3FacL3hhcrhneA+M/kl1OtvQiPmFrH9lbUQqOj/AfspJ8uGY3jaq8WuSg6ghatzYfuuzLAJIK4nGpCBafncF8EynOssPq64/Dz+JUWXqlo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUwOG4UqRLTz7eejgRBs9JjqFFmzMwHwYDVR0jBBgwFoAUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaAAwZQIxAI8HWLrke7uzhOpwlD1cNixPmoX9XFKe7bEPozo0D+vKi0Gt6VlC7xPedFIw4/AypAIwQP+FGRWvfx0IAH5/n0aRiN7/LVpyFA5RkJASZOVOib2Y8pNuhXa9V3ZbWO6v6kW/"},{"rawBytes":"MIIB9TCCAXqgAwIBAgIUNFryA06EHDIcd5EIbe8swbl9OY4wCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTMzMDgwNDEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEXYaXx4H0oNuVP/2cfydA3oaafvvkkkgb5hbL8/j/BO25S7uTmDOCA5e4QLLWCKFuc+xp2j14tCH4WmHzMUDvf2tXtInVliY5wZgQMM9L6klo/IwA9x4omdcjnT+kKJAjo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaQAwZgIxAPzXsV+eokrqOHSQZH/XhhHE1slOscKy3DQpYpYJ1AWmJ2lJu/XOmubBX5s7apllUwIxALw2Ts8CDACiK42UymC8fk6sbNfoXUAWqdyKTVt2Lst+wNdkRniGvx7jT65BKTkcsQ=="}]},"validFor":{"start":"2026-06-12T00:00:00Z"}}],"timestampAuthorities":[{"subject":{"organization":"GitHub, Inc.","commonName":"Internal Services Root"},"uri":"timestamp.githubapp.com","certChain":{"certificates":[{"rawBytes":"MIICHDCCAaGgAwIBAgIUNDVlmtZuvoujn4KwiC/oxIr8hxAwCgYIKoZIzj0EAwMwMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMB4XDTIzMDgzMTEyMDAwMFoXDTI0MDgzMDEyMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgVGltZXN0YW1waW5nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEV/zJhNTdu0Fa9hGCUih/JvqEoE81tEWrAVwUXXhdRgIY9hIFErLhNo6sSOpV9d7Zuy0KWMHhcimCUr41a1732ByVRy3f+Z4QhqpsgFMh5b5J90HJLK7HOyUZjehAnvSno3gwdjAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUGwE6T5ZIh6lY9wP6vt42UHyVMewwHwYDVR0jBBgwFoAUdh+GTP65aetHLVLs9hdhGgDIKIwwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwCgYIKoZIzj0EAwMDaQAwZgIxAJo48LtrSsn0UmLtqGiUKg2EUvso+aDN5EyjpvMmobZ/Oq9zjnR7Of369hoABW4/1gIxANg5ZW4FqijhsXnA3md6jM9yLrLCI9QL+KnuZnXq6WgAcNQaAN7PNNjVDKV3iJEklw=="},{"rawBytes":"MIICJDCCAaqgAwIBAgIUckXVHpiw7iJY1V/jY8LYLj5TgqAwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTI4MDgwNTEyMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEISv96hTQ58QroEzzu4K+o9p8YkwDCBia2U7Y+VBNbOG/w1mLRibve9hSeUE1FSyLBMkiFSSm6MexcsbjyqOoNtRxuMinyYt6DSEox+/It2s/bTPyNAN0QP0DCQQOpnTZo3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwgwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUdh+GTP65aetHLVLs9hdhGgDIKIwwHwYDVR0jBBgwFoAUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaAAwZQIxAIhf+2E5W2yOb/fCDAjhL/G/jerf74M0tG/zyo32U2keawxkzZosDdwnPaHaGLynAQIwa8nr3en4fZz1AdOZm6nK5hr1qK2F94nifgnAJ/WeT0fZnK/oHan0R28x363qYuYH"},{"rawBytes":"MIIB9TCCAXqgAwIBAgIUNFryA06EHDIcd5EIbe8swbl9OY4wCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTMzMDgwNDEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEXYaXx4H0oNuVP/2cfydA3oaafvvkkkgb5hbL8/j/BO25S7uTmDOCA5e4QLLWCKFuc+xp2j14tCH4WmHzMUDvf2tXtInVliY5wZgQMM9L6klo/IwA9x4omdcjnT+kKJAjo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaQAwZgIxAPzXsV+eokrqOHSQZH/XhhHE1slOscKy3DQpYpYJ1AWmJ2lJu/XOmubBX5s7apllUwIxALw2Ts8CDACiK42UymC8fk6sbNfoXUAWqdyKTVt2Lst+wNdkRniGvx7jT65BKTkcsQ=="}]},"validFor":{"start":"2023-10-27T16:30:00Z","end":"2024-05-25T00:00:00Z"}},{"subject":{"organization":"GitHub, Inc.","commonName":"Internal Services Root"},"uri":"timestamp.githubapp.com","certChain":{"certificates":[{"rawBytes":"MIICGzCCAaGgAwIBAgIUPPgn6ner1PU/75CQ+62fdBuazaAwCgYIKoZIzj0EAwMwMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMB4XDTI0MDUxMzAwMDAwMFoXDTI1MDUxMzAwMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgVGltZXN0YW1waW5nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEA0pG1mAC4qafk1JJuOoIvhnMME9XmBDxjGFreDLnyzaexIzRw+UHUFy8C2gE6Me+0tIGQt4Ftbu66NGmfvBkR6boPMYQSU2O5X5ykZBm/9LR/Aqz0lgmBy/OlXvTJjglo3gwdjAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUDa4GVhd97Z2V8kiVl9DB0kC53CMwHwYDVR0jBBgwFoAUdh+GTP65aetHLVLs9hdhGgDIKIwwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwCgYIKoZIzj0EAwMDaAAwZQIwEQHd++b7IBAAuqT2/1i/wXf1WM2XrkFF6qd1c3kFcBVvdLQyJ5KoyNUHnfCCVJROAjEA+FoASOEcARlU6RqVcif9JthHwzh6nNwz0AfAHvO8xantN/7HjiLmFrFEGR/g0kN/"},{"rawBytes":"MIICJDCCAaqgAwIBAgIUckXVHpiw7iJY1V/jY8LYLj5TgqAwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTI4MDgwNTEyMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEISv96hTQ58QroEzzu4K+o9p8YkwDCBia2U7Y+VBNbOG/w1mLRibve9hSeUE1FSyLBMkiFSSm6MexcsbjyqOoNtRxuMinyYt6DSEox+/It2s/bTPyNAN0QP0DCQQOpnTZo3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwgwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUdh+GTP65aetHLVLs9hdhGgDIKIwwHwYDVR0jBBgwFoAUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaAAwZQIxAIhf+2E5W2yOb/fCDAjhL/G/jerf74M0tG/zyo32U2keawxkzZosDdwnPaHaGLynAQIwa8nr3en4fZz1AdOZm6nK5hr1qK2F94nifgnAJ/WeT0fZnK/oHan0R28x363qYuYH"},{"rawBytes":"MIIB9TCCAXqgAwIBAgIUNFryA06EHDIcd5EIbe8swbl9OY4wCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTMzMDgwNDEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEXYaXx4H0oNuVP/2cfydA3oaafvvkkkgb5hbL8/j/BO25S7uTmDOCA5e4QLLWCKFuc+xp2j14tCH4WmHzMUDvf2tXtInVliY5wZgQMM9L6klo/IwA9x4omdcjnT+kKJAjo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaQAwZgIxAPzXsV+eokrqOHSQZH/XhhHE1slOscKy3DQpYpYJ1AWmJ2lJu/XOmubBX5s7apllUwIxALw2Ts8CDACiK42UymC8fk6sbNfoXUAWqdyKTVt2Lst+wNdkRniGvx7jT65BKTkcsQ=="}]},"validFor":{"start":"2024-05-13T00:00:00Z","end":"2024-10-25T00:00:00Z"}},{"subject":{"organization":"GitHub, Inc.","commonName":"Internal Services Root"},"uri":"timestamp.githubapp.com","certChain":{"certificates":[{"rawBytes":"MIICGzCCAaGgAwIBAgIUH7swiMTn+svhcDh80OeZccDTj7AwCgYIKoZIzj0EAwMwMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMB4XDTI0MTAwNDEyMDAwMFoXDTI1MTAwNDEyMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgVGltZXN0YW1waW5nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEM7jdYNBTeD6hjym2/y73b50u2AFQsf8305Sr1NleOqamH9aWt6obhJQH3NoNUw9iFzHcDvafYWQFMu7SmOxS5n3aqwwfR8oJxKnEl36uCmGB+8TXS3B76SVTHEhG5rzOo3gwdjAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUQvz9YbWX3S6a+jruBkhRBiE2RCkwHwYDVR0jBBgwFoAUdh+GTP65aetHLVLs9hdhGgDIKIwwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwCgYIKoZIzj0EAwMDaAAwZQIxAI4dhu5iyx/g+z1vKAAWvHtebl1ZwsC+Vwgjm6Ttlq5yLNHHvYEnJ/h15Qv2IuXvdgIwZ8H/iy4lXsFJdFYSsB1/zavl24EgxSzxK/pCpihXMetYYDA/lX3xMyquisMx45rN"},{"rawBytes":"MIICJDCCAaqgAwIBAgIUckXVHpiw7iJY1V/jY8LYLj5TgqAwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTI4MDgwNTEyMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEISv96hTQ58QroEzzu4K+o9p8YkwDCBia2U7Y+VBNbOG/w1mLRibve9hSeUE1FSyLBMkiFSSm6MexcsbjyqOoNtRxuMinyYt6DSEox+/It2s/bTPyNAN0QP0DCQQOpnTZo3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwgwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUdh+GTP65aetHLVLs9hdhGgDIKIwwHwYDVR0jBBgwFoAUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaAAwZQIxAIhf+2E5W2yOb/fCDAjhL/G/jerf74M0tG/zyo32U2keawxkzZosDdwnPaHaGLynAQIwa8nr3en4fZz1AdOZm6nK5hr1qK2F94nifgnAJ/WeT0fZnK/oHan0R28x363qYuYH"},{"rawBytes":"MIIB9TCCAXqgAwIBAgIUNFryA06EHDIcd5EIbe8swbl9OY4wCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTMzMDgwNDEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEXYaXx4H0oNuVP/2cfydA3oaafvvkkkgb5hbL8/j/BO25S7uTmDOCA5e4QLLWCKFuc+xp2j14tCH4WmHzMUDvf2tXtInVliY5wZgQMM9L6klo/IwA9x4omdcjnT+kKJAjo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaQAwZgIxAPzXsV+eokrqOHSQZH/XhhHE1slOscKy3DQpYpYJ1AWmJ2lJu/XOmubBX5s7apllUwIxALw2Ts8CDACiK42UymC8fk6sbNfoXUAWqdyKTVt2Lst+wNdkRniGvx7jT65BKTkcsQ=="}]},"validFor":{"start":"2024-10-07T00:00:00Z","end":"2025-06-19T00:00:00Z"}},{"subject":{"organization":"GitHub, Inc.","commonName":"Internal Services Root"},"uri":"timestamp.githubapp.com","certChain":{"certificates":[{"rawBytes":"MIICGzCCAaGgAwIBAgIUaaAaeAO3NzYXR/muq5YMnGY34IUwCgYIKoZIzj0EAwMwMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMB4XDTI1MDUyNzAwMDAwMFoXDTI2MDUyNzAwMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgVGltZXN0YW1waW5nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEAjyAb+jWITMjkgbjWC02N05Zf5/kPs73ICf5/8lHSyHnoqqItP2z3fkMpWiRpelX8eIjzCjZ0Zht+DpGxtJUNh5jMa+fFv2qoDLGbnQ17DXKXCr9nWHF4vCMflHZK5JHo3gwdjAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUyvAcv8s1s37A/IWZAwdEptzjfWIwHwYDVR0jBBgwFoAUdh+GTP65aetHLVLs9hdhGgDIKIwwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwCgYIKoZIzj0EAwMDaAAwZQIwMnoHA4qDtR3eRohUIhvHGG70xT9XMfEnuj0kRvi9wu9VumL4pXqz1ktJgTBm6DrYAjEA5SYnRfUm2yAYmGEkJr9RvSCV9O7X+fL8KqfM4TOPrXJLMERQjhRXbWZ4DjxvifnF"},{"rawBytes":"MIICJDCCAaqgAwIBAgIUckXVHpiw7iJY1V/jY8LYLj5TgqAwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTI4MDgwNTEyMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEISv96hTQ58QroEzzu4K+o9p8YkwDCBia2U7Y+VBNbOG/w1mLRibve9hSeUE1FSyLBMkiFSSm6MexcsbjyqOoNtRxuMinyYt6DSEox+/It2s/bTPyNAN0QP0DCQQOpnTZo3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwgwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUdh+GTP65aetHLVLs9hdhGgDIKIwwHwYDVR0jBBgwFoAUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaAAwZQIxAIhf+2E5W2yOb/fCDAjhL/G/jerf74M0tG/zyo32U2keawxkzZosDdwnPaHaGLynAQIwa8nr3en4fZz1AdOZm6nK5hr1qK2F94nifgnAJ/WeT0fZnK/oHan0R28x363qYuYH"},{"rawBytes":"MIIB9TCCAXqgAwIBAgIUNFryA06EHDIcd5EIbe8swbl9OY4wCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTMzMDgwNDEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEXYaXx4H0oNuVP/2cfydA3oaafvvkkkgb5hbL8/j/BO25S7uTmDOCA5e4QLLWCKFuc+xp2j14tCH4WmHzMUDvf2tXtInVliY5wZgQMM9L6klo/IwA9x4omdcjnT+kKJAjo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaQAwZgIxAPzXsV+eokrqOHSQZH/XhhHE1slOscKy3DQpYpYJ1AWmJ2lJu/XOmubBX5s7apllUwIxALw2Ts8CDACiK42UymC8fk6sbNfoXUAWqdyKTVt2Lst+wNdkRniGvx7jT65BKTkcsQ=="}]},"validFor":{"start":"2025-05-27T00:00:00Z","end":"2025-12-09T00:00:00Z"}},{"subject":{"organization":"GitHub, Inc.","commonName":"Internal Services Root"},"uri":"timestamp.githubapp.com","certChain":{"certificates":[{"rawBytes":"MIICGzCCAaGgAwIBAgIUHiljSG9Nx/Qf/gPpl7V1kyRh/jYwCgYIKoZIzj0EAwMwMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMB4XDTI1MTExMzAwMDAwMFoXDTI2MTExMzAwMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgVGltZXN0YW1waW5nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEG1ZUJgbtNpInC7jR9hi4OMWK5yF5VeRpmCs6ADS5lx23MEHo3ek32O123e3YwH+J3F9Vbpd7ePQHnpdc6tsBcQ/y/NPw2Vf+VwMI/E35C5/3Y9A5kRXq9CFXC1bU6Fj6o3gwdjAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUXa9JaNrcN+1Je5aHEemVnH2XXtMwHwYDVR0jBBgwFoAUdh+GTP65aetHLVLs9hdhGgDIKIwwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwCgYIKoZIzj0EAwMDaAAwZQIwJyVOay8lriDg4Up0VFWc/o19mzuQeoEItzM2dG50eXF4KbPo9zfdviS3D6StPFRGAjEA5IHCXJ8qP8CsAsQmT3IPimUTAvDNJZI4GFIxs+od/R/EUHGu4NxuwPX3A1ZnJP7z"},{"rawBytes":"MIICJDCCAaqgAwIBAgIUckXVHpiw7iJY1V/jY8LYLj5TgqAwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTI4MDgwNTEyMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEISv96hTQ58QroEzzu4K+o9p8YkwDCBia2U7Y+VBNbOG/w1mLRibve9hSeUE1FSyLBMkiFSSm6MexcsbjyqOoNtRxuMinyYt6DSEox+/It2s/bTPyNAN0QP0DCQQOpnTZo3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwgwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUdh+GTP65aetHLVLs9hdhGgDIKIwwHwYDVR0jBBgwFoAUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaAAwZQIxAIhf+2E5W2yOb/fCDAjhL/G/jerf74M0tG/zyo32U2keawxkzZosDdwnPaHaGLynAQIwa8nr3en4fZz1AdOZm6nK5hr1qK2F94nifgnAJ/WeT0fZnK/oHan0R28x363qYuYH"},{"rawBytes":"MIIB9TCCAXqgAwIBAgIUNFryA06EHDIcd5EIbe8swbl9OY4wCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTMzMDgwNDEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEXYaXx4H0oNuVP/2cfydA3oaafvvkkkgb5hbL8/j/BO25S7uTmDOCA5e4QLLWCKFuc+xp2j14tCH4WmHzMUDvf2tXtInVliY5wZgQMM9L6klo/IwA9x4omdcjnT+kKJAjo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaQAwZgIxAPzXsV+eokrqOHSQZH/XhhHE1slOscKy3DQpYpYJ1AWmJ2lJu/XOmubBX5s7apllUwIxALw2Ts8CDACiK42UymC8fk6sbNfoXUAWqdyKTVt2Lst+wNdkRniGvx7jT65BKTkcsQ=="}]},"validFor":{"start":"2025-11-13T00:00:00Z","end":"2026-07-13T00:00:00Z"}},{"subject":{"organization":"GitHub, Inc.","commonName":"Internal Services Root"},"uri":"timestamp.githubapp.com","certChain":{"certificates":[{"rawBytes":"MIICGzCCAaGgAwIBAgIUFzPb1un+DvofqFPzAXiB5slHM1IwCgYIKoZIzj0EAwMwMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMB4XDTI2MDYxMjAwMDAwMFoXDTI3MDYxMjAwMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgVGltZXN0YW1waW5nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETpok7Tndv57l0YnFjKL+VpcusM8A2xgbTdV353wW8IynvsLTGAjF1BNS4x80TDKHbXxN3jSoSId1u1c3sCWpuEqHB1bbBqCfrlslNn6hyWzhwtiqRLE4LM0T+RPclKQCo3gwdjAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUu/GnSzNLJ4IjG9elW5L5a/W61JMwHwYDVR0jBBgwFoAUdh+GTP65aetHLVLs9hdhGgDIKIwwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwCgYIKoZIzj0EAwMDaAAwZQIxANGz5WZBgyZqGnuhr0gDhUW2s4ZXkJ1CMUaFzynjsQ0/8WZghN7Jh04vmT4Vl8y9zgIwJeBHIZNDZtjzDQB0UC+7im4snua3zsBrRbsEtWnqQhDzyT580H/uF4EVQN2kUUtZ"},{"rawBytes":"MIICJDCCAaqgAwIBAgIUckXVHpiw7iJY1V/jY8LYLj5TgqAwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTI4MDgwNTEyMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEISv96hTQ58QroEzzu4K+o9p8YkwDCBia2U7Y+VBNbOG/w1mLRibve9hSeUE1FSyLBMkiFSSm6MexcsbjyqOoNtRxuMinyYt6DSEox+/It2s/bTPyNAN0QP0DCQQOpnTZo3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwgwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUdh+GTP65aetHLVLs9hdhGgDIKIwwHwYDVR0jBBgwFoAUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaAAwZQIxAIhf+2E5W2yOb/fCDAjhL/G/jerf74M0tG/zyo32U2keawxkzZosDdwnPaHaGLynAQIwa8nr3en4fZz1AdOZm6nK5hr1qK2F94nifgnAJ/WeT0fZnK/oHan0R28x363qYuYH"},{"rawBytes":"MIIB9TCCAXqgAwIBAgIUNFryA06EHDIcd5EIbe8swbl9OY4wCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTMzMDgwNDEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEXYaXx4H0oNuVP/2cfydA3oaafvvkkkgb5hbL8/j/BO25S7uTmDOCA5e4QLLWCKFuc+xp2j14tCH4WmHzMUDvf2tXtInVliY5wZgQMM9L6klo/IwA9x4omdcjnT+kKJAjo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaQAwZgIxAPzXsV+eokrqOHSQZH/XhhHE1slOscKy3DQpYpYJ1AWmJ2lJu/XOmubBX5s7apllUwIxALw2Ts8CDACiK42UymC8fk6sbNfoXUAWqdyKTVt2Lst+wNdkRniGvx7jT65BKTkcsQ=="}]},"validFor":{"start":"2026-06-12T00:00:00Z"}}]}