Skip to content

Commit 6314f67

Browse files
committed
Update protobuf-spec to 0.3.0
Generated sigstore bundles are now 0.3 Bundle parser can read 0.1, 0.2 and 0.3 bundles Signed-off-by: Appu Goundan <[email protected]>
1 parent fb4df60 commit 6314f67

File tree

13 files changed

+208
-84
lines changed

13 files changed

+208
-84
lines changed

sigstore-java/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ dependencies {
2020

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

23-
implementation("dev.sigstore:protobuf-specs:0.2.1") {
23+
implementation("dev.sigstore:protobuf-specs:0.3.0") {
2424
because("It generates Sigstore Bundle file")
2525
}
2626
implementation(platform("com.google.protobuf:protobuf-bom:3.25.2"))

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@ interface VerificationOptions {
3737
* rekor entry in a {@link KeylessSignature}. Verifier may still connect to Rekor to obtain an
3838
* entry if no {@link KeylessSignature#getEntry()} is empty.
3939
*/
40-
boolean alwaysUseRemoteRekorEntry();
40+
@Default
41+
default boolean alwaysUseRemoteRekorEntry() {
42+
return false;
43+
}
4144

4245
List<CertificateIdentity> getCertificateIdentities();
4346

sigstore-java/src/main/java/dev/sigstore/bundle/BundleFactoryInternal.java

Lines changed: 72 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,18 @@
1515
*/
1616
package dev.sigstore.bundle;
1717

18+
import com.google.common.collect.Iterables;
1819
import com.google.protobuf.ByteString;
1920
import com.google.protobuf.util.JsonFormat;
2021
import dev.sigstore.KeylessSignature;
21-
import dev.sigstore.encryption.certificates.Certificates;
22+
import dev.sigstore.proto.ProtoMutators;
2223
import dev.sigstore.proto.bundle.v1.Bundle;
2324
import dev.sigstore.proto.bundle.v1.VerificationMaterial;
2425
import dev.sigstore.proto.common.v1.HashAlgorithm;
2526
import dev.sigstore.proto.common.v1.HashOutput;
2627
import dev.sigstore.proto.common.v1.LogId;
2728
import dev.sigstore.proto.common.v1.MessageSignature;
2829
import dev.sigstore.proto.common.v1.X509Certificate;
29-
import dev.sigstore.proto.common.v1.X509CertificateChain;
3030
import dev.sigstore.proto.rekor.v1.Checkpoint;
3131
import dev.sigstore.proto.rekor.v1.InclusionPromise;
3232
import dev.sigstore.proto.rekor.v1.InclusionProof;
@@ -39,13 +39,11 @@
3939
import java.io.IOException;
4040
import java.io.Reader;
4141
import java.security.cert.CertPath;
42-
import java.security.cert.Certificate;
4342
import java.security.cert.CertificateEncodingException;
4443
import java.security.cert.CertificateException;
45-
import java.security.cert.CertificateFactory;
46-
import java.util.ArrayList;
4744
import java.util.Base64;
4845
import java.util.List;
46+
import java.util.Optional;
4947
import java.util.stream.Collectors;
5048
import org.bouncycastle.util.encoders.Hex;
5149

@@ -59,6 +57,12 @@
5957
class BundleFactoryInternal {
6058
static final JsonFormat.Printer JSON_PRINTER = JsonFormat.printer();
6159

60+
private static final String BUNDLE_V_0_1 = "application/vnd.dev.sigstore.bundle+json;version=0.1";
61+
private static final String BUNDLE_V_0_2 = "application/vnd.dev.sigstore.bundle+json;version=0.2";
62+
private static final String BUNDLE_V_0_3 = "application/vnd.dev.sigstore.bundle+json;version=0.3";
63+
private static final List<String> SUPPORTED_MEDIA_TYPES =
64+
List.of(BUNDLE_V_0_1, BUNDLE_V_0_2, BUNDLE_V_0_3);
65+
6266
/**
6367
* Generates Sigstore Bundle Builder from {@link KeylessSignature}. This might be useful in case
6468
* you want to add additional information to the bundle.
@@ -72,7 +76,7 @@ static Bundle.Builder createBundleBuilder(KeylessSignature signingResult) {
7276
"keyless signature must have artifact digest when serializing to bundle");
7377
}
7478
return Bundle.newBuilder()
75-
.setMediaType("application/vnd.dev.sigstore.bundle+json;version=0.2")
79+
.setMediaType(BUNDLE_V_0_3)
7680
.setVerificationMaterial(buildVerificationMaterial(signingResult))
7781
.setMessageSignature(
7882
MessageSignature.newBuilder()
@@ -85,29 +89,18 @@ static Bundle.Builder createBundleBuilder(KeylessSignature signingResult) {
8589

8690
private static VerificationMaterial.Builder buildVerificationMaterial(
8791
KeylessSignature signingResult) {
88-
var builder =
89-
VerificationMaterial.newBuilder()
90-
.setX509CertificateChain(
91-
X509CertificateChain.newBuilder()
92-
.addAllCertificates(
93-
signingResult.getCertPath().getCertificates().stream()
94-
.map(
95-
c -> {
96-
byte[] encoded;
97-
try {
98-
encoded = c.getEncoded();
99-
} catch (CertificateEncodingException e) {
100-
throw new IllegalArgumentException(
101-
"Cannot encode certificate " + c, e);
102-
}
103-
return X509Certificate.newBuilder()
104-
.setRawBytes(ByteString.copyFrom(encoded))
105-
.build();
106-
})
107-
.collect(Collectors.toList())));
108-
if (signingResult.getEntry().isPresent()) {
109-
builder.addTlogEntries(buildTlogEntries(signingResult.getEntry().get()));
92+
X509Certificate cert;
93+
var javaCert = Iterables.getLast(signingResult.getCertPath().getCertificates());
94+
try {
95+
cert = ProtoMutators.fromCert((java.security.cert.X509Certificate) javaCert);
96+
} catch (CertificateEncodingException ce) {
97+
throw new IllegalArgumentException("Cannot encode certificate " + javaCert, ce);
98+
}
99+
var builder = VerificationMaterial.newBuilder().setCertificate(cert);
100+
if (signingResult.getEntry().isEmpty()) {
101+
throw new IllegalArgumentException("A log entry must be present in the signing result");
110102
}
103+
builder.addTlogEntries(buildTlogEntries(signingResult.getEntry().get()));
111104
return builder;
112105
}
113106

@@ -135,10 +128,13 @@ private static TransparencyLogEntry.Builder buildTlogEntries(RekorEntry entry) {
135128
private static void addInclusionProof(
136129
TransparencyLogEntry.Builder transparencyLogEntry, RekorEntry entry) {
137130
RekorEntry.InclusionProof inclusionProof =
138-
entry.getVerification().getInclusionProof().orElse(null);
139-
if (inclusionProof == null) {
140-
return;
141-
}
131+
entry
132+
.getVerification()
133+
.getInclusionProof()
134+
.orElseThrow(
135+
() ->
136+
new IllegalArgumentException(
137+
"An inclusion proof must be present in the log entry in the signing result"));
142138
transparencyLogEntry.setInclusionProof(
143139
InclusionProof.newBuilder()
144140
.setLogIndex(inclusionProof.getLogIndex())
@@ -156,51 +152,47 @@ static KeylessSignature readBundle(Reader jsonReader) throws BundleParseExceptio
156152
try {
157153
JsonFormat.parser().merge(jsonReader, bundleBuilder);
158154
} catch (IOException ioe) {
159-
throw new BundleParseException("Could not read bundle json", ioe);
155+
throw new BundleParseException("Could not process bundle json", ioe);
160156
}
161-
Bundle bundle = bundleBuilder.build();
162157

163-
// TODO: only allow v0.2 bundles at some point, we will only be producing v0.2 bundles
164-
// TODO: in our GA release.
165-
// var supportedMediaType = "application/vnd.dev.sigstore.bundle+json;version=0.2";
166-
// if (!supportedMediaType.equals(bundle.getMediaType())) {
167-
// throw new BundleParseException(
168-
// "Unsupported media type '"
169-
// + bundle.getMediaType()
170-
// + "', only '"
171-
// + supportedMediaType
172-
// + "' is supported");
173-
// }
158+
Bundle bundle = bundleBuilder.build();
159+
if (!SUPPORTED_MEDIA_TYPES.contains(bundle.getMediaType())) {
160+
throw new BundleParseException("Unsupported bundle media type: " + bundle.getMediaType());
161+
}
174162

175163
if (bundle.getVerificationMaterial().getTlogEntriesCount() == 0) {
176164
throw new BundleParseException("Could not find any tlog entries in bundle json");
177165
}
178166
var bundleEntry = bundle.getVerificationMaterial().getTlogEntries(0);
167+
RekorEntry.InclusionProof inclusionProof = null;
179168
if (!bundleEntry.hasInclusionProof()) {
180-
throw new BundleParseException("Could not find an inclusion proof");
169+
if (!bundle.getMediaType().equals(BUNDLE_V_0_1)) {
170+
throw new BundleParseException("Could not find an inclusion proof");
171+
}
172+
} else {
173+
var bundleInclusionProof = bundleEntry.getInclusionProof();
174+
175+
inclusionProof =
176+
ImmutableInclusionProof.builder()
177+
.logIndex(bundleInclusionProof.getLogIndex())
178+
.rootHash(Hex.toHexString(bundleInclusionProof.getRootHash().toByteArray()))
179+
.treeSize(bundleInclusionProof.getTreeSize())
180+
.checkpoint(bundleInclusionProof.getCheckpoint().getEnvelope())
181+
.addAllHashes(
182+
bundleInclusionProof.getHashesList().stream()
183+
.map(ByteString::toByteArray)
184+
.map(Hex::toHexString)
185+
.collect(Collectors.toList()))
186+
.build();
181187
}
182-
var bundleInclusionProof = bundleEntry.getInclusionProof();
183-
184-
ImmutableInclusionProof inclusionProof =
185-
ImmutableInclusionProof.builder()
186-
.logIndex(bundleInclusionProof.getLogIndex())
187-
.rootHash(Hex.toHexString(bundleInclusionProof.getRootHash().toByteArray()))
188-
.treeSize(bundleInclusionProof.getTreeSize())
189-
.checkpoint(bundleInclusionProof.getCheckpoint().getEnvelope())
190-
.addAllHashes(
191-
bundleInclusionProof.getHashesList().stream()
192-
.map(ByteString::toByteArray)
193-
.map(Hex::toHexString)
194-
.collect(Collectors.toList()))
195-
.build();
196188

197189
var verification =
198190
ImmutableVerification.builder()
199191
.signedEntryTimestamp(
200192
Base64.getEncoder()
201193
.encodeToString(
202194
bundleEntry.getInclusionPromise().getSignedEntryTimestamp().toByteArray()))
203-
.inclusionProof(inclusionProof)
195+
.inclusionProof(Optional.ofNullable(inclusionProof))
204196
.build();
205197

206198
var rekorEntry =
@@ -214,6 +206,10 @@ static KeylessSignature readBundle(Reader jsonReader) throws BundleParseExceptio
214206
.verification(verification)
215207
.build();
216208

209+
if (bundle.hasDsseEnvelope()) {
210+
throw new BundleParseException("DSSE envelope signatures are not supported by this client");
211+
}
212+
217213
var digest = new byte[] {};
218214
if (bundle.getMessageSignature().hasMessageDigest()) {
219215
var hashAlgorithm = bundle.getMessageSignature().getMessageDigest().getAlgorithm();
@@ -228,27 +224,24 @@ static KeylessSignature readBundle(Reader jsonReader) throws BundleParseExceptio
228224
digest = bundle.getMessageSignature().getMessageDigest().getDigest().toByteArray();
229225
}
230226

227+
CertPath certPath;
231228
try {
232-
return KeylessSignature.builder()
233-
.digest(digest)
234-
.certPath(
235-
toCertPath(
236-
bundle.getVerificationMaterial().getX509CertificateChain().getCertificatesList()))
237-
.signature(bundle.getMessageSignature().getSignature().toByteArray())
238-
.entry(rekorEntry)
239-
.build();
229+
if (bundle.getVerificationMaterial().hasCertificate()) {
230+
certPath =
231+
ProtoMutators.toCertPath(List.of(bundle.getVerificationMaterial().getCertificate()));
232+
} else {
233+
certPath =
234+
ProtoMutators.toCertPath(
235+
bundle.getVerificationMaterial().getX509CertificateChain().getCertificatesList());
236+
}
240237
} catch (CertificateException ce) {
241238
throw new BundleParseException("Could not parse bundle certificate chain", ce);
242239
}
243-
}
244-
245-
private static CertPath toCertPath(List<X509Certificate> certificates)
246-
throws CertificateException {
247-
CertificateFactory cf = CertificateFactory.getInstance("X.509");
248-
List<Certificate> converted = new ArrayList<>(certificates.size());
249-
for (var cert : certificates) {
250-
converted.add(Certificates.fromDer(cert.getRawBytes().toByteArray()));
251-
}
252-
return cf.generateCertPath(converted);
240+
return KeylessSignature.builder()
241+
.digest(digest)
242+
.certPath(certPath)
243+
.signature(bundle.getMessageSignature().getSignature().toByteArray())
244+
.entry(rekorEntry)
245+
.build();
253246
}
254247
}

sigstore-java/src/main/java/dev/sigstore/proto/ProtoMutators.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@
1515
*/
1616
package dev.sigstore.proto;
1717

18+
import com.google.protobuf.ByteString;
1819
import com.google.protobuf.Timestamp;
1920
import dev.sigstore.encryption.certificates.Certificates;
2021
import dev.sigstore.proto.common.v1.X509Certificate;
2122
import java.security.cert.CertPath;
2223
import java.security.cert.Certificate;
24+
import java.security.cert.CertificateEncodingException;
2325
import java.security.cert.CertificateException;
2426
import java.security.cert.CertificateFactory;
2527
import java.time.Instant;
@@ -41,4 +43,11 @@ public static CertPath toCertPath(List<X509Certificate> certificates)
4143
public static Instant toInstant(Timestamp timestamp) {
4244
return Instant.ofEpochSecond(timestamp.getSeconds(), timestamp.getNanos());
4345
}
46+
47+
public static X509Certificate fromCert(java.security.cert.X509Certificate certificate)
48+
throws CertificateEncodingException {
49+
byte[] encoded;
50+
encoded = certificate.getEncoded();
51+
return X509Certificate.newBuilder().setRawBytes(ByteString.copyFrom(encoded)).build();
52+
}
4453
}

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

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,46 @@ public void testVerify_mismatchedSet() throws Exception {
6161
var verificationReq =
6262
KeylessVerificationRequest.builder()
6363
.keylessSignature(BundleFactory.readBundle(new StringReader(bundleFile)))
64-
.verificationOptions(
65-
VerificationOptions.builder().alwaysUseRemoteRekorEntry(false).build())
6664
.build();
6765
Assertions.assertThrows(
6866
KeylessVerificationException.class,
6967
() -> verifier.verify(Path.of(artifact), verificationReq));
7068
}
69+
70+
@Test
71+
public void testVerify_canVerifyV01Bundle() throws Exception {
72+
verifyBundle(
73+
"dev/sigstore/samples/bundles/artifact.txt",
74+
"dev/sigstore/samples/bundles/bundle.v1.sigstore");
75+
}
76+
77+
@Test
78+
public void testVerify_canVerifyV02Bundle() throws Exception {
79+
verifyBundle(
80+
"dev/sigstore/samples/bundles/artifact.txt",
81+
"dev/sigstore/samples/bundles/bundle.v2.sigstore");
82+
}
83+
84+
@Test
85+
public void testVerify_canVerifyV03Bundle() throws Exception {
86+
verifyBundle(
87+
"dev/sigstore/samples/bundles/artifact.txt",
88+
"dev/sigstore/samples/bundles/bundle.v3.sigstore");
89+
}
90+
91+
public void verifyBundle(String artifactResourcePath, String bundleResourcePath)
92+
throws Exception {
93+
var artifact = Resources.getResource(artifactResourcePath).getPath();
94+
var bundleFile =
95+
Resources.toString(Resources.getResource(bundleResourcePath), StandardCharsets.UTF_8);
96+
97+
var verifier = KeylessVerifier.builder().sigstorePublicDefaults().build();
98+
var verificationReq =
99+
KeylessVerificationRequest.builder()
100+
.keylessSignature(BundleFactory.readBundle(new StringReader(bundleFile)))
101+
.verificationOptions(VerificationOptions.builder().build())
102+
.build();
103+
104+
verifier.verify(Path.of(artifact), verificationReq);
105+
}
71106
}

0 commit comments

Comments
 (0)