22
22
import dev .sigstore .VerificationOptions .CertificateMatcher ;
23
23
import dev .sigstore .VerificationOptions .UncheckedCertificateException ;
24
24
import dev .sigstore .bundle .Bundle ;
25
+ import dev .sigstore .bundle .Bundle .MessageSignature ;
25
26
import dev .sigstore .encryption .certificates .Certificates ;
26
27
import dev .sigstore .encryption .signers .Verifiers ;
27
28
import dev .sigstore .fulcio .client .FulcioVerificationException ;
28
29
import dev .sigstore .fulcio .client .FulcioVerifier ;
29
30
import dev .sigstore .rekor .client .HashedRekordRequest ;
30
31
import dev .sigstore .rekor .client .RekorEntry ;
32
+ import dev .sigstore .rekor .client .RekorTypeException ;
33
+ import dev .sigstore .rekor .client .RekorTypes ;
31
34
import dev .sigstore .rekor .client .RekorVerificationException ;
32
35
import dev .sigstore .rekor .client .RekorVerifier ;
33
36
import dev .sigstore .tuf .SigstoreTufClient ;
45
48
import java .security .spec .InvalidKeySpecException ;
46
49
import java .sql .Date ;
47
50
import java .util .Arrays ;
51
+ import java .util .Base64 ;
48
52
import java .util .List ;
49
53
import java .util .Objects ;
50
54
import java .util .stream .Collectors ;
51
- import org .bouncycastle .util .encoders .Base64 ;
52
55
import org .bouncycastle .util .encoders .Hex ;
53
56
54
57
/** Verify hashrekords from rekor signed using the keyless signing flow with fulcio certificates. */
@@ -125,12 +128,10 @@ public void verify(byte[] artifactDigest, Bundle bundle, VerificationOptions opt
125
128
if (bundle .getDsseEnvelope ().isPresent ()) {
126
129
throw new KeylessVerificationException ("Cannot verify DSSE signature based bundles" );
127
130
}
128
-
129
131
if (bundle .getMessageSignature ().isEmpty ()) {
130
132
// this should be unreachable
131
133
throw new IllegalStateException ("Bundle must contain a message signature to verify" );
132
134
}
133
- var messageSignature = bundle .getMessageSignature ().get ();
134
135
135
136
if (bundle .getEntries ().isEmpty ()) {
136
137
throw new KeylessVerificationException ("Cannot verify bundle without tlog entry" );
@@ -149,20 +150,6 @@ public void verify(byte[] artifactDigest, Bundle bundle, VerificationOptions opt
149
150
var signingCert = bundle .getCertPath ();
150
151
var leafCert = Certificates .getLeaf (signingCert );
151
152
152
- // this ensures the provided artifact digest matches what may have come from a bundle (in
153
- // keyless signature)
154
- if (messageSignature .getMessageDigest ().isPresent ()) {
155
- var bundleDigest = messageSignature .getMessageDigest ().get ().getDigest ();
156
- if (!Arrays .equals (artifactDigest , bundleDigest )) {
157
- throw new KeylessVerificationException (
158
- "Provided artifact digest does not match digest used for verification"
159
- + "\n provided(hex) : "
160
- + Hex .toHexString (artifactDigest )
161
- + "\n verification : "
162
- + Hex .toHexString (bundleDigest ));
163
- }
164
- }
165
-
166
153
// verify the certificate chains up to a trusted root (fulcio) and contains a valid SCT from
167
154
// a trusted CT log
168
155
try {
@@ -175,8 +162,6 @@ public void verify(byte[] artifactDigest, Bundle bundle, VerificationOptions opt
175
162
// verify the certificate identity if options are present
176
163
checkCertificateMatchers (leafCert , options .getCertificateMatchers ());
177
164
178
- var signature = messageSignature .getSignature ();
179
-
180
165
RekorEntry rekorEntry = bundle .getEntries ().get (0 );
181
166
182
167
// verify the rekor entry is signed by the log keys
@@ -186,23 +171,6 @@ public void verify(byte[] artifactDigest, Bundle bundle, VerificationOptions opt
186
171
throw new KeylessVerificationException ("Rekor entry signature was not valid" , ex );
187
172
}
188
173
189
- // verify the log entry is relevant to the provided verification materials
190
- try {
191
- var calculatedHashedRekord =
192
- Base64 .toBase64String (
193
- HashedRekordRequest .newHashedRekordRequest (
194
- artifactDigest , Certificates .toPemBytes (leafCert ), signature )
195
- .toJsonPayload ()
196
- .getBytes (StandardCharsets .UTF_8 ));
197
- if (!Objects .equals (calculatedHashedRekord , rekorEntry .getBody ())) {
198
- throw new KeylessVerificationException (
199
- "Provided verification materials are inconsistent with log entry" );
200
- }
201
- } catch (IOException e ) {
202
- // this should be unreachable, we know leafCert is a valid certificate at this point
203
- throw new RuntimeException ("Unexpected IOException on valid leafCert" , e );
204
- }
205
-
206
174
// check if the time of entry inclusion in the log (a stand-in for signing time) is within the
207
175
// validity period for the certificate
208
176
var entryTime = Date .from (rekorEntry .getIntegratedTimeInstant ());
@@ -214,19 +182,7 @@ public void verify(byte[] artifactDigest, Bundle bundle, VerificationOptions opt
214
182
throw new KeylessVerificationException ("Signing time was after certificate expiry" , e );
215
183
}
216
184
217
- // finally check the supplied signature can be verified by the public key in the certificate
218
- var publicKey = leafCert .getPublicKey ();
219
- try {
220
- var verifier = Verifiers .newVerifier (publicKey );
221
- if (!verifier .verifyDigest (artifactDigest , signature )) {
222
- throw new KeylessVerificationException ("Artifact signature was not valid" );
223
- }
224
- } catch (NoSuchAlgorithmException | InvalidKeyException ex ) {
225
- throw new RuntimeException (ex );
226
- } catch (SignatureException ex ) {
227
- throw new KeylessVerificationException (
228
- "Signature could not be processed: " + ex .getMessage (), ex );
229
- }
185
+ checkMessageSignature (bundle .getMessageSignature ().get (), rekorEntry , artifactDigest , leafCert );
230
186
}
231
187
232
188
@ VisibleForTesting
@@ -244,4 +200,59 @@ void checkCertificateMatchers(X509Certificate cert, List<CertificateMatcher> mat
244
200
"Could not verify certificate identities: " + ce .getMessage ());
245
201
}
246
202
}
203
+
204
+ void checkMessageSignature (
205
+ MessageSignature messageSignature ,
206
+ RekorEntry rekorEntry ,
207
+ byte [] artifactDigest ,
208
+ X509Certificate leafCert )
209
+ throws KeylessVerificationException {
210
+ // this ensures the provided artifact digest matches what may have come from a bundle (in
211
+ // keyless signature)
212
+ if (messageSignature .getMessageDigest ().isPresent ()) {
213
+ var bundleDigest = messageSignature .getMessageDigest ().get ().getDigest ();
214
+ if (!Arrays .equals (artifactDigest , bundleDigest )) {
215
+ throw new KeylessVerificationException (
216
+ "Provided artifact digest does not match digest used for verification"
217
+ + "\n provided(hex) : "
218
+ + Hex .toHexString (artifactDigest )
219
+ + "\n verification(hex) : "
220
+ + Hex .toHexString (bundleDigest ));
221
+ }
222
+ }
223
+
224
+ // verify the signature over the artifact
225
+ var signature = messageSignature .getSignature ();
226
+ try {
227
+ if (!Verifiers .newVerifier (leafCert .getPublicKey ()).verifyDigest (artifactDigest , signature )) {
228
+ throw new KeylessVerificationException ("Artifact signature was not valid" );
229
+ }
230
+ } catch (NoSuchAlgorithmException | InvalidKeyException ex ) {
231
+ throw new RuntimeException (ex );
232
+ } catch (SignatureException ex ) {
233
+ throw new KeylessVerificationException (
234
+ "Signature could not be processed: " + ex .getMessage (), ex );
235
+ }
236
+
237
+ // recreate the log entry and check if it matches what was provided in the rekorEntry
238
+ try {
239
+ RekorTypes .getHashedRekord (rekorEntry );
240
+ var calculatedHashedRekord =
241
+ Base64 .getEncoder ()
242
+ .encodeToString (
243
+ HashedRekordRequest .newHashedRekordRequest (
244
+ artifactDigest , Certificates .toPemBytes (leafCert ), signature )
245
+ .toJsonPayload ()
246
+ .getBytes (StandardCharsets .UTF_8 ));
247
+ if (!Objects .equals (calculatedHashedRekord , rekorEntry .getBody ())) {
248
+ throw new KeylessVerificationException (
249
+ "Provided verification materials are inconsistent with log entry" );
250
+ }
251
+ } catch (IOException e ) {
252
+ // this should be unreachable, we know leafCert is a valid certificate at this point
253
+ throw new RuntimeException ("Unexpected IOException on valid leafCert" , e );
254
+ } catch (RekorTypeException re ) {
255
+ throw new KeylessVerificationException ("Unexpected rekor type" , re );
256
+ }
257
+ }
247
258
}
0 commit comments