Skip to content

Commit 1fafdf1

Browse files
authored
Merge pull request #1073 from sigstore/rekor-types-v2
Add Rekor v2 types to RekorTypes
2 parents d62cc6a + 5d2abc4 commit 1fafdf1

File tree

8 files changed

+160
-53
lines changed

8 files changed

+160
-53
lines changed

fuzzing/src/main/java/fuzzing/RekorTypesFuzzer.java

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,30 @@ public class RekorTypesFuzzer {
2929

3030
public static void fuzzerTestOneInput(FuzzedDataProvider data) {
3131
try {
32-
int type = data.pickValue(new int[] {0, 1});
32+
int type = data.pickValue(new int[] {0, 1, 2, 3});
3333
String string = data.consumeRemainingAsString();
3434

35-
URI uri = new URI(URL);
36-
RekorEntry entry = RekorResponse.newRekorResponse(uri, string).getEntry();
37-
38-
if (type == 0) {
39-
RekorTypes.getHashedRekord(entry);
35+
RekorEntry entry;
36+
if (type < 2) {
37+
URI uri = new URI(URL);
38+
entry = RekorResponse.newRekorResponse(uri, string).getEntry();
4039
} else {
41-
RekorTypes.getDsse(entry);
40+
entry = RekorEntry.fromTLogEntryJson(string);
41+
}
42+
43+
switch (type) {
44+
case 0:
45+
RekorTypes.getHashedRekordV001(entry);
46+
break;
47+
case 1:
48+
RekorTypes.getDsseV001(entry);
49+
break;
50+
case 2:
51+
RekorTypes.getHashedRekordV002(entry);
52+
break;
53+
case 3:
54+
RekorTypes.getDsseV002(entry);
55+
break;
4256
}
4357
} catch (URISyntaxException | RekorTypeException | RekorParseException e) {
4458
// Known exception

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

Lines changed: 13 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@
1919
import com.google.common.annotations.VisibleForTesting;
2020
import com.google.common.hash.Hashing;
2121
import com.google.common.io.Files;
22-
import com.google.gson.Gson;
23-
import com.google.protobuf.InvalidProtocolBufferException;
2422
import dev.sigstore.VerificationOptions.CertificateMatcher;
2523
import dev.sigstore.VerificationOptions.UncheckedCertificateException;
2624
import dev.sigstore.bundle.Bundle;
@@ -31,7 +29,6 @@
3129
import dev.sigstore.encryption.signers.Verifiers;
3230
import dev.sigstore.fulcio.client.FulcioVerificationException;
3331
import dev.sigstore.fulcio.client.FulcioVerifier;
34-
import dev.sigstore.json.ProtoJson;
3532
import dev.sigstore.proto.common.v1.HashAlgorithm;
3633
import dev.sigstore.proto.rekor.v2.DSSELogEntryV002;
3734
import dev.sigstore.proto.rekor.v2.HashedRekordLogEntryV002;
@@ -71,7 +68,9 @@
7168
import org.bouncycastle.util.encoders.DecoderException;
7269
import org.bouncycastle.util.encoders.Hex;
7370

74-
/** Verify hashrekords from rekor signed using the keyless signing flow with fulcio certificates. */
71+
/**
72+
* Verify hashedrekords from rekor signed using the keyless signing flow with fulcio certificates.
73+
*/
7574
public class KeylessVerifier {
7675

7776
private final FulcioVerifier fulcioVerifier;
@@ -271,7 +270,7 @@ private void checkMessageSignature(
271270
String version = rekorEntry.getBodyDecoded().getApiVersion();
272271
if ("0.0.1".equals(version)) {
273272
try {
274-
RekorTypes.getHashedRekord(rekorEntry);
273+
RekorTypes.getHashedRekordV001(rekorEntry);
275274
var calculatedHashedRekord =
276275
HashedRekordRequest.newHashedRekordRequest(
277276
artifactDigest, Certificates.toPemBytes(leafCert), signature)
@@ -291,21 +290,10 @@ private void checkMessageSignature(
291290
} else if ("0.0.2".equals(version)) {
292291
HashedRekordLogEntryV002 logEntrySpec;
293292
try {
294-
HashedRekordLogEntryV002.Builder builder = HashedRekordLogEntryV002.newBuilder();
295-
ProtoJson.parser()
296-
.ignoringUnknownFields()
297-
.merge(
298-
new Gson()
299-
.toJson(
300-
rekorEntry
301-
.getBodyDecoded()
302-
.getSpec()
303-
.getAsJsonObject()
304-
.get("hashedRekordV002")),
305-
builder);
306-
logEntrySpec = builder.build();
307-
} catch (InvalidProtocolBufferException ipbe) {
308-
throw new KeylessVerificationException("Could not parse hashedrekord from log entry body");
293+
logEntrySpec = RekorTypes.getHashedRekordV002(rekorEntry);
294+
} catch (RekorTypeException re) {
295+
throw new KeylessVerificationException(
296+
"Could not parse hashedrekord from log entry body", re);
309297
}
310298

311299
if (!logEntrySpec.getData().getAlgorithm().equals(HashAlgorithm.SHA2_256)) {
@@ -408,7 +396,7 @@ private void checkDsseEnvelope(
408396
if ("0.0.1".equals(version)) {
409397
Dsse rekorDsse;
410398
try {
411-
rekorDsse = RekorTypes.getDsse(rekorEntry);
399+
rekorDsse = RekorTypes.getDsseV001(rekorEntry);
412400
} catch (RekorTypeException re) {
413401
throw new KeylessVerificationException("Unexpected rekor type", re);
414402
}
@@ -425,7 +413,7 @@ private void checkDsseEnvelope(
425413
payloadDigest = Hex.decode(rekorDsse.getPayloadHash().getValue());
426414
} catch (DecoderException de) {
427415
throw new KeylessVerificationException(
428-
"Could not decode hex sha256 artifact hash in hashrekord", de);
416+
"Could not decode hex sha256 artifact hash in hashedrekord", de);
429417
}
430418

431419
byte[] calculatedDigest = Hashing.sha256().hashBytes(dsseEnvelope.getPayload()).asBytes();
@@ -450,16 +438,9 @@ private void checkDsseEnvelope(
450438
} else if ("0.0.2".equals(version)) {
451439
DSSELogEntryV002 logEntrySpec;
452440
try {
453-
DSSELogEntryV002.Builder builder = DSSELogEntryV002.newBuilder();
454-
ProtoJson.parser()
455-
.merge(
456-
new Gson()
457-
.toJson(
458-
rekorEntry.getBodyDecoded().getSpec().getAsJsonObject().get("dsseV002")),
459-
builder);
460-
logEntrySpec = builder.build();
461-
} catch (InvalidProtocolBufferException ipbe) {
462-
throw new KeylessVerificationException("Could not parse DSSE from log entry body", ipbe);
441+
logEntrySpec = RekorTypes.getDsseV002(rekorEntry);
442+
} catch (RekorTypeException re) {
443+
throw new KeylessVerificationException("Could not parse DSSE from log entry body", re);
463444
}
464445

465446
if (!logEntrySpec.getPayloadHash().getAlgorithm().equals(HashAlgorithm.SHA2_256)) {

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
import dev.sigstore.KeylessVerificationException;
1919
import dev.sigstore.TrustedRootProvider;
2020
import dev.sigstore.encryption.certificates.Certificates;
21-
import dev.sigstore.trustroot.*;
21+
import dev.sigstore.trustroot.Service;
22+
import dev.sigstore.trustroot.SigstoreConfigurationException;
23+
import dev.sigstore.trustroot.TransparencyLog;
2224
import dev.sigstore.tuf.SigstoreTufClient;
2325
import java.io.IOException;
2426
import java.nio.file.Path;
@@ -81,7 +83,7 @@ public RekorEntry getEntryFromRekor(
8183
artifactDigest, Certificates.toPemBytes(leafCert), signature);
8284
} catch (IOException e) {
8385
throw new KeylessVerificationException(
84-
"Could not convert certificate to PEM when recreating hashrekord", e);
86+
"Could not convert certificate to PEM when recreating hashedrekord", e);
8587
}
8688
Optional<RekorEntry> rekorEntry;
8789

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

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
import static dev.sigstore.json.GsonSupplier.GSON;
1919

2020
import com.google.gson.JsonParseException;
21+
import com.google.protobuf.InvalidProtocolBufferException;
22+
import dev.sigstore.json.ProtoJson;
23+
import dev.sigstore.proto.rekor.v2.DSSELogEntryV002;
24+
import dev.sigstore.proto.rekor.v2.HashedRekordLogEntryV002;
2125
import dev.sigstore.rekor.dsse.v0_0_1.Dsse;
2226
import dev.sigstore.rekor.hashedRekord.v0_0_1.HashedRekord;
2327

@@ -29,15 +33,41 @@ public class RekorTypes {
2933
*
3034
* @param entry the rekor entry obtained from rekor
3135
* @return the parsed pojo
32-
* @throws RekorTypeException if the hashrekord:0.0.1 entry could not be parsed
36+
* @throws RekorTypeException if the hashedrekord:0.0.1 entry could not be parsed
3337
*/
34-
public static HashedRekord getHashedRekord(RekorEntry entry) throws RekorTypeException {
38+
public static HashedRekord getHashedRekordV001(RekorEntry entry) throws RekorTypeException {
3539
expect(entry, "hashedrekord", "0.0.1");
3640

3741
try {
3842
return GSON.get().fromJson(entry.getBodyDecoded().getSpec(), HashedRekord.class);
3943
} catch (JsonParseException jpe) {
40-
throw new RekorTypeException("Could not parse hashrekord:0.0.1", jpe);
44+
throw new RekorTypeException("Could not parse hashedrekord:0.0.1", jpe);
45+
}
46+
}
47+
48+
/**
49+
* Parse a hashedrekord from rekor at api version 0.0.2.
50+
*
51+
* @param entry the rekor entry obtained from rekor
52+
* @return the parsed proto
53+
* @throws RekorTypeException if the hashedrekord:0.0.2 entry could not be parsed
54+
*/
55+
public static HashedRekordLogEntryV002 getHashedRekordV002(RekorEntry entry)
56+
throws RekorTypeException {
57+
expect(entry, "hashedrekord", "0.0.2");
58+
59+
try {
60+
HashedRekordLogEntryV002.Builder builder = HashedRekordLogEntryV002.newBuilder();
61+
ProtoJson.parser()
62+
.ignoringUnknownFields()
63+
.merge(
64+
GSON.get()
65+
.toJson(
66+
entry.getBodyDecoded().getSpec().getAsJsonObject().get("hashedRekordV002")),
67+
builder);
68+
return builder.build();
69+
} catch (InvalidProtocolBufferException | NullPointerException | IllegalStateException e) {
70+
throw new RekorTypeException("Could not parse hashedrekord:0.0.2", e);
4171
}
4272
}
4373

@@ -48,7 +78,7 @@ public static HashedRekord getHashedRekord(RekorEntry entry) throws RekorTypeExc
4878
* @return the parsed pojo
4979
* @throws RekorTypeException if the dsse:0.0.1 entry could not be parsed
5080
*/
51-
public static Dsse getDsse(RekorEntry entry) throws RekorTypeException {
81+
public static Dsse getDsseV001(RekorEntry entry) throws RekorTypeException {
5282
expect(entry, "dsse", "0.0.1");
5383

5484
try {
@@ -58,6 +88,29 @@ public static Dsse getDsse(RekorEntry entry) throws RekorTypeException {
5888
}
5989
}
6090

91+
/**
92+
* Parse a dsse from rekor at api version 0.0.2.
93+
*
94+
* @param entry the rekor entry obtained from rekor
95+
* @return the parsed proto
96+
* @throws RekorTypeException if the dsse:0.0.2 entry could not be parsed
97+
*/
98+
public static DSSELogEntryV002 getDsseV002(RekorEntry entry) throws RekorTypeException {
99+
expect(entry, "dsse", "0.0.2");
100+
101+
try {
102+
DSSELogEntryV002.Builder builder = DSSELogEntryV002.newBuilder();
103+
ProtoJson.parser()
104+
.ignoringUnknownFields()
105+
.merge(
106+
GSON.get().toJson(entry.getBodyDecoded().getSpec().getAsJsonObject().get("dsseV002")),
107+
builder);
108+
return builder.build();
109+
} catch (InvalidProtocolBufferException | NullPointerException | IllegalStateException e) {
110+
throw new RekorTypeException("Could not parse dsse:0.0.2", e);
111+
}
112+
}
113+
61114
private static void expect(RekorEntry entry, String expectedKind, String expectedApiVersion)
62115
throws RekorTypeException {
63116
var kind = entry.getBodyDecoded().getKind();

sigstore-java/src/test/java/dev/sigstore/KeylessVerifierTest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import dev.sigstore.bundle.Bundle;
2424
import dev.sigstore.bundle.ImmutableBundle;
2525
import dev.sigstore.encryption.signers.Signers;
26+
import dev.sigstore.rekor.client.RekorTypeException;
2627
import dev.sigstore.rekor.client.RekorVerificationException;
2728
import dev.sigstore.strings.StringMatcher;
2829
import dev.sigstore.testing.CertGenerator;
@@ -271,6 +272,8 @@ public void testVerify_mismatchedCertificate_rekorV2() throws Exception {
271272
VerificationOptions.empty()));
272273
Assertions.assertEquals(
273274
"Could not parse hashedrekord from log entry body", thrown.getMessage());
275+
Assertions.assertTrue(thrown.getCause() instanceof RekorTypeException);
276+
Assertions.assertEquals("Could not parse hashedrekord:0.0.2", thrown.getCause().getMessage());
274277
}
275278

276279
@Test

sigstore-java/src/test/java/dev/sigstore/rekor/client/RekorClientHttpTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ public void searchEntries_oneResult_publicKey() throws Exception {
110110
null,
111111
null,
112112
"x509",
113-
RekorTypes.getHashedRekord(resp.getEntry())
113+
RekorTypes.getHashedRekordV001(resp.getEntry())
114114
.getSignature()
115115
.getPublicKey()
116116
.getContent())

sigstore-java/src/test/java/dev/sigstore/rekor/client/RekorTypesTest.java

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,28 +23,54 @@
2323

2424
public class RekorTypesTest {
2525

26-
private RekorEntry fromResource(String path) throws Exception {
26+
private RekorEntry fromResourceV001(String path) throws Exception {
2727
var rekorResponse = Resources.toString(Resources.getResource(path), StandardCharsets.UTF_8);
2828

2929
return RekorResponse.newRekorResponse(new URI("https://not.used.com"), rekorResponse)
3030
.getEntry();
3131
}
3232

33+
private RekorEntry fromResourceV002(String path) throws Exception {
34+
var tle = Resources.toString(Resources.getResource(path), StandardCharsets.UTF_8);
35+
36+
return RekorEntry.fromTLogEntryJson(tle);
37+
}
38+
39+
@Test
40+
public void getHashedRekordV001_pass() throws Exception {
41+
var entry = fromResourceV001("dev/sigstore/samples/rekor-response/valid/entry.json");
42+
43+
var hashedRekord = RekorTypes.getHashedRekordV001(entry);
44+
Assertions.assertNotNull(hashedRekord);
45+
}
46+
3347
@Test
34-
public void getHashedRekord_pass() throws Exception {
35-
var entry = fromResource("dev/sigstore/samples/rekor-response/valid/entry.json");
48+
public void getHashedRekordV002_pass() throws Exception {
49+
var entry = fromResourceV002("dev/sigstore/samples/rekor-response/valid/entry-v2.json");
3650

37-
var hashedRekord = RekorTypes.getHashedRekord(entry);
51+
var hashedRekord = RekorTypes.getHashedRekordV002(entry);
3852
Assertions.assertNotNull(hashedRekord);
3953
}
4054

4155
@Test
42-
public void getHashedRekord_badType() throws Exception {
43-
var entry = fromResource("dev/sigstore/samples/rekor-response/valid/jar-entry.json");
56+
public void getHashedRekordV001_badType() throws Exception {
57+
var entry = fromResourceV001("dev/sigstore/samples/rekor-response/valid/jar-entry.json");
4458

4559
var exception =
46-
Assertions.assertThrows(RekorTypeException.class, () -> RekorTypes.getHashedRekord(entry));
60+
Assertions.assertThrows(
61+
RekorTypeException.class, () -> RekorTypes.getHashedRekordV001(entry));
4762
Assertions.assertEquals(
4863
"Expecting type hashedrekord:0.0.1, but found jar:0.0.1", exception.getMessage());
4964
}
65+
66+
@Test
67+
public void getHashedRekordV002_badType() throws Exception {
68+
var entry = fromResourceV002("dev/sigstore/samples/rekor-response/valid/jar-entry-v2.json");
69+
70+
var exception =
71+
Assertions.assertThrows(
72+
RekorTypeException.class, () -> RekorTypes.getHashedRekordV002(entry));
73+
Assertions.assertEquals(
74+
"Expecting type hashedrekord:0.0.2, but found jar:0.0.2", exception.getMessage());
75+
}
5076
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"logIndex": "743",
3+
"logId": {
4+
"keyId": "8w1amZ2S5mJIQkQmPxdMuOrL/oJkvFg9MnQXmeOCXck="
5+
},
6+
"kindVersion": {
7+
"kind": "jar",
8+
"version": "0.0.2"
9+
},
10+
"inclusionProof": {
11+
"logIndex": "743",
12+
"rootHash": "esdDSd9WE37oIvN7WDlJVKtt/QajruODJO7PVEwwTXs=",
13+
"treeSize": "744",
14+
"hashes": [
15+
"yBZXhlQSPuOSafAsvOnchoFkE4MDWvNF6dUSk9D5aRA=",
16+
"PtwLKIOKsSaNPEOf1mwqL5+x2p0eacowpueVXhsChWg=",
17+
"MMTssW4XXsO1QXFJ9gBI8tWD03ySifDU5wkYwpz1rKE=",
18+
"rPkfqzM2orzgdbRk88RBPZMBlUUu1QEqMkY+f1X846g=",
19+
"+gnK+M5cyTZ0UncCImJch9APOM+yjuVvfEuX7z6AamQ=",
20+
"QMesRTEZdIgthOEinYE/9J7wGv+VmArDZTICj9POmhY=",
21+
"UNUMG62rMwoqCqFKknh4R5Ubkf5Z6dj+Pk0m/1xu8uo="
22+
],
23+
"checkpoint": {
24+
"envelope": "log2025-alpha1.rekor.sigstage.dev\n744\nesdDSd9WE37oIvN7WDlJVKtt/QajruODJO7PVEwwTXs=\n\n— log2025-alpha1.rekor.sigstage.dev 8w1amdUe0s4o19zD+N8ffKDR3+mDCYIBCOX+O8gqThpWp6Rq/07hW+UpMbOdY2i6skEjvY71RebKMx2jt+Hq9JRpJAs=\n"
25+
}
26+
},
27+
"canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjIiLCJraW5kIjoiamFyIiwic3BlYyI6eyJoYXNoZWRSZWtvcmRWMDAyIjp7ImRhdGEiOnsiYWxnb3JpdGhtIjoiU0hBMl8yNTYiLCJkaWdlc3QiOiJMazFHb1VnZWduVHBTQ0tsd2doYUpjMDQycEFVcVdhZzRuQldhUG5SUDRNPSJ9LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FUUNJQVdDTG93SC9senhLZ20xcGNybmg0emFIYmtkMHdLNHN1UnlUdTZYbDBXbEFpQXduSE5VOTJZbHJzbzB0dVdkbmYyZTNSaVY0QW95NFlHNUJxbXhvNGxBWXc9PSIsInZlcmlmaWVyIjp7ImtleURldGFpbHMiOiJQS0lYX0VDRFNBX1AyNTZfU0hBXzI1NiIsIng1MDlDZXJ0aWZpY2F0ZSI6eyJyYXdCeXRlcyI6Ik1JSUNkRENDQWhxZ0F3SUJBZ0lHQVpkazdaZXNNQW9HQ0NxR1NNNDlCQU1DTUNveERUQUxCZ05WQkFNTUJIUmxjM1F4R1RBWEJnTlZCQW9NRUhSbGMzUWdZMlZ5ZEdsbWFXTmhkR1V3SGhjTk1qVXdOakV5TVRZeE5qSXhXaGNOTWpVd05qRXlNVFl6TmpJeFdqQXFNUTB3Q3dZRFZRUUREQVIwWlhOME1Sa3dGd1lEVlFRS0RCQjBaWE4wSUdObGNuUnBabWxqWVhSbE1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTg2WHQ1ZzMyRVdMVkdLcFNXV0xlQUE5bHJ2S1FTVm1RNHZIaHhxKytuMWRjMWI1UG5MZTU0TU9KTjNNQ2hYZkFUTEUwejJ5djlTck9HUjQrL01QRmlxT0NBU293Z2dFbU1CMEdBMVVkRGdRV0JCVHI1ZHJjdnNqRTg1ZW9iTFh4aFZnNUtUanRnakFmQmdOVkhTTUVHREFXZ0JRTEZIZmJqL3EzRHdvVjV5a08yeWhPZ290NkVUQU9CZ05WSFE4QkFmOEVCQU1DQjRBd0V3WURWUjBsQkF3d0NnWUlLd1lCQlFVSEF3TXdEQVlEVlIwVEFRSC9CQUl3QURBYkJnTlZIUkVCQWY4RUVUQVBnUTEwWlhOMFFIUmxjM1F1WTI5dE1Dc0dDaXNHQVFRQmc3OHdBUUVFSFdoMGRIQnpPaTh2Wm1GclpXRmpZMjkxYm5SekxuUmxjM1F1WTI5dE1DMEdDaXNHQVFRQmc3OHdBUWdFSHd3ZGFIUjBjSE02THk5bVlXdGxZV05qYjNWdWRITXVkR1Z6ZEM1amIyMHdHQVlLS3dZQkJBR0dqUjhxS2dRS2RHVnpkQ0IyWVd4MVpUQWVCZ29yQmdFRUFZYU5IeW9yQkJBTURuUmxjM1FnZG1Gc2RXVWdaR1Z5TUFvR0NDcUdTTTQ5QkFNQ0EwZ0FNRVVDSUV2MUN4aVhHWXJTNTBtMmRSU0x3QW9mUGx3b05KQnBjeWlXZUJrSlhYeDFBaUVBbSsvZGxUaVNXV2hvOHpOT0plcHNEMWJiNTFPUWN6S2RJQjRUeUxHRWJVQT0ifX19fX19"
28+
}

0 commit comments

Comments
 (0)