Skip to content

Commit

Permalink
Update protobuf-spec to 0.2.1
Browse files Browse the repository at this point in the history
- We now publish only v0.2 bundle specs (inclusion proof required)
- v0.1 bundles can still be read (for now) but the behavior isn't
  super well defined
- Message digest is optional in the bundle

Signed-off-by: Appu Goundan <[email protected]>
  • Loading branch information
loosebazooka committed Oct 11, 2023
1 parent c16c808 commit 3d1cb32
Show file tree
Hide file tree
Showing 11 changed files with 116 additions and 42 deletions.
2 changes: 1 addition & 1 deletion sigstore-java/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ dependencies {

implementation("io.github.erdtman:java-json-canonicalization:1.1")

implementation("dev.sigstore:protobuf-specs:0.1.0") {
implementation("dev.sigstore:protobuf-specs:0.2.1") {
because("It generates Sigstore Bundle file")
}
implementation(platform("com.google.protobuf:protobuf-bom:3.24.3"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@

@Value.Immutable
public interface KeylessSignature {
/** The sha256 hash digest of the artifact */
/**
* The sha256 hash digest of the artifact, this may be empty and should be treated as not present
* in that case.
*/
byte[] getDigest();

/**
Expand Down
17 changes: 10 additions & 7 deletions sigstore-java/src/main/java/dev/sigstore/KeylessVerifier.java
Original file line number Diff line number Diff line change
Expand Up @@ -146,13 +146,16 @@ public void verify(byte[] artifactDigest, KeylessVerificationRequest request)

// this ensures the provided artifact digest matches what may have come from a bundle (in
// keyless signature)
if (!Arrays.equals(artifactDigest, request.getKeylessSignature().getDigest())) {
throw new KeylessVerificationException(
"Provided artifact sha256 digest does not match digest used for verification"
+ "\nprovided(hex) : "
+ Hex.toHexString(artifactDigest)
+ "\nverification : "
+ Hex.toHexString(request.getKeylessSignature().getDigest()));
var digest = request.getKeylessSignature().getDigest();
if (digest.length > 0) {
if (!Arrays.equals(artifactDigest, digest)) {
throw new KeylessVerificationException(
"Provided artifact sha256 digest does not match digest used for verification"
+ "\nprovided(hex) : "
+ Hex.toHexString(artifactDigest)
+ "\nverification : "
+ Hex.toHexString(digest));
}
}

// verify the certificate chains up to a trusted root (fulcio) and contains a valid SCT from
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.bouncycastle.util.encoders.Hex;

Expand All @@ -68,8 +67,12 @@ class BundleFactoryInternal {
* @return Sigstore Bundle in protobuf builder format
*/
static Bundle.Builder createBundleBuilder(KeylessSignature signingResult) {
if (signingResult.getDigest().length == 0) {
throw new IllegalStateException(
"keyless signature must have artifact digest when serializing to bundle");
}
return Bundle.newBuilder()
.setMediaType("application/vnd.dev.sigstore.bundle+json;version=0.1")
.setMediaType("application/vnd.dev.sigstore.bundle+json;version=0.2")
.setVerificationMaterial(buildVerificationMaterial(signingResult))
.setMessageSignature(
MessageSignature.newBuilder()
Expand Down Expand Up @@ -157,35 +160,47 @@ static KeylessSignature readBundle(Reader jsonReader) throws BundleParseExceptio
}
Bundle bundle = bundleBuilder.build();

// TODO: only allow v0.2 bundles at some point, we will only be producing v0.2 bundles
// TODO: in our GA release.
// var supportedMediaType = "application/vnd.dev.sigstore.bundle+json;version=0.2";
// if (!supportedMediaType.equals(bundle.getMediaType())) {
// throw new BundleParseException(
// "Unsupported media type '"
// + bundle.getMediaType()
// + "', only '"
// + supportedMediaType
// + "' is supported");
// }

if (bundle.getVerificationMaterial().getTlogEntriesCount() == 0) {
throw new BundleParseException("Could not find any tlog entries in bundle json");
}
var bundleEntry = bundle.getVerificationMaterial().getTlogEntries(0);
if (!bundleEntry.hasInclusionProof()) {
throw new BundleParseException("Could not find an inclusion proof");
}
var bundleInclusionProof = bundleEntry.getInclusionProof();

ImmutableInclusionProof inclusionProof = null;
if (bundleEntry.hasInclusionProof()) {
inclusionProof =
ImmutableInclusionProof.builder()
.logIndex(bundleInclusionProof.getLogIndex())
.rootHash(Hex.toHexString(bundleInclusionProof.getRootHash().toByteArray()))
.treeSize(bundleInclusionProof.getTreeSize())
.checkpoint(bundleInclusionProof.getCheckpoint().getEnvelope())
.addAllHashes(
bundleInclusionProof.getHashesList().stream()
.map(ByteString::toByteArray)
.map(Hex::toHexString)
.collect(Collectors.toList()))
.build();
}
ImmutableInclusionProof inclusionProof =
ImmutableInclusionProof.builder()
.logIndex(bundleInclusionProof.getLogIndex())
.rootHash(Hex.toHexString(bundleInclusionProof.getRootHash().toByteArray()))
.treeSize(bundleInclusionProof.getTreeSize())
.checkpoint(bundleInclusionProof.getCheckpoint().getEnvelope())
.addAllHashes(
bundleInclusionProof.getHashesList().stream()
.map(ByteString::toByteArray)
.map(Hex::toHexString)
.collect(Collectors.toList()))
.build();

var verification =
ImmutableVerification.builder()
.signedEntryTimestamp(
Base64.getEncoder()
.encodeToString(
bundleEntry.getInclusionPromise().getSignedEntryTimestamp().toByteArray()))
.inclusionProof(Optional.ofNullable(inclusionProof))
.inclusionProof(inclusionProof)
.build();

var rekorEntry =
Expand All @@ -199,18 +214,23 @@ static KeylessSignature readBundle(Reader jsonReader) throws BundleParseExceptio
.verification(verification)
.build();

var hashAlgorithm = bundle.getMessageSignature().getMessageDigest().getAlgorithm();
if (hashAlgorithm != HashAlgorithm.SHA2_256) {
throw new BundleParseException(
"Cannot read message digests of type "
+ hashAlgorithm
+ ", only "
+ HashAlgorithm.SHA2_256
+ " is supported");
var digest = new byte[] {};
if (bundle.getMessageSignature().hasMessageDigest()) {
var hashAlgorithm = bundle.getMessageSignature().getMessageDigest().getAlgorithm();
if (hashAlgorithm != HashAlgorithm.SHA2_256) {
throw new BundleParseException(
"Cannot read message digests of type "
+ hashAlgorithm
+ ", only "
+ HashAlgorithm.SHA2_256
+ " is supported");
}
digest = bundle.getMessageSignature().getMessageDigest().getDigest().toByteArray();
}

try {
return KeylessSignature.builder()
.digest(bundle.getMessageSignature().getMessageDigest().getDigest().toByteArray())
.digest(digest)
.certPath(
toCertPath(
bundle.getVerificationMaterial().getX509CertificateChain().getCertificatesList()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,13 @@ private static String subMessagePrefix(
}

static boolean isRequired(Descriptors.FieldDescriptor field) {
// while this isn't configured into the spec, we do not support rfc3161 timestamps in java yet,
// so make SETs from rekor required in code here
if (field
.getFullName()
.equals("dev.sigstore.rekor.v1.TransparencyLogEntry.inclusion_promise")) {
return true;
}
return field.isRequired()
|| field
.toProto()
Expand Down
4 changes: 1 addition & 3 deletions sigstore-java/src/test/java/dev/sigstore/KeylessTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ public static void setupArtifact() throws IOException {
}

@Test
@SuppressWarnings("deprecation")
@EnabledIfOidcExists(provider = OidcProviderType.ANY)
public void sign_production() throws Exception {
var signer = KeylessSigner.builder().sigstorePublicDefaults().build();
Expand All @@ -78,7 +77,6 @@ public void sign_production() throws Exception {
}

@Test
@SuppressWarnings("deprecation")
@EnabledIfOidcExists(provider = OidcProviderType.ANY)
public void sign_staging() throws Exception {
var signer = KeylessSigner.builder().sigstoreStagingDefaults().build();
Expand Down Expand Up @@ -108,7 +106,7 @@ private void verifySigningResult(List<KeylessSignature> results)
Assertions.assertNotNull(result.getSignature());

var hr = RekorTypes.getHashedRekord(result.getEntry().get());
// check if ht rekor entry has the digest we sent
// check if the rekor entry has the digest we sent
Assertions.assertArrayEquals(artifactDigest, result.getDigest());
// check if the rekor entry has the signature we sent
Assertions.assertArrayEquals(
Expand Down
18 changes: 18 additions & 0 deletions sigstore-java/src/test/java/dev/sigstore/KeylessVerifierTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,24 @@ public void testVerify(boolean isOnline) throws Exception {
verifier.verify(Path.of(artifact), verificationReq);
}

@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testVerify_noDigestInBundle(boolean isOnline) throws Exception {
var bundleFile =
Resources.toString(
Resources.getResource("dev/sigstore/samples/bundles/bundle-no-digest.sigstore"),
StandardCharsets.UTF_8);
var artifact = Resources.getResource("dev/sigstore/samples/bundles/artifact.txt").getPath();

var verifier = KeylessVerifier.builder().sigstorePublicDefaults().build();
var verificationReq =
KeylessVerificationRequest.builder()
.keylessSignature(BundleFactory.readBundle(new StringReader(bundleFile)))
.verificationOptions(VerificationOptions.builder().isOnline(isOnline).build())
.build();
verifier.verify(Path.of(artifact), verificationReq);
}

@ParameterizedTest
@ValueSource(booleans = {true, false})
public void testVerify_mismatchedSet(boolean isOnline) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,38 @@ void allRequiredFieldsInBundle() {
+ "dev.sigstore.common.v1.LogId\n"
+ " key_id\n"
+ "\n"
+ "dev.sigstore.rekor.v1.KindVersion\n"
+ " kind\n"
+ " version\n"
+ "\n"
+ "dev.sigstore.rekor.v1.InclusionPromise\n"
+ " signed_entry_timestamp\n"
+ "\n"
+ "dev.sigstore.rekor.v1.Checkpoint\n"
+ " envelope\n"
+ "\n"
+ "dev.sigstore.rekor.v1.InclusionProof\n"
+ " log_index\n"
+ " root_hash\n"
+ " tree_size\n"
+ " hashes\n"
+ " checkpoint\n"
+ "\n"
+ "dev.sigstore.rekor.v1.TransparencyLogEntry\n"
+ " log_index\n"
+ " log_id\n"
+ " kind_version\n"
+ " integrated_time\n"
+ " inclusion_promise\n"
+ " inclusion_proof\n"
+ "\n"
+ "dev.sigstore.common.v1.RFC3161SignedTimestamp\n"
+ " signed_timestamp\n"
+ "\n"
+ "dev.sigstore.bundle.v1.VerificationMaterial\n"
+ " content\n"
+ "\n"
+ "dev.sigstore.common.v1.MessageSignature\n"
+ " message_digest\n"
+ " signature\n"
+ "\n"
+ "dev.sigstore.bundle.v1.Bundle\n"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"mediaType":"application/vnd.dev.sigstore.bundle+json;version=0.2","verificationMaterial":{"x509CertificateChain":{"certificates":[{"rawBytes":"MIICyDCCAk6gAwIBAgIUenpCAKVU2BcGqhnsCe0doys6ibwwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjMxMDExMTQyODAzWhcNMjMxMDExMTQzODAzWjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC0NNg9GUBzDJz2+i3Ffl1RiCwBekqVQvaYdjXJu6O7zd0WOofuc9zaBQ3WhE8o3EXH0Y5prD6bGajd2XEaMW6KOCAW0wggFpMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQULM+gqZePC3pfVoEclUSrtVPwGwgwHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4YZD8wHQYDVR0RAQH/BBMwEYEPYXBwdUBnb29nbGUuY29tMCkGCisGAQQBg78wAQEEG2h0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbTArBgorBgEEAYO/MAEIBB0MG2h0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbTCBigYKKwYBBAHWeQIEAgR8BHoAeAB2AN09MGrGxxEyYxkeHJlnNwKiSl643jyt/4eKcoAvKe6OAAABix8jOOQAAAQDAEcwRQIhANeveOTEE7bNqX0UyaSYlSoOXtOntGNsEAO9FUaa/p/XAiBaMNu8aUovaFnoY+e3VIwsLfKoQls17tZbbH6sY+RZQjAKBggqhkjOPQQDAwNoADBlAjB63/BTZjqtZFoKpilCsvEzz4a6eBGf0dbnqhhDS/9ELuKjj2H7jQuNICxqED5p9vECMQDxUugt9pitEpNkOR4gnHdEAS726LUKhCic2QUhwbNJt/+Th+O7MyvyfoDjdlgGGMI="}]},"tlogEntries":[{"logIndex":"41958702","logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="},"kindVersion":{"kind":"hashedrekord","version":"0.0.1"},"integratedTime":"1697034484","inclusionPromise":{"signedEntryTimestamp":"MEUCIFJST52F75FnhiyWApcgiQWgszyb/rf6J5wIRHEFb6LiAiEAgK/el1WsJLdpoRn0Pp9np9LPbY4ebQyyX03j8325Q48="},"inclusionProof":{"logIndex":"37795271","rootHash":"60ll7idWI1jYRZzxc+jKflYoW+4jWxgZaGR15ASsWt4=","treeSize":"37795272","hashes":["ZIU3md9RFYeb/QLydGOwhQ3ND+W4anIY65AcncCDATg=","FW20NiNv2Sqj0JVrVV0jxM2orMbQ9xh+VP1lTkSDl/Q=","S+iI/+iSshhUyd1Q7CAhJL1r0ztj4VmlB+Mz6EIYVnc=","IarfFu1ExpVVeg2h4fG1IWKYMs6BKCBnp46dlDN+iRA=","lMgRhGHIJoON6m0fp7dqo58UDMgHbflIjryXEw8/6GI=","wFBMBVQ5HXrKMuf5XOphsNlin7vSAfDFJnQq5YDxVrY=","FBAwNrNC3qhOjCcwdeB59P7bwQ40jtBUib0Y/j87xpE=","FTHlMv8QQyKAsmnkVAwWZSzP3mApzQXkiZrN+bQGg08=","5Lrnz5mdl9fOisdC44l0ljBJi9bwDSb/ArXsvCpgCiQ=","dA4IFz5UaTwkR83x1QUkZLq0UZJu9kRZJuSDsU7kVkE=","EqXDaDjlrIheY/2CQ/d0+pp4EBeqLlaq2/0ociyo5AY=","aWnEm9c/Gb8operqvTMd3WBQLe+yzT2W4Xt0HICt7Gw="],"checkpoint":{"envelope":"rekor.sigstore.dev - 2605736670972794746\n37795272\n60ll7idWI1jYRZzxc+jKflYoW+4jWxgZaGR15ASsWt4=\nTimestamp: 1697034484441201852\n\n— rekor.sigstore.dev wNI9ajBGAiEAlWUH2HSPa6IMIRBgFcIXph3Mj9xM70WR0VVADvGIl/oCIQCgfvUUjR/X5jewlqpAWI8NuJIicKpTG64vo6UM5fpSgQ==\n"}},"canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiJhMGNmYzcxMjcxZDZlMjc4ZTU3Y2QzMzJmZjk1N2MzZjcwNDNmZGRhMzU0YzRjYmIxOTBhMzBkNTZlZmEwMWJmIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJRG9xaGp4NWJ6Z1EwS3dNRDFtNzJDTGR4bTZMRHNhWU9oWE94L1NkS0NiRkFpRUExalY0V3kxUkhSN0pWS1FHUmZ5UGpLQTVzRUhWb0M0VUJnRHhpZjJ2Nmx3PSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVTjVSRU5EUVdzMlowRjNTVUpCWjBsVlpXNXdRMEZMVmxVeVFtTkhjV2h1YzBObE1HUnZlWE0yYVdKM2QwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcE5lRTFFUlhoTlZGRjVUMFJCZWxkb1kwNU5hazE0VFVSRmVFMVVVWHBQUkVGNlYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZETUU1T1p6bEhWVUo2UkVwNk1pdHBNMFptYkRGU2FVTjNRbVZyY1ZaUmRtRlpaR29LV0VwMU5rODNlbVF3VjA5dlpuVmpPWHBoUWxFelYyaEZPRzh6UlZoSU1GazFjSEpFTm1KSFlXcGtNbGhGWVUxWE5rdFBRMEZYTUhkblowWndUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlZNVFN0bkNuRmFaVkJETTNCbVZtOUZZMnhWVTNKMFZsQjNSM2RuZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDBoUldVUldVakJTUVZGSUwwSkNUWGRGV1VWUVdWaENkMlJWUW01aU1qbHVZa2RWZFZreU9YUk5RMnRIUTJselIwRlJVVUpuTnpoM1FWRkZSUXBITW1nd1pFaENlazlwT0haWlYwNXFZak5XZFdSSVRYVmFNamwyV2pKNGJFeHRUblppVkVGeVFtZHZja0puUlVWQldVOHZUVUZGU1VKQ01FMUhNbWd3Q21SSVFucFBhVGgyV1ZkT2FtSXpWblZrU0UxMVdqSTVkbG95ZUd4TWJVNTJZbFJEUW1sbldVdExkMWxDUWtGSVYyVlJTVVZCWjFJNFFraHZRV1ZCUWpJS1FVNHdPVTFIY2tkNGVFVjVXWGhyWlVoS2JHNU9kMHRwVTJ3Mk5ETnFlWFF2TkdWTFkyOUJka3RsTms5QlFVRkNhWGc0YWs5UFVVRkJRVkZFUVVWamR3cFNVVWxvUVU1bGRtVlBWRVZGTjJKT2NWZ3dWWGxoVTFsc1UyOVBXSFJQYm5SSFRuTkZRVTg1UmxWaFlTOXdMMWhCYVVKaFRVNTFPR0ZWYjNaaFJtNXZDbGtyWlROV1NYZHpUR1pMYjFGc2N6RTNkRnBpWWtnMmMxa3JVbHBSYWtGTFFtZG5jV2hyYWs5UVVWRkVRWGRPYjBGRVFteEJha0kyTXk5Q1ZGcHFjWFFLV2tadlMzQnBiRU56ZGtWNmVqUmhObVZDUjJZd1pHSnVjV2hvUkZNdk9VVk1kVXRxYWpKSU4ycFJkVTVKUTNoeFJVUTFjRGwyUlVOTlVVUjRWWFZuZEFvNWNHbDBSWEJPYTA5U05HZHVTR1JGUVZNM01qWk1WVXRvUTJsak1sRlZhSGRpVGtwMEx5dFVhQ3RQTjAxNWRubG1iMFJxWkd4blIwZE5TVDBLTFMwdExTMUZUa1FnUTBWU1ZFbEdTVU5CVkVVdExTMHRMUW89In19fX0="}]},"messageSignature":{"signature":"MEUCIDoqhjx5bzgQ0KwMD1m72CLdxm6LDsaYOhXOx/SdKCbFAiEA1jV4Wy1RHR7JVKQGRfyPjKA5sEHVoC4UBgDxif2v6lw="}}
Loading

0 comments on commit 3d1cb32

Please sign in to comment.