Skip to content

Commit ae185ca

Browse files
authored
Merge pull request #701 from sigstore/bundle_first_class
Make bundle a first class type -- part 1
2 parents a180ace + e3b4402 commit ae185ca

File tree

20 files changed

+680
-486
lines changed

20 files changed

+680
-486
lines changed

fuzzing/src/main/java/fuzzing/BundleFactoryFuzzer.java renamed to fuzzing/src/main/java/fuzzing/BundleReaderFuzzer.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@
1616
package fuzzing;
1717

1818
import com.code_intelligence.jazzer.api.FuzzedDataProvider;
19-
import dev.sigstore.bundle.BundleFactory;
19+
import dev.sigstore.bundle.Bundle;
2020
import dev.sigstore.bundle.BundleParseException;
2121
import java.io.StringReader;
2222

23-
public class BundleFactoryFuzzer {
23+
public class BundleReaderFuzzer {
2424
public static void fuzzerTestOneInput(FuzzedDataProvider data) {
2525
try {
2626
String string = data.consumeRemainingAsString();
27-
BundleFactory.createBundle(BundleFactory.readBundle(new StringReader(string)));
27+
Bundle.from(new StringReader(string));
2828
} catch (BundleParseException | IllegalArgumentException e) {
2929
// Known exception
3030
}

fuzzing/src/main/java/fuzzing/BundleVerifierFuzzer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public class BundleVerifierFuzzer {
2222
public static void fuzzerTestOneInput(FuzzedDataProvider data) {
2323
try {
2424
String string = data.consumeRemainingAsString();
25-
BundleVerifier.findMissingFields(string);
25+
BundleVerifier.allMissingFields(string);
2626
} catch (IllegalArgumentException e) {
2727
// Known exception
2828
}

sigstore-cli/src/main/java/dev/sigstore/cli/Sign.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
package dev.sigstore.cli;
1717

1818
import dev.sigstore.KeylessSigner;
19-
import dev.sigstore.bundle.BundleFactory;
19+
import dev.sigstore.bundle.Bundle;
2020
import dev.sigstore.encryption.certificates.Certificates;
2121
import dev.sigstore.oidc.client.OidcClients;
2222
import java.nio.charset.StandardCharsets;
@@ -77,7 +77,7 @@ public Integer call() throws Exception {
7777
} else {
7878
Files.write(
7979
signatureFiles.bundleFile,
80-
BundleFactory.createBundle(signingResult).getBytes(StandardCharsets.UTF_8));
80+
Bundle.from(signingResult).toJson().getBytes(StandardCharsets.UTF_8));
8181
}
8282
return 0;
8383
}

sigstore-cli/src/main/java/dev/sigstore/cli/Verify.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
import dev.sigstore.KeylessVerificationRequest.CertificateIdentity;
2525
import dev.sigstore.KeylessVerificationRequest.VerificationOptions;
2626
import dev.sigstore.KeylessVerifier;
27-
import dev.sigstore.bundle.BundleFactory;
27+
import dev.sigstore.bundle.Bundle;
2828
import dev.sigstore.encryption.certificates.Certificates;
2929
import java.nio.charset.StandardCharsets;
3030
import java.nio.file.Files;
@@ -101,9 +101,9 @@ public Integer call() throws Exception {
101101
keylessSignature =
102102
KeylessSignature.builder().signature(signature).certPath(certPath).digest(digest).build();
103103
} else {
104-
keylessSignature =
105-
BundleFactory.readBundle(
106-
newReader(signatureFiles.bundleFile.toFile(), StandardCharsets.UTF_8));
104+
Bundle bundle =
105+
Bundle.from(newReader(signatureFiles.bundleFile.toFile(), StandardCharsets.UTF_8));
106+
keylessSignature = bundle.toKeylessSignature();
107107
}
108108

109109
var verificationOptionsBuilder = VerificationOptions.builder();

sigstore-gradle/sigstore-gradle-sign-base-plugin/src/main/kotlin/dev/sigstore/sign/work/SignWorkAction.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
package dev.sigstore.sign.work
1818

1919
import dev.sigstore.KeylessSigner
20-
import dev.sigstore.bundle.BundleFactory
20+
import dev.sigstore.bundle.Bundle
2121
import dev.sigstore.oidc.client.OidcClient
2222
import dev.sigstore.oidc.client.OidcClients
2323
import dev.sigstore.sign.OidcClientConfiguration
@@ -56,7 +56,7 @@ abstract class SignWorkAction : WorkAction<SignWorkParameters> {
5656
}
5757

5858
val result = signer.signFile(inputFile.toPath())
59-
val bundleJson = BundleFactory.createBundle(result)
59+
val bundleJson = Bundle.from(result).toJson()
6060
parameters.outputSignature.get().asFile.writeText(bundleJson)
6161
}
6262
}
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
/*
2+
* Copyright 2024 The Sigstore Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package dev.sigstore.bundle;
17+
18+
import com.google.common.base.Preconditions;
19+
import dev.sigstore.KeylessSignature;
20+
import dev.sigstore.rekor.client.RekorEntry;
21+
import java.io.Reader;
22+
import java.security.cert.CertPath;
23+
import java.util.List;
24+
import java.util.Optional;
25+
import org.immutables.value.Value;
26+
import org.immutables.value.Value.Immutable;
27+
import org.immutables.value.Value.Lazy;
28+
29+
/**
30+
* A representation of sigstore signing materials. See <a
31+
* href="https://github.com/sigstore/protobuf-specs">protobuf-specs</a>
32+
*/
33+
@Immutable
34+
public abstract class Bundle {
35+
36+
public enum HashAlgorithm {
37+
SHA2_256
38+
}
39+
40+
static final String BUNDLE_V_0_1 = "application/vnd.dev.sigstore.bundle+json;version=0.1";
41+
static final String BUNDLE_V_0_2 = "application/vnd.dev.sigstore.bundle+json;version=0.2";
42+
static final String BUNDLE_V_0_3 = "application/vnd.dev.sigstore.bundle+json;version=0.3";
43+
// media_type format switch: https://github.com/sigstore/protobuf-specs/pull/279
44+
static final String BUNDLE_V_0_3_1 = "application/vnd.dev.sigstore.bundle.v0.3+json";
45+
static final List<String> SUPPORTED_MEDIA_TYPES =
46+
List.of(BUNDLE_V_0_1, BUNDLE_V_0_2, BUNDLE_V_0_3, BUNDLE_V_0_3_1);
47+
48+
/** The bundle version */
49+
public abstract String getMediaType();
50+
51+
/** A signature represented as a signature and digest */
52+
public abstract Optional<MessageSignature> getMessageSignature();
53+
54+
/** A DSSE envelope signature type that may contain an arbitrary payload */
55+
public abstract Optional<DSSESignature> getDSSESignature();
56+
57+
@Value.Check
58+
protected void checkOnlyOneSignature() {
59+
Preconditions.checkState(
60+
(getDSSESignature().isEmpty() && getMessageSignature().isPresent())
61+
|| (getDSSESignature().isPresent() && getMessageSignature().isEmpty()));
62+
}
63+
64+
@Value.Check
65+
protected void checkAtLeastOneTimestamp() {
66+
for (var entry : getEntries()) {
67+
if (entry.getVerification().getSignedEntryTimestamp() != null) {
68+
return;
69+
}
70+
}
71+
if (getTimestamps().size() > 0) {
72+
return;
73+
}
74+
throw new IllegalStateException("No timestamp verification (set, timestamp) was provided");
75+
}
76+
77+
/**
78+
* The partial certificate chain provided by fulcio for the public key and identity used to sign
79+
* the artifact, this should NOT contain the trusted root or any trusted intermediates. But users
80+
* of this object should understand that older signatures may include the full chain.
81+
*/
82+
public abstract CertPath getCertPath();
83+
84+
/**
85+
* The entry in the rekor transparency log (represented as a list for future compatibility, but
86+
* currently only allow for at most one entry.
87+
*/
88+
public abstract List<RekorEntry> getEntries();
89+
90+
/** A list of timestamps to verify the time of signing. Currently, allows rfc3161 timestamps. */
91+
public abstract List<Timestamp> getTimestamps();
92+
93+
@Immutable
94+
interface MessageSignature {
95+
96+
/**
97+
* An optional message digest, this should not be used to verify signature validity. A digest
98+
* should be provided or computed by the system.
99+
*/
100+
Optional<MessageDigest> getMessageDigest();
101+
102+
/** Signature over an artifact. */
103+
byte[] getSignature();
104+
}
105+
106+
@Immutable
107+
interface MessageDigest {
108+
109+
/** The algorithm used to compute the digest. */
110+
HashAlgorithm getHashAlgorithm();
111+
112+
/**
113+
* The raw bytes of the digest computer using the hashing algorithm described by {@link
114+
* #getHashAlgorithm()}
115+
*/
116+
byte[] getDigest();
117+
}
118+
119+
@Immutable
120+
interface DSSESignature {
121+
122+
/** An arbitrary payload that does not need to be parsed to be validated */
123+
String getPayload();
124+
125+
/** Information on how to interpret the payload */
126+
String getPayloadType();
127+
128+
/** DSSE specific signature */
129+
byte[] getSignature();
130+
}
131+
132+
@Immutable
133+
interface Timestamp {
134+
135+
/** Raw bytes of an rfc31616 timestamp */
136+
byte[] getRfc3161Timestamp();
137+
}
138+
139+
public static Bundle from(Reader bundleJson) throws BundleParseException {
140+
return BundleReader.readBundle(bundleJson);
141+
}
142+
143+
@Lazy
144+
public String toJson() {
145+
return BundleWriter.writeBundle(this);
146+
}
147+
148+
/** Compat method to convert from keyless signature. Don't use, will be removed. */
149+
public static Bundle from(KeylessSignature keylessSignature) {
150+
var sig =
151+
ImmutableMessageSignature.builder()
152+
.messageDigest(
153+
ImmutableMessageDigest.builder()
154+
.hashAlgorithm(HashAlgorithm.SHA2_256)
155+
.digest(keylessSignature.getDigest())
156+
.build())
157+
.signature(keylessSignature.getSignature())
158+
.build();
159+
160+
return ImmutableBundle.builder()
161+
.mediaType(BUNDLE_V_0_3_1)
162+
.messageSignature(sig)
163+
.addEntries(keylessSignature.getEntry().get())
164+
.certPath(keylessSignature.getCertPath())
165+
.build();
166+
}
167+
168+
/** Compat method to convert to keyless signature. Don't use, will be removed. */
169+
@Lazy
170+
public KeylessSignature toKeylessSignature() {
171+
if (getDSSESignature().isPresent()) {
172+
throw new IllegalStateException("This client can't process bundles with DSSE signatures.");
173+
}
174+
if (getTimestamps().size() >= 1) {
175+
throw new IllegalStateException("This client can't process bundles with RFC3161 Timestamps");
176+
}
177+
var builder =
178+
KeylessSignature.builder()
179+
.certPath(getCertPath())
180+
.signature(getMessageSignature().get().getSignature())
181+
.entry(getEntries().get(0));
182+
if (getMessageSignature().get().getMessageDigest().isPresent()) {
183+
builder.digest(getMessageSignature().get().getMessageDigest().get().getDigest());
184+
} else {
185+
builder.digest();
186+
}
187+
return builder.build();
188+
}
189+
}
Lines changed: 4 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2022 The Sigstore Authors.
2+
* Copyright 2024 The Sigstore Authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,54 +15,11 @@
1515
*/
1616
package dev.sigstore.bundle;
1717

18-
import com.google.protobuf.InvalidProtocolBufferException;
1918
import dev.sigstore.KeylessSignature;
20-
import dev.sigstore.proto.bundle.v1.Bundle;
21-
import java.io.Reader;
22-
import java.util.List;
2319

24-
/**
25-
* Generates Sigstore Bundle.
26-
*
27-
* @see <a href="https://github.com/sigstore/protobuf-specs">Sigstore Bundle Protobuf
28-
* specifications</a>
29-
*/
20+
/** Compat class, needed for build to continue to work while we make API changes. */
3021
public class BundleFactory {
31-
/**
32-
* Generates Sigstore Bundle JSON from {@link KeylessSignature}.
33-
*
34-
* @param signingResult Keyless signing result.
35-
* @return Sigstore Bundle in JSON format
36-
*/
37-
public static String createBundle(KeylessSignature signingResult) {
38-
Bundle bundle = BundleFactoryInternal.createBundleBuilder(signingResult).build();
39-
try {
40-
String jsonBundle = BundleFactoryInternal.JSON_PRINTER.print(bundle);
41-
List<String> missingFields = BundleVerifierInternal.findMissingFields(bundle);
42-
if (!missingFields.isEmpty()) {
43-
throw new IllegalStateException(
44-
"Some of the fields were not initialized: "
45-
+ String.join(", ", missingFields)
46-
+ "; bundle JSON: "
47-
+ jsonBundle);
48-
}
49-
return jsonBundle;
50-
} catch (InvalidProtocolBufferException e) {
51-
throw new IllegalArgumentException(
52-
"Can't serialize signing result to Sigstore Bundle JSON", e);
53-
}
54-
}
55-
56-
/**
57-
* Read a bundle json and convert it back into a keyless signing result for use within this
58-
* library
59-
*
60-
* @param jsonReader a reader to a valid bundle json file
61-
* @return the converted signing result object
62-
* @throws BundleParseException if all or parts of the bundle were not convertible to library
63-
* types
64-
*/
65-
public static KeylessSignature readBundle(Reader jsonReader) throws BundleParseException {
66-
return BundleFactoryInternal.readBundle(jsonReader);
22+
public static String createBundle(KeylessSignature keylessSignature) {
23+
return Bundle.from(keylessSignature).toJson();
6724
}
6825
}

0 commit comments

Comments
 (0)