Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 21 additions & 7 deletions fuzzing/src/main/java/fuzzing/RekorTypesFuzzer.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,30 @@ public class RekorTypesFuzzer {

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

URI uri = new URI(URL);
RekorEntry entry = RekorResponse.newRekorResponse(uri, string).getEntry();

if (type == 0) {
RekorTypes.getHashedRekord(entry);
RekorEntry entry;
if (type < 2) {
URI uri = new URI(URL);
entry = RekorResponse.newRekorResponse(uri, string).getEntry();
} else {
RekorTypes.getDsse(entry);
entry = RekorEntry.fromTLogEntryJson(string);
}

switch (type) {
case 0:
RekorTypes.getHashedRekordV001(entry);
break;
case 1:
RekorTypes.getDsseV001(entry);
break;
case 2:
RekorTypes.getHashedRekordV002(entry);
break;
case 3:
RekorTypes.getDsseV002(entry);
break;
}
} catch (URISyntaxException | RekorTypeException | RekorParseException e) {
// Known exception
Expand Down
45 changes: 13 additions & 32 deletions sigstore-java/src/main/java/dev/sigstore/KeylessVerifier.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.hash.Hashing;
import com.google.common.io.Files;
import com.google.gson.Gson;
import com.google.protobuf.InvalidProtocolBufferException;
import dev.sigstore.VerificationOptions.CertificateMatcher;
import dev.sigstore.VerificationOptions.UncheckedCertificateException;
import dev.sigstore.bundle.Bundle;
Expand All @@ -31,7 +29,6 @@
import dev.sigstore.encryption.signers.Verifiers;
import dev.sigstore.fulcio.client.FulcioVerificationException;
import dev.sigstore.fulcio.client.FulcioVerifier;
import dev.sigstore.json.ProtoJson;
import dev.sigstore.proto.common.v1.HashAlgorithm;
import dev.sigstore.proto.rekor.v2.DSSELogEntryV002;
import dev.sigstore.proto.rekor.v2.HashedRekordLogEntryV002;
Expand Down Expand Up @@ -71,7 +68,9 @@
import org.bouncycastle.util.encoders.DecoderException;
import org.bouncycastle.util.encoders.Hex;

/** Verify hashrekords from rekor signed using the keyless signing flow with fulcio certificates. */
/**
* Verify hashedrekords from rekor signed using the keyless signing flow with fulcio certificates.
*/
public class KeylessVerifier {

private final FulcioVerifier fulcioVerifier;
Expand Down Expand Up @@ -271,7 +270,7 @@ private void checkMessageSignature(
String version = rekorEntry.getBodyDecoded().getApiVersion();
if ("0.0.1".equals(version)) {
try {
RekorTypes.getHashedRekord(rekorEntry);
RekorTypes.getHashedRekordV001(rekorEntry);
var calculatedHashedRekord =
HashedRekordRequest.newHashedRekordRequest(
artifactDigest, Certificates.toPemBytes(leafCert), signature)
Expand All @@ -291,21 +290,10 @@ private void checkMessageSignature(
} else if ("0.0.2".equals(version)) {
HashedRekordLogEntryV002 logEntrySpec;
try {
HashedRekordLogEntryV002.Builder builder = HashedRekordLogEntryV002.newBuilder();
ProtoJson.parser()
.ignoringUnknownFields()
.merge(
new Gson()
.toJson(
rekorEntry
.getBodyDecoded()
.getSpec()
.getAsJsonObject()
.get("hashedRekordV002")),
builder);
logEntrySpec = builder.build();
} catch (InvalidProtocolBufferException ipbe) {
throw new KeylessVerificationException("Could not parse hashedrekord from log entry body");
logEntrySpec = RekorTypes.getHashedRekordV002(rekorEntry);
} catch (RekorTypeException re) {
throw new KeylessVerificationException(
"Could not parse hashedrekord from log entry body", re);
}

if (!logEntrySpec.getData().getAlgorithm().equals(HashAlgorithm.SHA2_256)) {
Expand Down Expand Up @@ -408,7 +396,7 @@ private void checkDsseEnvelope(
if ("0.0.1".equals(version)) {
Dsse rekorDsse;
try {
rekorDsse = RekorTypes.getDsse(rekorEntry);
rekorDsse = RekorTypes.getDsseV001(rekorEntry);
} catch (RekorTypeException re) {
throw new KeylessVerificationException("Unexpected rekor type", re);
}
Expand All @@ -425,7 +413,7 @@ private void checkDsseEnvelope(
payloadDigest = Hex.decode(rekorDsse.getPayloadHash().getValue());
} catch (DecoderException de) {
throw new KeylessVerificationException(
"Could not decode hex sha256 artifact hash in hashrekord", de);
"Could not decode hex sha256 artifact hash in hashedrekord", de);
}

byte[] calculatedDigest = Hashing.sha256().hashBytes(dsseEnvelope.getPayload()).asBytes();
Expand All @@ -450,16 +438,9 @@ private void checkDsseEnvelope(
} else if ("0.0.2".equals(version)) {
DSSELogEntryV002 logEntrySpec;
try {
DSSELogEntryV002.Builder builder = DSSELogEntryV002.newBuilder();
ProtoJson.parser()
.merge(
new Gson()
.toJson(
rekorEntry.getBodyDecoded().getSpec().getAsJsonObject().get("dsseV002")),
builder);
logEntrySpec = builder.build();
} catch (InvalidProtocolBufferException ipbe) {
throw new KeylessVerificationException("Could not parse DSSE from log entry body", ipbe);
logEntrySpec = RekorTypes.getDsseV002(rekorEntry);
} catch (RekorTypeException re) {
throw new KeylessVerificationException("Could not parse DSSE from log entry body", re);
}

if (!logEntrySpec.getPayloadHash().getAlgorithm().equals(HashAlgorithm.SHA2_256)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
import dev.sigstore.KeylessVerificationException;
import dev.sigstore.TrustedRootProvider;
import dev.sigstore.encryption.certificates.Certificates;
import dev.sigstore.trustroot.*;
import dev.sigstore.trustroot.Service;
import dev.sigstore.trustroot.SigstoreConfigurationException;
import dev.sigstore.trustroot.TransparencyLog;
import dev.sigstore.tuf.SigstoreTufClient;
import java.io.IOException;
import java.nio.file.Path;
Expand Down Expand Up @@ -81,7 +83,7 @@ public RekorEntry getEntryFromRekor(
artifactDigest, Certificates.toPemBytes(leafCert), signature);
} catch (IOException e) {
throw new KeylessVerificationException(
"Could not convert certificate to PEM when recreating hashrekord", e);
"Could not convert certificate to PEM when recreating hashedrekord", e);
}
Optional<RekorEntry> rekorEntry;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
import static dev.sigstore.json.GsonSupplier.GSON;

import com.google.gson.JsonParseException;
import com.google.protobuf.InvalidProtocolBufferException;
import dev.sigstore.json.ProtoJson;
import dev.sigstore.proto.rekor.v2.DSSELogEntryV002;
import dev.sigstore.proto.rekor.v2.HashedRekordLogEntryV002;
import dev.sigstore.rekor.dsse.v0_0_1.Dsse;
import dev.sigstore.rekor.hashedRekord.v0_0_1.HashedRekord;

Expand All @@ -29,15 +33,41 @@ public class RekorTypes {
*
* @param entry the rekor entry obtained from rekor
* @return the parsed pojo
* @throws RekorTypeException if the hashrekord:0.0.1 entry could not be parsed
* @throws RekorTypeException if the hashedrekord:0.0.1 entry could not be parsed
*/
public static HashedRekord getHashedRekord(RekorEntry entry) throws RekorTypeException {
public static HashedRekord getHashedRekordV001(RekorEntry entry) throws RekorTypeException {
expect(entry, "hashedrekord", "0.0.1");

try {
return GSON.get().fromJson(entry.getBodyDecoded().getSpec(), HashedRekord.class);
} catch (JsonParseException jpe) {
throw new RekorTypeException("Could not parse hashrekord:0.0.1", jpe);
throw new RekorTypeException("Could not parse hashedrekord:0.0.1", jpe);
}
}

/**
* Parse a hashedrekord from rekor at api version 0.0.2.
*
* @param entry the rekor entry obtained from rekor
* @return the parsed proto
* @throws RekorTypeException if the hashedrekord:0.0.2 entry could not be parsed
*/
public static HashedRekordLogEntryV002 getHashedRekordV002(RekorEntry entry)
throws RekorTypeException {
expect(entry, "hashedrekord", "0.0.2");

try {
HashedRekordLogEntryV002.Builder builder = HashedRekordLogEntryV002.newBuilder();
ProtoJson.parser()
.ignoringUnknownFields()
.merge(
GSON.get()
.toJson(
entry.getBodyDecoded().getSpec().getAsJsonObject().get("hashedRekordV002")),
builder);
return builder.build();
} catch (InvalidProtocolBufferException | NullPointerException | IllegalStateException e) {
throw new RekorTypeException("Could not parse hashedrekord:0.0.2", e);
}
}

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

try {
Expand All @@ -58,6 +88,29 @@ public static Dsse getDsse(RekorEntry entry) throws RekorTypeException {
}
}

/**
* Parse a dsse from rekor at api version 0.0.2.
*
* @param entry the rekor entry obtained from rekor
* @return the parsed proto
* @throws RekorTypeException if the dsse:0.0.2 entry could not be parsed
*/
public static DSSELogEntryV002 getDsseV002(RekorEntry entry) throws RekorTypeException {
expect(entry, "dsse", "0.0.2");

try {
DSSELogEntryV002.Builder builder = DSSELogEntryV002.newBuilder();
ProtoJson.parser()
.ignoringUnknownFields()
.merge(
GSON.get().toJson(entry.getBodyDecoded().getSpec().getAsJsonObject().get("dsseV002")),
builder);
return builder.build();
} catch (InvalidProtocolBufferException | NullPointerException | IllegalStateException e) {
throw new RekorTypeException("Could not parse dsse:0.0.2", e);
}
}

private static void expect(RekorEntry entry, String expectedKind, String expectedApiVersion)
throws RekorTypeException {
var kind = entry.getBodyDecoded().getKind();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import dev.sigstore.bundle.Bundle;
import dev.sigstore.bundle.ImmutableBundle;
import dev.sigstore.encryption.signers.Signers;
import dev.sigstore.rekor.client.RekorTypeException;
import dev.sigstore.rekor.client.RekorVerificationException;
import dev.sigstore.strings.StringMatcher;
import dev.sigstore.testing.CertGenerator;
Expand Down Expand Up @@ -271,6 +272,8 @@ public void testVerify_mismatchedCertificate_rekorV2() throws Exception {
VerificationOptions.empty()));
Assertions.assertEquals(
"Could not parse hashedrekord from log entry body", thrown.getMessage());
Assertions.assertTrue(thrown.getCause() instanceof RekorTypeException);
Assertions.assertEquals("Could not parse hashedrekord:0.0.2", thrown.getCause().getMessage());
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ public void searchEntries_oneResult_publicKey() throws Exception {
null,
null,
"x509",
RekorTypes.getHashedRekord(resp.getEntry())
RekorTypes.getHashedRekordV001(resp.getEntry())
.getSignature()
.getPublicKey()
.getContent())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,28 +23,54 @@

public class RekorTypesTest {

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

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

private RekorEntry fromResourceV002(String path) throws Exception {
var tle = Resources.toString(Resources.getResource(path), StandardCharsets.UTF_8);

return RekorEntry.fromTLogEntryJson(tle);
}

@Test
public void getHashedRekordV001_pass() throws Exception {
var entry = fromResourceV001("dev/sigstore/samples/rekor-response/valid/entry.json");

var hashedRekord = RekorTypes.getHashedRekordV001(entry);
Assertions.assertNotNull(hashedRekord);
}

@Test
public void getHashedRekord_pass() throws Exception {
var entry = fromResource("dev/sigstore/samples/rekor-response/valid/entry.json");
public void getHashedRekordV002_pass() throws Exception {
var entry = fromResourceV002("dev/sigstore/samples/rekor-response/valid/entry-v2.json");

var hashedRekord = RekorTypes.getHashedRekord(entry);
var hashedRekord = RekorTypes.getHashedRekordV002(entry);
Assertions.assertNotNull(hashedRekord);
}

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

var exception =
Assertions.assertThrows(RekorTypeException.class, () -> RekorTypes.getHashedRekord(entry));
Assertions.assertThrows(
RekorTypeException.class, () -> RekorTypes.getHashedRekordV001(entry));
Assertions.assertEquals(
"Expecting type hashedrekord:0.0.1, but found jar:0.0.1", exception.getMessage());
}

@Test
public void getHashedRekordV002_badType() throws Exception {
var entry = fromResourceV002("dev/sigstore/samples/rekor-response/valid/jar-entry-v2.json");

var exception =
Assertions.assertThrows(
RekorTypeException.class, () -> RekorTypes.getHashedRekordV002(entry));
Assertions.assertEquals(
"Expecting type hashedrekord:0.0.2, but found jar:0.0.2", exception.getMessage());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"logIndex": "743",
"logId": {
"keyId": "8w1amZ2S5mJIQkQmPxdMuOrL/oJkvFg9MnQXmeOCXck="
},
"kindVersion": {
"kind": "jar",
"version": "0.0.2"
},
"inclusionProof": {
"logIndex": "743",
"rootHash": "esdDSd9WE37oIvN7WDlJVKtt/QajruODJO7PVEwwTXs=",
"treeSize": "744",
"hashes": [
"yBZXhlQSPuOSafAsvOnchoFkE4MDWvNF6dUSk9D5aRA=",
"PtwLKIOKsSaNPEOf1mwqL5+x2p0eacowpueVXhsChWg=",
"MMTssW4XXsO1QXFJ9gBI8tWD03ySifDU5wkYwpz1rKE=",
"rPkfqzM2orzgdbRk88RBPZMBlUUu1QEqMkY+f1X846g=",
"+gnK+M5cyTZ0UncCImJch9APOM+yjuVvfEuX7z6AamQ=",
"QMesRTEZdIgthOEinYE/9J7wGv+VmArDZTICj9POmhY=",
"UNUMG62rMwoqCqFKknh4R5Ubkf5Z6dj+Pk0m/1xu8uo="
],
"checkpoint": {
"envelope": "log2025-alpha1.rekor.sigstage.dev\n744\nesdDSd9WE37oIvN7WDlJVKtt/QajruODJO7PVEwwTXs=\n\n— log2025-alpha1.rekor.sigstage.dev 8w1amdUe0s4o19zD+N8ffKDR3+mDCYIBCOX+O8gqThpWp6Rq/07hW+UpMbOdY2i6skEjvY71RebKMx2jt+Hq9JRpJAs=\n"
}
},
"canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjIiLCJraW5kIjoiamFyIiwic3BlYyI6eyJoYXNoZWRSZWtvcmRWMDAyIjp7ImRhdGEiOnsiYWxnb3JpdGhtIjoiU0hBMl8yNTYiLCJkaWdlc3QiOiJMazFHb1VnZWduVHBTQ0tsd2doYUpjMDQycEFVcVdhZzRuQldhUG5SUDRNPSJ9LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FUUNJQVdDTG93SC9senhLZ20xcGNybmg0emFIYmtkMHdLNHN1UnlUdTZYbDBXbEFpQXduSE5VOTJZbHJzbzB0dVdkbmYyZTNSaVY0QW95NFlHNUJxbXhvNGxBWXc9PSIsInZlcmlmaWVyIjp7ImtleURldGFpbHMiOiJQS0lYX0VDRFNBX1AyNTZfU0hBXzI1NiIsIng1MDlDZXJ0aWZpY2F0ZSI6eyJyYXdCeXRlcyI6Ik1JSUNkRENDQWhxZ0F3SUJBZ0lHQVpkazdaZXNNQW9HQ0NxR1NNNDlCQU1DTUNveERUQUxCZ05WQkFNTUJIUmxjM1F4R1RBWEJnTlZCQW9NRUhSbGMzUWdZMlZ5ZEdsbWFXTmhkR1V3SGhjTk1qVXdOakV5TVRZeE5qSXhXaGNOTWpVd05qRXlNVFl6TmpJeFdqQXFNUTB3Q3dZRFZRUUREQVIwWlhOME1Sa3dGd1lEVlFRS0RCQjBaWE4wSUdObGNuUnBabWxqWVhSbE1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTg2WHQ1ZzMyRVdMVkdLcFNXV0xlQUE5bHJ2S1FTVm1RNHZIaHhxKytuMWRjMWI1UG5MZTU0TU9KTjNNQ2hYZkFUTEUwejJ5djlTck9HUjQrL01QRmlxT0NBU293Z2dFbU1CMEdBMVVkRGdRV0JCVHI1ZHJjdnNqRTg1ZW9iTFh4aFZnNUtUanRnakFmQmdOVkhTTUVHREFXZ0JRTEZIZmJqL3EzRHdvVjV5a08yeWhPZ290NkVUQU9CZ05WSFE4QkFmOEVCQU1DQjRBd0V3WURWUjBsQkF3d0NnWUlLd1lCQlFVSEF3TXdEQVlEVlIwVEFRSC9CQUl3QURBYkJnTlZIUkVCQWY4RUVUQVBnUTEwWlhOMFFIUmxjM1F1WTI5dE1Dc0dDaXNHQVFRQmc3OHdBUUVFSFdoMGRIQnpPaTh2Wm1GclpXRmpZMjkxYm5SekxuUmxjM1F1WTI5dE1DMEdDaXNHQVFRQmc3OHdBUWdFSHd3ZGFIUjBjSE02THk5bVlXdGxZV05qYjNWdWRITXVkR1Z6ZEM1amIyMHdHQVlLS3dZQkJBR0dqUjhxS2dRS2RHVnpkQ0IyWVd4MVpUQWVCZ29yQmdFRUFZYU5IeW9yQkJBTURuUmxjM1FnZG1Gc2RXVWdaR1Z5TUFvR0NDcUdTTTQ5QkFNQ0EwZ0FNRVVDSUV2MUN4aVhHWXJTNTBtMmRSU0x3QW9mUGx3b05KQnBjeWlXZUJrSlhYeDFBaUVBbSsvZGxUaVNXV2hvOHpOT0plcHNEMWJiNTFPUWN6S2RJQjRUeUxHRWJVQT0ifX19fX19"
}
Loading