Skip to content

Commit 75bdd89

Browse files
PR #1252 review suggestions fixed
1 parent d7521af commit 75bdd89

6 files changed

Lines changed: 99 additions & 112 deletions

File tree

sigstore-cli/src/main/java/dev/sigstore/cli/Verify.java

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -113,16 +113,6 @@ static class Policy {
113113
required = false)
114114
Path workingDirectory;
115115

116-
@Option(
117-
names = {"--allow-non-transparency-log"},
118-
description =
119-
"verify a bundle that has no transparency-log entry, relying on a signed RFC 3161 "
120-
+ "timestamp instead (reduced assurance; for instances that do not publish to a "
121-
+ "transparency log, e.g. GitHub's Sigstore instance for private repositories)",
122-
required = false,
123-
defaultValue = "false")
124-
Boolean allowNonTransparencyLog;
125-
126116
@Override
127117
public Integer call() throws Exception {
128118
byte[] digest;
@@ -152,9 +142,6 @@ public Integer call() throws Exception {
152142
.subjectAlternativeName(StringMatcher.string(policy.certificateSan))
153143
.build());
154144
}
155-
if (allowNonTransparencyLog) {
156-
verificationOptionsBuilder.allowNonTransparencyLogVerification(true);
157-
}
158145
var verificationOptions = verificationOptionsBuilder.build();
159146

160147
KeylessVerifier verifier;

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

Lines changed: 32 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -132,43 +132,31 @@ public Builder trustedRootProvider(TrustedRootProvider trustedRootProvider) {
132132
private AlgorithmRegistry.HashAlgorithm findHashAlgorithm(Bundle bundle)
133133
throws KeylessVerificationException {
134134
try {
135-
// When the bundle has no transparency-log entry (e.g. a signed-timestamp-only bundle),
136-
// derive the hash algorithm without one: DSSE bundles use SHA-256 (as in the dsse:0.0.1
137-
// case below), otherwise fall back to the signing certificate's algorithm. Whether such a
138-
// bundle is permitted at all is enforced in verify(...) via
139-
// VerificationOptions#allowNonTransparencyLogVerification.
140-
if (bundle.getEntries().isEmpty()) {
141-
if (bundle.getDsseEnvelope().isPresent()) {
142-
return AlgorithmRegistry.HashAlgorithm.SHA2_256;
143-
}
144-
var publicKey = Certificates.getLeaf(bundle.getCertPath()).getPublicKey();
145-
return AlgorithmRegistry.getSigningAlgorithm(publicKey).getHashAlgorithm();
146-
}
147-
var rekorEntry = bundle.getEntries().get(0);
148-
var body = rekorEntry.getBodyDecoded();
149-
if ("0.0.1".equals(body.getApiVersion())) {
150-
if ("hashedrekord".equals(body.getKind())) {
151-
var entry = RekorTypes.getHashedRekordV001(rekorEntry);
152-
switch (entry.getData().getHash().getAlgorithm()) {
153-
case SHA_256:
154-
return AlgorithmRegistry.HashAlgorithm.SHA2_256;
155-
case SHA_384:
156-
return AlgorithmRegistry.HashAlgorithm.SHA2_384;
157-
case SHA_512:
158-
return AlgorithmRegistry.HashAlgorithm.SHA2_512;
135+
// rekor 0.0.1 hashedrekord entries encode the hash algorithm in the entry itself. Others
136+
// (rekor 0.0.2, a bundle with no transparency-log entry) derive it from the signing
137+
// certificate's public key.
138+
if (!bundle.getEntries().isEmpty()) {
139+
var rekorEntry = bundle.getEntries().get(0);
140+
var body = rekorEntry.getBodyDecoded();
141+
if ("0.0.1".equals(body.getApiVersion())) {
142+
if ("hashedrekord".equals(body.getKind())) {
143+
var entry = RekorTypes.getHashedRekordV001(rekorEntry);
144+
switch (entry.getData().getHash().getAlgorithm()) {
145+
case SHA_256:
146+
return AlgorithmRegistry.HashAlgorithm.SHA2_256;
147+
case SHA_384:
148+
return AlgorithmRegistry.HashAlgorithm.SHA2_384;
149+
case SHA_512:
150+
return AlgorithmRegistry.HashAlgorithm.SHA2_512;
151+
}
152+
} else if ("dsse".equals(body.getKind())) {
153+
// dsse:0.0.1 is always sha256
154+
return AlgorithmRegistry.HashAlgorithm.SHA2_256;
159155
}
160-
} else if ("dsse".equals(body.getKind())) {
161-
// dsse:0.0.1 is always sha256
162-
return AlgorithmRegistry.HashAlgorithm.SHA2_256;
163156
}
164-
} else if ("0.0.2".equals(body.getApiVersion())) {
165-
// rekor v2 entries must conform to the Algorithm Registry
166-
var publicKey = Certificates.getLeaf(bundle.getCertPath()).getPublicKey();
167-
var signingAlgorithm = AlgorithmRegistry.getSigningAlgorithm(publicKey);
168-
return signingAlgorithm.getHashAlgorithm();
169157
}
170-
throw new KeylessVerificationException(
171-
"Unsupported entry type: '" + body.getKind() + ":" + body.getApiVersion() + "'");
158+
var publicKey = Certificates.getLeaf(bundle.getCertPath()).getPublicKey();
159+
return AlgorithmRegistry.getSigningAlgorithm(publicKey).getHashAlgorithm();
172160
} catch (JsonParseException | RekorTypeException | UnsupportedAlgorithmException ex) {
173161
throw new KeylessVerificationException(
174162
"Could not determine hash algorithm from sigstore bundle", ex);
@@ -211,8 +199,7 @@ public void verify(byte[] artifactDigest, Bundle bundle, VerificationOptions opt
211199

212200
// a trusted CT log
213201
try {
214-
fulcioVerifier.verifySigningCertificate(
215-
signingCert, options.allowNonTransparencyLogVerification());
202+
fulcioVerifier.verifySigningCertificate(signingCert, options.getCtLogOptions());
216203
} catch (FulcioVerificationException | IOException ex) {
217204
throw new KeylessVerificationException(
218205
"Fulcio certificate was not valid: " + ex.getMessage(), ex);
@@ -223,17 +210,17 @@ public void verify(byte[] artifactDigest, Bundle bundle, VerificationOptions opt
223210

224211
var hashAlgorithm = findHashAlgorithm(bundle);
225212

226-
// A transparency-log entry is required by default. Bundles from instances that do not publish
227-
// to a log (e.g. GitHub's Sigstore instance for private repositories) carry only a signed
228-
// timestamp; verifying those requires opting in and relies on the RFC 3161 timestamp (verified
229-
// below) for trusted time.
213+
// A transparency-log entry is required unless disabled (e.g. a private deployment that verifies
214+
// via a signed timestamp instead). We also refuse to ignore an entry that is present, so a
215+
// disabled policy cannot silently skip verifying a log entry the bundle actually contains.
230216
var entries = bundle.getEntries();
231-
if (entries.isEmpty() && !options.allowNonTransparencyLogVerification()) {
217+
if (entries.isEmpty() && options.getTLogOptions().isEnabled()) {
218+
throw new KeylessVerificationException(
219+
"No transparency log entry found and transparency-log verification is required");
220+
}
221+
if (!entries.isEmpty() && !options.getTLogOptions().isEnabled()) {
232222
throw new KeylessVerificationException(
233-
"Bundle does not contain a transparency log entry. If it is from an instance that does "
234-
+ "not publish to a transparency log, set "
235-
+ "VerificationOptions.allowNonTransparencyLogVerification(true) to verify using its "
236-
+ "signed timestamp instead.");
223+
"Bundle contains a transparency log entry but transparency-log verification is disabled");
237224
}
238225
var rekorEntry = entries.isEmpty() ? null : entries.get(0);
239226

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

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,22 +29,38 @@ public interface VerificationOptions {
2929
/** An allow list of certificate identities to match with. */
3030
List<CertificateMatcher> getCertificateMatchers();
3131

32-
/**
33-
* Whether to allow verifying a bundle that contains no transparency-log entry, relying solely on
34-
* a signed RFC 3161 timestamp for trusted time. Defaults to {@code false}, matching the Sigstore
35-
* public-good instance where a transparency-log entry is always present and required.
36-
*
37-
* <p>Enabling this weakens the guarantees: without a transparency-log entry the signature is not
38-
* publicly monitorable/auditable. Only enable it for bundles from instances that intentionally do
39-
* not publish to a transparency log (for example, GitHub's Sigstore instance for private
40-
* repositories), and pair it with a pinned trusted root. A signed timestamp is still required.
41-
*
42-
* <p>This only relaxes the <em>requirement</em> that a transparency-log entry exists; a
43-
* transparency-log entry that <em>is</em> present in the bundle is still fully verified.
44-
*/
32+
/** Transparency-log (rekor) verification policy; defaults to requiring an entry. */
4533
@Default
46-
default boolean allowNonTransparencyLogVerification() {
47-
return false;
34+
default TLogOptions getTLogOptions() {
35+
return TLogOptions.builder().isEnabled(true).build();
36+
}
37+
38+
/** Certificate-transparency (SCT) verification policy; defaults to requiring an SCT. */
39+
@Default
40+
default CTLogOptions getCtLogOptions() {
41+
return CTLogOptions.builder().isEnabled(true).build();
42+
}
43+
44+
/** Transparency-log verification options. */
45+
@Immutable
46+
interface TLogOptions {
47+
/** Whether a transparency-log entry is required and verified. */
48+
boolean isEnabled();
49+
50+
static ImmutableTLogOptions.Builder builder() {
51+
return ImmutableTLogOptions.builder();
52+
}
53+
}
54+
55+
/** Certificate-transparency verification options. */
56+
@Immutable
57+
interface CTLogOptions {
58+
/** Whether a certificate-transparency log entry (SCT) is required and verified. */
59+
boolean isEnabled();
60+
61+
static ImmutableCTLogOptions.Builder builder() {
62+
return ImmutableCTLogOptions.builder();
63+
}
4864
}
4965

5066
/**

sigstore-java/src/main/java/dev/sigstore/bundle/BundleReader.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,7 @@ static Bundle readBundle(Reader jsonReader) throws BundleParseException {
5050

5151
bundleBuilder.mediaType(protoBundle.getMediaType());
5252

53-
// A bundle may legitimately carry no transparency-log entry (for example, GitHub's Sigstore
54-
// instance for private repositories, which relies on a signed RFC 3161 timestamp instead).
55-
// Such bundles are parsed here; whether their absence is acceptable is a verification-time
56-
// policy decision (see VerificationOptions#allowNonTransparencyLogVerification), not a parse
57-
// error. Any tlog entries that ARE present must still be well-formed (inclusion proof
58-
// required).
53+
// A bundle may skip tlog entries in private environments that don't require them.
5954
for (var bundleEntry : protoBundle.getVerificationMaterial().getTlogEntriesList()) {
6055
if (!bundleEntry.hasInclusionProof()) {
6156
// all consumed bundles must have an inclusion proof

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

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package dev.sigstore.fulcio.client;
1717

1818
import com.google.common.annotations.VisibleForTesting;
19+
import dev.sigstore.VerificationOptions.CTLogOptions;
1920
import dev.sigstore.encryption.certificates.Certificates;
2021
import dev.sigstore.encryption.certificates.transparency.CTLogInfo;
2122
import dev.sigstore.encryption.certificates.transparency.CTVerificationResult;
@@ -44,7 +45,7 @@
4445
import java.util.Map;
4546
import java.util.stream.Collectors;
4647

47-
/** Verifier for fulcio generated signing cerificates */
48+
/** Verifier for fulcio generated signing certificates */
4849
public class FulcioVerifier {
4950
private final List<CertificateAuthority> cas;
5051
private final List<TransparencyLog> ctLogs;
@@ -150,28 +151,17 @@ private void verifyEmbeddedScts(CertPath certPath) throws FulcioVerificationExce
150151
*/
151152
public void verifySigningCertificate(CertPath signingCertificate)
152153
throws FulcioVerificationException, IOException {
153-
verifySigningCertificate(signingCertificate, false);
154+
verifySigningCertificate(signingCertificate, CTLogOptions.builder().isEnabled(true).build());
154155
}
155156

156157
/**
157-
* Verify a signing certificate, optionally tolerating a trust root that provides no certificate
158-
* transparency logs.
159-
*
160-
* @param signingCertificate the chain to verify
161-
* @param allowEmptyCtLogs when {@code true} and the configured trust root declares <em>no</em> CT
162-
* logs, SCT verification is skipped. This is required for Sigstore instances that do not use
163-
* certificate transparency (for example, GitHub's Sigstore instance for private repositories,
164-
* whose trust root carries zero CT logs and whose certificates embed no SCT). When {@code
165-
* false} (the default) a missing CT log is an error, preserving the strict behavior for the
166-
* public-good instance. If the trust root <em>does</em> provide CT logs, SCTs are always
167-
* verified regardless of this flag.
158+
* Verify a signing certificate, applying the given certificate-transparency policy. When SCT
159+
* verification is disabled (ex: a private deployment without CT logs) the SCT check is skipped.
168160
*/
169-
public void verifySigningCertificate(CertPath signingCertificate, boolean allowEmptyCtLogs)
161+
public void verifySigningCertificate(CertPath signingCertificate, CTLogOptions ctLogOptions)
170162
throws FulcioVerificationException, IOException {
171163
CertPath fullCertPath = validateCertPath(signingCertificate);
172-
if (ctLogs.isEmpty() && allowEmptyCtLogs) {
173-
// The trust root declares no certificate-transparency logs, so there is nothing to verify an
174-
// SCT against. Trusted time is established elsewhere (a signed RFC 3161 timestamp).
164+
if (!ctLogOptions.isEnabled()) {
175165
return;
176166
}
177167
verifySct(fullCertPath);
@@ -200,7 +190,8 @@ CertPath validateCertPath(CertPath signingCertificate) throws FulcioVerification
200190
} catch (NoSuchAlgorithmException e) {
201191
//
202192
throw new RuntimeException(
203-
"No PKIX CertPathValidator, we probably shouldn't be here, but this seems to be a system library error not a program control flow issue",
193+
"No PKIX CertPathValidator, we probably shouldn't be here, but this seems to be a system"
194+
+ " library error not a program control flow issue",
204195
e);
205196
}
206197

@@ -220,7 +211,8 @@ CertPath validateCertPath(CertPath signingCertificate) throws FulcioVerification
220211
pkixParams = new PKIXParameters(Collections.singleton(ca.asTrustAnchor()));
221212
} catch (InvalidAlgorithmParameterException | CertificateException e) {
222213
throw new RuntimeException(
223-
"Can't create PKIX parameters for fulcioRoot. This should have been checked when generating a verifier instance",
214+
"Can't create PKIX parameters for fulcioRoot. This should have been checked when"
215+
+ " generating a verifier instance",
224216
e);
225217
}
226218
pkixParams.setRevocationEnabled(false);

0 commit comments

Comments
 (0)