@@ -132,31 +132,30 @@ public Builder trustedRootProvider(TrustedRootProvider trustedRootProvider) {
132132 private AlgorithmRegistry .HashAlgorithm findHashAlgorithm (Bundle bundle )
133133 throws KeylessVerificationException {
134134 try {
135- var rekorEntry = bundle .getEntries ().get (0 );
136- var body = rekorEntry .getBodyDecoded ();
137- if ("0.0.1" .equals (body .getApiVersion ())) {
138- if ("hashedrekord" .equals (body .getKind ())) {
139- var entry = RekorTypes .getHashedRekordV001 (rekorEntry );
140- switch (entry .getData ().getHash ().getAlgorithm ()) {
141- case SHA_256 :
142- return AlgorithmRegistry .HashAlgorithm .SHA2_256 ;
143- case SHA_384 :
144- return AlgorithmRegistry .HashAlgorithm .SHA2_384 ;
145- case SHA_512 :
146- return AlgorithmRegistry .HashAlgorithm .SHA2_512 ;
135+ // special case hashedrekord:0.0.1 and dsse:0.0.1 which may contain legacy public key to hash
136+ // algorithm mappings
137+ if (!bundle .getEntries ().isEmpty ()) {
138+ var rekorEntry = bundle .getEntries ().get (0 );
139+ var body = rekorEntry .getBodyDecoded ();
140+ if ("0.0.1" .equals (body .getApiVersion ())) {
141+ if ("hashedrekord" .equals (body .getKind ())) {
142+ var entry = RekorTypes .getHashedRekordV001 (rekorEntry );
143+ switch (entry .getData ().getHash ().getAlgorithm ()) {
144+ case SHA_256 :
145+ return AlgorithmRegistry .HashAlgorithm .SHA2_256 ;
146+ case SHA_384 :
147+ return AlgorithmRegistry .HashAlgorithm .SHA2_384 ;
148+ case SHA_512 :
149+ return AlgorithmRegistry .HashAlgorithm .SHA2_512 ;
150+ }
151+ } else if ("dsse" .equals (body .getKind ())) {
152+ // dsse:0.0.1 is always sha256
153+ return AlgorithmRegistry .HashAlgorithm .SHA2_256 ;
147154 }
148- } else if ("dsse" .equals (body .getKind ())) {
149- // dsse:0.0.1 is always sha256
150- return AlgorithmRegistry .HashAlgorithm .SHA2_256 ;
151155 }
152- } else if ("0.0.2" .equals (body .getApiVersion ())) {
153- // rekor v2 entries must conform to the Algorithm Registry
154- var publicKey = Certificates .getLeaf (bundle .getCertPath ()).getPublicKey ();
155- var signingAlgorithm = AlgorithmRegistry .getSigningAlgorithm (publicKey );
156- return signingAlgorithm .getHashAlgorithm ();
157156 }
158- throw new KeylessVerificationException (
159- "Unsupported entry type: '" + body . getKind () + ":" + body . getApiVersion () + "'" );
157+ var publicKey = Certificates . getLeaf ( bundle . getCertPath ()). getPublicKey ();
158+ return AlgorithmRegistry . getSigningAlgorithm ( publicKey ). getHashAlgorithm ( );
160159 } catch (JsonParseException | RekorTypeException | UnsupportedAlgorithmException ex ) {
161160 throw new KeylessVerificationException (
162161 "Could not determine hash algorithm from sigstore bundle" , ex );
@@ -199,7 +198,7 @@ public void verify(byte[] artifactDigest, Bundle bundle, VerificationOptions opt
199198
200199 // a trusted CT log
201200 try {
202- fulcioVerifier .verifySigningCertificate (signingCert );
201+ fulcioVerifier .verifySigningCertificate (signingCert , options . getCtLogOptions () );
203202 } catch (FulcioVerificationException | IOException ex ) {
204203 throw new KeylessVerificationException (
205204 "Fulcio certificate was not valid: " + ex .getMessage (), ex );
@@ -209,7 +208,20 @@ public void verify(byte[] artifactDigest, Bundle bundle, VerificationOptions opt
209208 checkCertificateMatchers (leafCert , options .getCertificateMatchers ());
210209
211210 var hashAlgorithm = findHashAlgorithm (bundle );
212- var rekorEntry = bundle .getEntries ().get (0 );
211+
212+ // A transparency-log entry is required unless disabled (e.g. a private deployment that verifies
213+ // via a signed timestamp instead). We also refuse to ignore an entry that is present, so a
214+ // disabled policy cannot silently skip verifying a log entry the bundle actually contains.
215+ var entries = bundle .getEntries ();
216+ if (entries .isEmpty () && options .getTLogOptions ().isEnabled ()) {
217+ throw new KeylessVerificationException (
218+ "No transparency log entry found and transparency-log verification is required" );
219+ }
220+ if (!entries .isEmpty () && !options .getTLogOptions ().isEnabled ()) {
221+ throw new KeylessVerificationException (
222+ "Bundle contains a transparency log entry but transparency-log verification is disabled" );
223+ }
224+ var rekorEntry = entries .isEmpty () ? null : entries .get (0 );
213225
214226 byte [] signature ;
215227 if (bundle .getMessageSignature ().isPresent ()) { // hashedrekord
@@ -222,16 +234,20 @@ public void verify(byte[] artifactDigest, Bundle bundle, VerificationOptions opt
222234 signature = dsseEnvelope .getSignature ();
223235 }
224236
225- try {
226- rekorVerifier .verifyEntry (rekorEntry );
227- } catch (RekorVerificationException ex ) {
228- throw new KeylessVerificationException ("Transparency log entry could not be verified" , ex );
237+ // A transparency-log entry that is present is always verified; opting in only relaxes the
238+ // requirement that one exists, never the verification of one that is provided.
239+ Instant entryTime = null ;
240+ if (rekorEntry != null ) {
241+ try {
242+ rekorVerifier .verifyEntry (rekorEntry );
243+ } catch (RekorVerificationException ex ) {
244+ throw new KeylessVerificationException ("Transparency log entry could not be verified" , ex );
245+ }
246+ // if entry was verified and has a SET, get time from it
247+ var set = rekorEntry .getVerification ().getSignedEntryTimestamp ();
248+ entryTime = set != null ? rekorEntry .getIntegratedTimeInstant () : null ;
229249 }
230250
231- // if entry was verified and has a SET, get time from it
232- var set = rekorEntry .getVerification ().getSignedEntryTimestamp ();
233- var entryTime = set != null ? rekorEntry .getIntegratedTimeInstant () : null ;
234-
235251 verifyTimestamps (leafCert , bundle .getTimestamps (), entryTime , signature );
236252 }
237253
@@ -325,6 +341,12 @@ private void checkMessageSignature(
325341 "Signature could not be processed: " + ex .getMessage (), ex );
326342 }
327343
344+ // No transparency-log entry (allowed via VerificationOptions): the artifact digest and
345+ // signature have been verified above, and there is nothing to cross-check against.
346+ if (rekorEntry == null ) {
347+ return ;
348+ }
349+
328350 // recreate the log entry and check if it matches what was provided in the entry
329351 String version ;
330352 try {
@@ -469,6 +491,12 @@ private void checkDsseEnvelope(
469491 throw new KeylessVerificationException ("Signature could not be processed" , se );
470492 }
471493
494+ // No transparency-log entry (allowed via VerificationOptions): the payload subject digest and
495+ // DSSE signature have been verified above, and there is nothing to cross-check against.
496+ if (rekorEntry == null ) {
497+ return ;
498+ }
499+
472500 RekorEntryBody entryBody ;
473501 try {
474502 entryBody = rekorEntry .getBodyDecoded ();
0 commit comments