Skip to content

Commit b81ab3e

Browse files
committed
Wrap json operations for checked exceptions
Signed-off-by: Appu Goundan <[email protected]>
1 parent da48db2 commit b81ab3e

33 files changed

+314
-151
lines changed

config/forbiddenApis.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
com.google.protobuf.util.JsonFormat#parser() @ Use dev.sigstore.json.ProtoJson#parser() instead
22
dev.sigstore.http.HttpClients#newHttpTransport(dev.sigstore.http.HttpParams) @ Use dev.sigstore.http.HttpClients#newRequestFactory(...) instead
3+
com.google.gson.GsonBuilder @ Use dev.sigstore.json.GsonSupplier.GSON instead

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import com.google.errorprone.annotations.CanIgnoreReturnValue;
2323
import com.google.errorprone.annotations.CheckReturnValue;
2424
import com.google.errorprone.annotations.concurrent.GuardedBy;
25-
import com.google.gson.JsonSyntaxException;
2625
import com.google.protobuf.ByteString;
2726
import dev.sigstore.bundle.Bundle;
2827
import dev.sigstore.bundle.Bundle.MessageSignature;
@@ -40,6 +39,7 @@
4039
import dev.sigstore.fulcio.client.FulcioVerificationException;
4140
import dev.sigstore.fulcio.client.FulcioVerifier;
4241
import dev.sigstore.fulcio.client.UnsupportedAlgorithmException;
42+
import dev.sigstore.json.JsonParseException;
4343
import dev.sigstore.oidc.client.OidcClients;
4444
import dev.sigstore.oidc.client.OidcException;
4545
import dev.sigstore.oidc.client.OidcToken;
@@ -697,7 +697,7 @@ public Bundle attest(String payload) throws KeylessSignerException {
697697
InTotoPayload inTotoPayload;
698698
try {
699699
inTotoPayload = InTotoPayload.from(payload);
700-
} catch (JsonSyntaxException jse) {
700+
} catch (JsonParseException jse) {
701701
throw new IllegalArgumentException("Payload is not a valid in-toto statement");
702702
}
703703

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

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import dev.sigstore.encryption.signers.Verifiers;
3030
import dev.sigstore.fulcio.client.FulcioVerificationException;
3131
import dev.sigstore.fulcio.client.FulcioVerifier;
32+
import dev.sigstore.json.JsonParseException;
3233
import dev.sigstore.proto.common.v1.HashAlgorithm;
3334
import dev.sigstore.proto.rekor.v2.DSSELogEntryV002;
3435
import dev.sigstore.proto.rekor.v2.HashedRekordLogEntryV002;
@@ -267,7 +268,12 @@ private void checkMessageSignature(
267268
}
268269

269270
// recreate the log entry and check if it matches what was provided in the entry
270-
String version = rekorEntry.getBodyDecoded().getApiVersion();
271+
String version;
272+
try {
273+
version = rekorEntry.getBodyDecoded().getApiVersion();
274+
} catch (JsonParseException ex) {
275+
throw new KeylessVerificationException("Could not extract body from log entry");
276+
}
271277
if ("0.0.1".equals(version)) {
272278
try {
273279
RekorTypes.getHashedRekordV001(rekorEntry);
@@ -346,7 +352,13 @@ private void checkDsseEnvelope(
346352
+ dsseEnvelope.getPayloadType()
347353
+ "'");
348354
}
349-
InTotoPayload payload = InTotoPayload.from(dsseEnvelope);
355+
356+
InTotoPayload payload;
357+
try {
358+
payload = InTotoPayload.from(dsseEnvelope);
359+
} catch (JsonParseException jpe) {
360+
throw new KeylessVerificationException("Could not parse DSSE payload", jpe);
361+
}
350362

351363
// find one sha256 hash in the subject list that matches the artifact hash
352364
if (payload.getSubject().stream()
@@ -392,7 +404,12 @@ private void checkDsseEnvelope(
392404
throw new KeylessVerificationException("Signature could not be processed", se);
393405
}
394406

395-
String version = rekorEntry.getBodyDecoded().getApiVersion();
407+
String version;
408+
try {
409+
version = rekorEntry.getBodyDecoded().getApiVersion();
410+
} catch (JsonParseException ex) {
411+
throw new KeylessVerificationException("Could not extract body from log entry");
412+
}
396413
if ("0.0.1".equals(version)) {
397414
Dsse rekorDsse;
398415
try {

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

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.google.protobuf.ByteString;
2020
import com.google.protobuf.InvalidProtocolBufferException;
2121
import com.google.protobuf.util.JsonFormat;
22+
import dev.sigstore.json.JsonParseException;
2223
import dev.sigstore.proto.ProtoMutators;
2324
import dev.sigstore.proto.bundle.v1.TimestampVerificationData;
2425
import dev.sigstore.proto.bundle.v1.VerificationMaterial;
@@ -146,26 +147,35 @@ private static VerificationMaterial.Builder buildVerificationMaterial(Bundle bun
146147
}
147148

148149
private static TransparencyLogEntry.Builder buildTlogEntries(RekorEntry entry) {
149-
TransparencyLogEntry.Builder transparencyLogEntry =
150-
TransparencyLogEntry.newBuilder()
151-
.setLogIndex(entry.getLogIndex())
152-
.setLogId(LogId.newBuilder().setKeyId(ByteString.fromHex(entry.getLogID())))
153-
.setKindVersion(
154-
KindVersion.newBuilder()
155-
.setKind(entry.getBodyDecoded().getKind())
156-
.setVersion(entry.getBodyDecoded().getApiVersion()))
157-
.setIntegratedTime(entry.getIntegratedTime())
158-
.setCanonicalizedBody(ByteString.copyFrom(Base64.getDecoder().decode(entry.getBody())));
159-
if (entry.getVerification().getSignedEntryTimestamp() != null) {
160-
transparencyLogEntry.setInclusionPromise(
161-
InclusionPromise.newBuilder()
162-
.setSignedEntryTimestamp(
163-
ByteString.copyFrom(
164-
Base64.getDecoder()
165-
.decode(entry.getVerification().getSignedEntryTimestamp()))));
150+
try {
151+
TransparencyLogEntry.Builder transparencyLogEntry =
152+
TransparencyLogEntry.newBuilder()
153+
.setLogIndex(entry.getLogIndex())
154+
.setLogId(LogId.newBuilder().setKeyId(ByteString.fromHex(entry.getLogID())))
155+
.setKindVersion(
156+
KindVersion.newBuilder()
157+
.setKind(entry.getBodyDecoded().getKind())
158+
.setVersion(entry.getBodyDecoded().getApiVersion()))
159+
.setIntegratedTime(entry.getIntegratedTime())
160+
.setCanonicalizedBody(
161+
ByteString.copyFrom(Base64.getDecoder().decode(entry.getBody())));
162+
if (entry.getVerification().getSignedEntryTimestamp() != null) {
163+
transparencyLogEntry.setInclusionPromise(
164+
InclusionPromise.newBuilder()
165+
.setSignedEntryTimestamp(
166+
ByteString.copyFrom(
167+
Base64.getDecoder()
168+
.decode(entry.getVerification().getSignedEntryTimestamp()))));
169+
}
170+
addInclusionProof(transparencyLogEntry, entry);
171+
return transparencyLogEntry;
172+
} catch (JsonParseException jpe) {
173+
throw new IllegalStateException(
174+
"Trying to serialize a bad bundle: rekor entry body is invalid: '"
175+
+ entry.getBody()
176+
+ "'",
177+
jpe);
166178
}
167-
addInclusionProof(transparencyLogEntry, entry);
168-
return transparencyLogEntry;
169179
}
170180

171181
private static void addInclusionProof(

sigstore-java/src/main/java/dev/sigstore/dsse/InTotoPayload.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import com.google.gson.JsonElement;
2121
import dev.sigstore.bundle.Bundle.DsseEnvelope;
22+
import dev.sigstore.json.JsonParseException;
2223
import java.util.List;
2324
import java.util.Map;
2425
import org.immutables.gson.Gson;
@@ -51,11 +52,11 @@ interface Subject {
5152
Map<String, String> getDigest();
5253
}
5354

54-
static InTotoPayload from(String payload) {
55+
static InTotoPayload from(String payload) throws JsonParseException {
5556
return GSON.get().fromJson(payload, InTotoPayload.class);
5657
}
5758

58-
static InTotoPayload from(DsseEnvelope dsseEnvelope) {
59+
static InTotoPayload from(DsseEnvelope dsseEnvelope) throws JsonParseException {
5960
return from(dsseEnvelope.getPayloadAsString());
6061
}
6162
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright 2025 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.json;
17+
18+
import com.google.gson.Gson;
19+
import com.google.gson.JsonElement;
20+
import java.io.Reader;
21+
import java.lang.reflect.Type;
22+
23+
/** A Gson wrapper that catches all runtime exceptions. */
24+
public final class GsonChecked {
25+
26+
Gson gson;
27+
28+
GsonChecked(Gson gson) {
29+
this.gson = gson;
30+
}
31+
32+
public <T> T fromJson(JsonElement element, Class<T> classOfT) throws JsonParseException {
33+
try {
34+
return gson.fromJson(element, classOfT);
35+
} catch (RuntimeException e) {
36+
throw new JsonParseException(e);
37+
}
38+
}
39+
40+
public <T> T fromJson(String json, Class<T> classOfT) throws JsonParseException {
41+
try {
42+
return gson.fromJson(json, classOfT);
43+
} catch (RuntimeException e) {
44+
throw new JsonParseException(e);
45+
}
46+
}
47+
48+
public <T> T fromJson(String json, Type typeOfT) throws JsonParseException {
49+
try {
50+
return gson.fromJson(json, typeOfT);
51+
} catch (RuntimeException e) {
52+
throw new JsonParseException(e);
53+
}
54+
}
55+
56+
public <T> T fromJson(Reader reader, Class<T> classOfT) throws JsonParseException {
57+
try {
58+
return gson.fromJson(reader, classOfT);
59+
} catch (RuntimeException e) {
60+
throw new JsonParseException(e);
61+
}
62+
}
63+
64+
public <T> String toJson(T src) {
65+
return gson.toJson(src);
66+
}
67+
68+
public <T> void toJson(T src, Appendable writer) {
69+
gson.toJson(src, writer);
70+
}
71+
}

sigstore-java/src/main/java/dev/sigstore/json/GsonSupplier.java

Lines changed: 34 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import com.google.gson.*;
1919
import dev.sigstore.dsse.GsonAdaptersInTotoPayload;
20+
import dev.sigstore.forbidden.SuppressForbidden;
2021
import dev.sigstore.rekor.client.GsonAdaptersRekorEntry;
2122
import dev.sigstore.rekor.client.GsonAdaptersRekorEntryBody;
2223
import dev.sigstore.tuf.model.*;
@@ -30,42 +31,44 @@
3031
* requests between sigstore and this client -- and should probably be used for any api call to
3132
* sigstore that expects JSON.
3233
*/
33-
public enum GsonSupplier implements Supplier<Gson> {
34+
@SuppressForbidden(reason = "GsonBuilder")
35+
public enum GsonSupplier implements Supplier<GsonChecked> {
3436
GSON;
3537

3638
@SuppressWarnings("ImmutableEnumChecker")
37-
private final Gson gson =
38-
new GsonBuilder()
39-
.registerTypeAdapter(byte[].class, new GsonByteArrayAdapter())
40-
.registerTypeAdapter(
41-
LocalDateTime.class,
42-
(JsonDeserializer<LocalDateTime>)
43-
(json, type, jsonDeserializationContext) ->
44-
ZonedDateTime.parse(json.getAsJsonPrimitive().getAsString())
45-
.toLocalDateTime())
46-
// Immutables generated GSON Adapters in alphabetical order
47-
.registerTypeAdapterFactory(new GsonAdaptersDelegations())
48-
.registerTypeAdapterFactory(new GsonAdaptersDelegationRole())
49-
.registerTypeAdapterFactory(new GsonAdaptersHashes())
50-
.registerTypeAdapterFactory(new GsonAdaptersKey())
51-
.registerTypeAdapterFactory(new GsonAdaptersRekorEntry())
52-
.registerTypeAdapterFactory(new GsonAdaptersRekorEntryBody())
53-
.registerTypeAdapterFactory(new GsonAdaptersRoot())
54-
.registerTypeAdapterFactory(new GsonAdaptersRootMeta())
55-
.registerTypeAdapterFactory(new GsonAdaptersRootRole())
56-
.registerTypeAdapterFactory(new GsonAdaptersSignature())
57-
.registerTypeAdapterFactory(new GsonAdaptersSnapshot())
58-
.registerTypeAdapterFactory(new GsonAdaptersSnapshotMeta())
59-
.registerTypeAdapterFactory(new GsonAdaptersTargets())
60-
.registerTypeAdapterFactory(new GsonAdaptersTargetMeta())
61-
.registerTypeAdapterFactory(new GsonAdaptersTimestamp())
62-
.registerTypeAdapterFactory(new GsonAdaptersTimestampMeta())
63-
.registerTypeAdapterFactory(new GsonAdaptersInTotoPayload())
64-
.disableHtmlEscaping()
65-
.create();
39+
private final GsonChecked gson =
40+
new GsonChecked(
41+
new GsonBuilder()
42+
.registerTypeAdapter(byte[].class, new GsonByteArrayAdapter())
43+
.registerTypeAdapter(
44+
LocalDateTime.class,
45+
(JsonDeserializer<LocalDateTime>)
46+
(json, type, jsonDeserializationContext) ->
47+
ZonedDateTime.parse(json.getAsJsonPrimitive().getAsString())
48+
.toLocalDateTime())
49+
// Immutables generated GSON Adapters in alphabetical order
50+
.registerTypeAdapterFactory(new GsonAdaptersDelegations())
51+
.registerTypeAdapterFactory(new GsonAdaptersDelegationRole())
52+
.registerTypeAdapterFactory(new GsonAdaptersHashes())
53+
.registerTypeAdapterFactory(new GsonAdaptersKey())
54+
.registerTypeAdapterFactory(new GsonAdaptersRekorEntry())
55+
.registerTypeAdapterFactory(new GsonAdaptersRekorEntryBody())
56+
.registerTypeAdapterFactory(new GsonAdaptersRoot())
57+
.registerTypeAdapterFactory(new GsonAdaptersRootMeta())
58+
.registerTypeAdapterFactory(new GsonAdaptersRootRole())
59+
.registerTypeAdapterFactory(new GsonAdaptersSignature())
60+
.registerTypeAdapterFactory(new GsonAdaptersSnapshot())
61+
.registerTypeAdapterFactory(new GsonAdaptersSnapshotMeta())
62+
.registerTypeAdapterFactory(new GsonAdaptersTargets())
63+
.registerTypeAdapterFactory(new GsonAdaptersTargetMeta())
64+
.registerTypeAdapterFactory(new GsonAdaptersTimestamp())
65+
.registerTypeAdapterFactory(new GsonAdaptersTimestampMeta())
66+
.registerTypeAdapterFactory(new GsonAdaptersInTotoPayload())
67+
.disableHtmlEscaping()
68+
.create());
6669

6770
@Override
68-
public Gson get() {
71+
public GsonChecked get() {
6972
return gson;
7073
}
7174
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright 2025 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.json;
17+
18+
public class JsonParseException extends Exception {
19+
public JsonParseException(Throwable cause) {
20+
super(cause);
21+
}
22+
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package dev.sigstore.rekor.client;
1717

18+
import dev.sigstore.json.JsonParseException;
1819
import java.io.IOException;
1920
import java.util.List;
2021
import java.util.Optional;
@@ -57,5 +58,5 @@ Optional<RekorEntry> getEntry(HashedRekordRequest hashedRekordRequest)
5758
*/
5859
List<String> searchEntry(
5960
String email, String hash, String publicKeyFormat, String publicKeyContent)
60-
throws IOException;
61+
throws IOException, JsonParseException;
6162
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.google.api.client.util.Preconditions;
2626
import dev.sigstore.http.HttpClients;
2727
import dev.sigstore.http.HttpParams;
28+
import dev.sigstore.json.JsonParseException;
2829
import dev.sigstore.trustroot.Service;
2930
import java.io.IOException;
3031
import java.net.URI;
@@ -130,7 +131,7 @@ public Optional<RekorEntry> getEntry(String UUID) throws IOException, RekorParse
130131
@Override
131132
public List<String> searchEntry(
132133
String email, String hash, String publicKeyFormat, String publicKeyContent)
133-
throws IOException {
134+
throws IOException, JsonParseException {
134135
URI rekorSearchEndpoint = uri.resolve(REKOR_INDEX_SEARCH_PATH);
135136

136137
HashMap<String, Object> publicKeyParams = null;

0 commit comments

Comments
 (0)