Skip to content

Commit 9ec514d

Browse files
authored
Merge pull request #660 from sigstore/verify-checkpoints
Add checkpoint verification
2 parents 9170121 + 5aba06b commit 9ec514d

File tree

3 files changed

+54
-7
lines changed

3 files changed

+54
-7
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ public void verify(byte[] artifactDigest, KeylessVerificationRequest request)
177177
try {
178178
rekorVerifier.verifyEntry(rekorEntry);
179179
} catch (RekorVerificationException ex) {
180-
throw new KeylessVerificationException("Rekor entry signature was not valid");
180+
throw new KeylessVerificationException("Rekor entry signature was not valid", ex);
181181
}
182182

183183
// check if the time of entry inclusion in the log (a stand-in for signing time) is within the

sigstore-java/src/main/java/dev/sigstore/rekor/client/RekorVerifier.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,15 @@
1717

1818
import com.google.common.hash.Hashing;
1919
import dev.sigstore.encryption.signers.Verifiers;
20+
import dev.sigstore.rekor.client.RekorEntry.Checkpoint;
2021
import dev.sigstore.trustroot.SigstoreTrustedRoot;
22+
import dev.sigstore.trustroot.TransparencyLog;
2123
import dev.sigstore.trustroot.TransparencyLogs;
2224
import java.security.InvalidKeyException;
2325
import java.security.NoSuchAlgorithmException;
2426
import java.security.SignatureException;
2527
import java.security.spec.InvalidKeySpecException;
28+
import java.util.Arrays;
2629
import java.util.Base64;
2730
import org.bouncycastle.util.encoders.Hex;
2831

@@ -84,6 +87,7 @@ public void verifyEntry(RekorEntry entry) throws RekorVerificationException {
8487

8588
// verify inclusion proof
8689
verifyInclusionProof(entry);
90+
verifyCheckpoint(entry, tlog);
8791
}
8892

8993
/** Verify that a Rekor Entry is in the log by checking inclusion proof. */
@@ -134,6 +138,43 @@ private void verifyInclusionProof(RekorEntry entry) throws RekorVerificationExce
134138
}
135139
}
136140

141+
private void verifyCheckpoint(RekorEntry entry, TransparencyLog tlog)
142+
throws RekorVerificationException {
143+
Checkpoint checkpoint;
144+
try {
145+
checkpoint = entry.getVerification().getInclusionProof().parsedCheckpoint();
146+
} catch (RekorParseException ex) {
147+
throw new RekorVerificationException("Could not parse checkpoint", ex);
148+
}
149+
150+
byte[] inclusionRootHash =
151+
Hex.decode(entry.getVerification().getInclusionProof().getRootHash());
152+
byte[] checkpointRootHash = Base64.getDecoder().decode(checkpoint.getBase64Hash());
153+
154+
if (!Arrays.equals(inclusionRootHash, checkpointRootHash)) {
155+
throw new RekorVerificationException(
156+
"Checkpoint root hash does not match root hash provided in inclusion proof");
157+
}
158+
var keyHash = Hashing.sha256().hashBytes(tlog.getPublicKey().getRawBytes()).asBytes();
159+
// checkpoint 0 is always the log, not any of the cross signing verifiers/monitors
160+
var sig = checkpoint.getSignatures().get(0);
161+
for (int i = 0; i < 4; i++) {
162+
if (sig.getKeyHint()[i] != keyHash[i]) {
163+
throw new RekorVerificationException(
164+
"Checkpoint key hint did not match provided log public key");
165+
}
166+
}
167+
try {
168+
Verifiers.newVerifier(tlog.getPublicKey().toJavaPublicKey())
169+
.verifyDigest(inclusionRootHash, sig.getSignature());
170+
} catch (NoSuchAlgorithmException
171+
| InvalidKeySpecException
172+
| SignatureException
173+
| InvalidKeyException ex) {
174+
throw new RekorVerificationException("Could not verify checkpoint signature", ex);
175+
}
176+
}
177+
137178
private static byte[] combineBytes(byte[] first, byte[] second) {
138179
byte[] result = new byte[first.length + second.length];
139180
System.arraycopy(first, 0, result, 0, first.length);

sigstore-java/src/test/resources/dev/sigstore/samples/rekor-response/valid/entry.json

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,23 @@
1111
"67e9d9f66f0ad388f7e1a20991e9a2ae3efad5cbf281e8b3d2aaf1ef99a4618c",
1212
"16a106400c53465f6e18c2475df6ba889ca30f5667bacf32b1a5661f14a5080c",
1313
"b4439e8d71edbc96271723cb7a969dd725e23e73d139361864a62ed76ce8dc11",
14-
"f4926d3efb0abd42b18e18372886854825a2b378e249bd333f81d8d4534485c4",
14+
"49b3e90806c7b63b5a86f5748e3ecb7d264ea0828eb74a45bc1a2cd7962408e8",
15+
"5059ad9b48fa50bd9adcbff0dd81c5a0dcb60f37e0716e723a33805a464f72f8",
1516
"6c2ce64219799e61d72996884eee9e19fb906e4d7fa04b71625fde4108f21762",
1617
"784f79c817abb78db3ae99b6c1ede640470bf4bb678673a05bf3a6b50aaaddd6",
17-
"0da021f68571b65e49e926e4c69024de3ac248a1319d254bc51a85a657b93c33"
18+
"c6d92ebf4e10cdba500ca410166cd0a8d8b312154d2f45bc4292d63dea6112f6",
19+
"1768732027401f6718b0df7769e2803127cfc099eb130a8ed7d913218f6a65f6",
20+
"0da021f68571b65e49e926e4c69024de3ac248a1319d254bc51a85a657b93c33",
21+
"bc8cf0c8497d5c24841de0c9bef598ec99bbd59d9538d58568340646fe289e9a",
22+
"be328fa737b8fa9461850b8034250f237ff5b0b590b9468e6223968df294872b",
23+
"6f06f4025d0346f04830352b23f65c8cd9e3ce4b8cb899877c35282521ddaf85"
1824
],
1925
"logIndex": 1227,
20-
"rootHash": "c2aaeaf36d5899ee2ab6d931bdc39d0c50e6a9dccee0322b6a9483538f4ef079",
21-
"checkpoint": "The checkpoint (signed tree head) that the inclusion proof is based on",
22-
"treeSize": 1237
26+
"rootHash": "effa4fa4575f72829016a64e584441203de533212f9470d63a56d1992e73465d",
27+
"treeSize": 14358,
28+
"checkpoint": "rekor.sigstage.dev - 108574341321668964\n14358\n7/pPpFdfcoKQFqZOWERBID3lMyEvlHDWOlbRmS5zRl0=\n\n— rekor.sigstage.dev 0y8wozBFAiB8OkuzdwlL6/rDEu2CsIfqmesaH/KLfmIMvlH3YTdIYgIhAPFZeXK6+b0vbWy4GSU/YZxiTpFrrzjsVOShN4LlPdZb\n"
2329
},
24-
"signedEntryTimestamp": "MEUCIAM2WgNNS1xwUBuoX/rYUBxbyKa6PVwNakss5KJzxw4jAiEA1//71yKdbHUi+rZRX7UyWBf4yBRp1vrkvOcWx6bbqfY="
30+
"signedEntryTimestamp": "MEUCIQCO8dFvolJwFZDHkhkSdsW3Ny+07fG8CF7G32feG8NJMgIgd2qfJ5shezuXX8I1S6DsudvIZ8xN/+y95at/V5xHfEQ="
2531
}
2632
}
2733
}

0 commit comments

Comments
 (0)