diff --git a/sigstore-java/src/main/java/dev/sigstore/encryption/Keys.java b/sigstore-java/src/main/java/dev/sigstore/encryption/Keys.java
index ff31dbb0..3ac633c4 100644
--- a/sigstore-java/src/main/java/dev/sigstore/encryption/Keys.java
+++ b/sigstore-java/src/main/java/dev/sigstore/encryption/Keys.java
@@ -15,19 +15,15 @@
*/
package dev.sigstore.encryption;
+import static org.bouncycastle.jce.ECPointUtil.*;
+
import com.google.common.annotations.VisibleForTesting;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
-import java.security.KeyFactory;
-import java.security.NoSuchAlgorithmException;
-import java.security.PublicKey;
-import java.security.Security;
-import java.security.spec.EncodedKeySpec;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.RSAPublicKeySpec;
-import java.security.spec.X509EncodedKeySpec;
+import java.security.*;
+import java.security.spec.*;
import java.util.logging.Logger;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1Sequence;
@@ -36,7 +32,10 @@
import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters;
import org.bouncycastle.crypto.params.RSAKeyParameters;
import org.bouncycastle.crypto.util.PublicKeyFactory;
+import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
+import org.bouncycastle.jce.spec.ECNamedCurveSpec;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
@@ -105,6 +104,63 @@ public static PublicKey parsePublicKey(byte[] keyBytes)
return keyFactory.generatePublic(publicKeySpec);
}
+ /**
+ * Valid values for scheme are:
+ *
+ *
+ * - ed25519
+ *
- ecdsa-sha2-nistp256
+ *
+ *
+ * {@see https://theupdateframework.github.io/specification/latest/index.html#role-role}
+ *
+ * @param contents
+ * @param scheme
+ * @return
+ * @throws NoSuchAlgorithmException
+ * @throws InvalidKeySpecException
+ */
+ public static PublicKey constructTufPublicKey(byte[] contents, String scheme)
+ throws NoSuchAlgorithmException, InvalidKeySpecException {
+ PublicKey publicKey = null;
+ switch (scheme) {
+ case "rsassa-pss-sha256":
+ throw new RuntimeException("rsassa-pss-sha256 not currently supported");
+ case "ed25519":
+ {
+ final KeyFactory kf = KeyFactory.getInstance("Ed25519");
+ final X509EncodedKeySpec keySpec = new X509EncodedKeySpec(contents);
+ publicKey = kf.generatePublic(keySpec);
+ break;
+ }
+ case "ecdsa-sha2-nistp256":
+ {
+ // spec for P-256 curve
+ ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("P-256");
+ // create a KeyFactory with ECDSA (Elliptic Curve Diffie-Hellman) algorithm and use
+ // BouncyCastle
+ // as the provider
+ KeyFactory kf = null;
+ try {
+ kf = KeyFactory.getInstance("ECDSA", BouncyCastleProvider.PROVIDER_NAME);
+ } catch (NoSuchProviderException e) {
+ throw new RuntimeException(e);
+ }
+
+ // code below just creates the public key from the bytes contained in publicK
+ // using the curve parameters (spec variable)
+ ECNamedCurveSpec params =
+ new ECNamedCurveSpec("P-256", spec.getCurve(), spec.getG(), spec.getN());
+ ECPoint point = decodePoint(params.getCurve(), contents);
+ ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(point, params);
+ publicKey = kf.generatePublic(pubKeySpec);
+ break;
+ }
+ }
+ return publicKey;
+ }
+
// https://stackoverflow.com/questions/42911637/get-publickey-from-key-bytes-not-knowing-the-key-algorithm
private static String extractKeyAlgorithm(AsymmetricKeyParameter keyParameters)
throws NoSuchAlgorithmException {
diff --git a/sigstore-java/src/main/java/dev/sigstore/encryption/signers/Verifiers.java b/sigstore-java/src/main/java/dev/sigstore/encryption/signers/Verifiers.java
index 9596bfc2..97fc5576 100644
--- a/sigstore-java/src/main/java/dev/sigstore/encryption/signers/Verifiers.java
+++ b/sigstore-java/src/main/java/dev/sigstore/encryption/signers/Verifiers.java
@@ -26,7 +26,7 @@ public static Verifier newVerifier(PublicKey publicKey) throws NoSuchAlgorithmEx
if (publicKey.getAlgorithm().equals("RSA")) {
return new RsaVerifier(publicKey);
}
- if (publicKey.getAlgorithm().equals("EC")) {
+ if (publicKey.getAlgorithm().equals("EC") || publicKey.getAlgorithm().equals("ECDSA")) {
return new EcdsaVerifier(publicKey);
}
throw new NoSuchAlgorithmException(
diff --git a/sigstore-java/src/main/java/dev/sigstore/json/GsonSupplier.java b/sigstore-java/src/main/java/dev/sigstore/json/GsonSupplier.java
index 6799df73..1ee5b0b6 100644
--- a/sigstore-java/src/main/java/dev/sigstore/json/GsonSupplier.java
+++ b/sigstore-java/src/main/java/dev/sigstore/json/GsonSupplier.java
@@ -55,12 +55,6 @@ public enum GsonSupplier implements Supplier {
.registerTypeAdapterFactory(new GsonAdaptersKey())
.registerTypeAdapterFactory(new GsonAdaptersRoot())
.registerTypeAdapterFactory(new GsonAdaptersTargets())
- .registerTypeAdapter(
- LocalDateTime.class,
- (JsonDeserializer)
- (json, type, jsonDeserializationContext) ->
- ZonedDateTime.parse(json.getAsJsonPrimitive().getAsString())
- .toLocalDateTime())
.disableHtmlEscaping()
.create();
diff --git a/sigstore-java/src/main/java/dev/sigstore/tuf/MetaFileExceedsMaxException.java b/sigstore-java/src/main/java/dev/sigstore/tuf/MetaFileExceedsMaxException.java
new file mode 100644
index 00000000..dfc96818
--- /dev/null
+++ b/sigstore-java/src/main/java/dev/sigstore/tuf/MetaFileExceedsMaxException.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2022 The Sigstore Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package dev.sigstore.tuf;
+
+/**
+ * Thrown when the Meta File exceeds the max allowable file size as configured in the {@link
+ * TufClient}
+ */
+public class MetaFileExceedsMaxException extends TufException {
+
+ private String fileUrl;
+ private int maxSize;
+
+ public MetaFileExceedsMaxException(String fileUrl, int maxSize) {
+ super(
+ String.format(
+ "The file at %s exceeds the client's max file size limit (%d)", fileUrl, maxSize));
+ this.fileUrl = fileUrl;
+ this.maxSize = maxSize;
+ }
+
+ public String getFileUrl() {
+ return fileUrl;
+ }
+
+ public int getMaxSize() {
+ return maxSize;
+ }
+}
diff --git a/sigstore-java/src/main/java/dev/sigstore/tuf/RoleVersionException.java b/sigstore-java/src/main/java/dev/sigstore/tuf/RoleVersionException.java
new file mode 100644
index 00000000..69dd0c18
--- /dev/null
+++ b/sigstore-java/src/main/java/dev/sigstore/tuf/RoleVersionException.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2022 The Sigstore Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package dev.sigstore.tuf;
+
+/** Thrown when the version of the latest downloaded role does not match the expectation. */
+public class RoleVersionException extends TufException {
+ private int expectedVersion;
+ private int foundVersion;
+
+ public RoleVersionException(int expectedVersion, int foundVersion) {
+ super(String.format("Expected version %d but found version %d", expectedVersion, foundVersion));
+ this.expectedVersion = expectedVersion;
+ this.foundVersion = foundVersion;
+ }
+
+ public int getExpectedVersion() {
+ return expectedVersion;
+ }
+
+ public int getFoundVersion() {
+ return foundVersion;
+ }
+}
diff --git a/sigstore-java/src/main/java/dev/sigstore/tuf/RootExpiredException.java b/sigstore-java/src/main/java/dev/sigstore/tuf/RootExpiredException.java
new file mode 100644
index 00000000..5da2fc8b
--- /dev/null
+++ b/sigstore-java/src/main/java/dev/sigstore/tuf/RootExpiredException.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2022 The Sigstore Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package dev.sigstore.tuf;
+
+import java.time.ZonedDateTime;
+
+/**
+ * Thrown when the local trusted root is expired and no valid root is found on the remote mirror.
+ */
+public class RootExpiredException extends TufException {
+ private String rootUrl;
+ private ZonedDateTime updateTime;
+ private ZonedDateTime rootExpirationTime;
+
+ public RootExpiredException(
+ String rootUrl, ZonedDateTime updateTime, ZonedDateTime rootExpirationTime) {
+ super(
+ String.format(
+ "Trusted root metadata is expired but no new versions are available at the "
+ + "mirror URL:(%s)\n update start time: %tc\n expired time: %tc)",
+ rootUrl, updateTime, rootExpirationTime));
+ this.rootUrl = rootUrl;
+ this.updateTime = updateTime;
+ this.rootExpirationTime = rootExpirationTime;
+ }
+
+ public String getRootUrl() {
+ return rootUrl;
+ }
+
+ public ZonedDateTime getUpdateTime() {
+ return updateTime;
+ }
+
+ public ZonedDateTime getRootExpirationTime() {
+ return rootExpirationTime;
+ }
+}
diff --git a/sigstore-java/src/main/java/dev/sigstore/tuf/SignatureVerificationException.java b/sigstore-java/src/main/java/dev/sigstore/tuf/SignatureVerificationException.java
new file mode 100644
index 00000000..7e8dd6cb
--- /dev/null
+++ b/sigstore-java/src/main/java/dev/sigstore/tuf/SignatureVerificationException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2022 The Sigstore Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package dev.sigstore.tuf;
+
+/** Thrown when the metadata has not been signed by enough of the allowed keys. */
+public class SignatureVerificationException extends TufException {
+ final int requiredSignatures, verifiedSignatures;
+
+ public SignatureVerificationException(int requiredSignatures, int verifiedSignatures) {
+ super(
+ String.format(
+ "The role has not been signed by enough keys. [Theshold: %d, Actual: %d]",
+ requiredSignatures, verifiedSignatures));
+ this.requiredSignatures = requiredSignatures;
+ this.verifiedSignatures = verifiedSignatures;
+ }
+}
diff --git a/sigstore-java/src/main/java/dev/sigstore/tuf/TufClient.java b/sigstore-java/src/main/java/dev/sigstore/tuf/TufClient.java
new file mode 100644
index 00000000..69f85e62
--- /dev/null
+++ b/sigstore-java/src/main/java/dev/sigstore/tuf/TufClient.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright 2022 The Sigstore Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package dev.sigstore.tuf;
+
+import static dev.sigstore.json.GsonSupplier.GSON;
+
+import com.google.api.client.http.GenericUrl;
+import com.google.api.client.json.gson.GsonFactory;
+import dev.sigstore.encryption.Keys;
+import dev.sigstore.encryption.signers.Verifiers;
+import dev.sigstore.http.HttpClients;
+import dev.sigstore.http.ImmutableHttpParams;
+import dev.sigstore.tuf.model.*;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SignatureException;
+import java.security.spec.InvalidKeySpecException;
+import java.time.Clock;
+import java.time.ZonedDateTime;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import org.bouncycastle.util.encoders.Hex;
+
+/**
+ * Tuf Client. Will eventually support configuring multiple remote mirrors and trust roots and
+ * mapping to specific targets.
+ */
+public class TufClient {
+
+ private static final int MAX_META_BYTES = 99 * 1024; // 99 KB
+ private static final int MAX_UPDATES =
+ 1024; // Limit the update loop to retrieve a max of 1024 subsequent versions.
+
+ protected Clock clock = Clock.systemUTC();
+
+ ZonedDateTime updateStartTime;
+
+ // https://theupdateframework.github.io/specification/latest/#detailed-client-workflow
+ public void updateRoot(Path trustedRootPath, URL mirror, Path localStore)
+ throws IOException, RootExpiredException, NoSuchAlgorithmException, InvalidKeySpecException,
+ InvalidKeyException, MetaFileExceedsMaxException, RoleVersionException,
+ SignatureVerificationException {
+ // 5.3.1) record the time at start and use for expiration checks consistently throughout the
+ // update.
+ updateStartTime = ZonedDateTime.now(clock);
+ Root trustedRoot;
+
+ // 5.3.2) load the trust metadata file (root.json), get version of root.json and the role
+ // signature threshold value
+
+ trustedRoot = GSON.get().fromJson(Files.readString(trustedRootPath), Root.class);
+ int baseVersion = trustedRoot.getSignedMeta().getVersion();
+ int nextVersion = baseVersion + 1;
+ // keep these for verifying the last step. 5.3.11
+ var preUpdateSnapshotRole = trustedRoot.getSignedMeta().getRoles().get("snapshot");
+ var preUpdateTimestampRole = trustedRoot.getSignedMeta().getRoles().get("timestamp");
+
+ while (nextVersion < baseVersion + MAX_UPDATES) {
+ // 5.3.3) download $version+1.root.json from mirror url (eventually obtained from remote.json
+ // or map.json) up MAX_META_BYTES. If the file is not available, or we have reached
+ // MAX_UPDATES number of root metadata files go to step 5.3.10
+ String nextVersionFileName = nextVersion + ".root.json";
+ GenericUrl nextVersionUrl = new GenericUrl(mirror + "/" + nextVersionFileName);
+ var req =
+ HttpClients.newHttpTransport(ImmutableHttpParams.builder().build())
+ .createRequestFactory(
+ request -> {
+ request.setParser(GsonFactory.getDefaultInstance().createJsonObjectParser());
+ })
+ .buildGetRequest(nextVersionUrl);
+ req.getHeaders().setAccept("application/json; api-version=2.0");
+ req.getHeaders().setContentType("application/json");
+ req.setThrowExceptionOnExecuteError(false);
+ var resp = req.execute();
+ if (resp.getStatusCode() == 404) {
+ // No newer versions, go to 5.3.10.
+ break;
+ }
+ if (resp.getStatusCode() != 200) {
+ throw new TufException(
+ String.format(
+ "Unexpected return from mirror. Status code: %s, status message: %s"
+ + resp.getStatusCode()
+ + resp.getStatusMessage()));
+ }
+ byte[] rootBytes = resp.getContent().readNBytes(MAX_META_BYTES);
+ if (rootBytes.length == MAX_META_BYTES && resp.getContent().read() != -1) {
+ throw new MetaFileExceedsMaxException(nextVersionUrl.toString(), MAX_META_BYTES);
+ }
+ var newRoot = GSON.get().fromJson(new String(rootBytes, StandardCharsets.UTF_8), Root.class);
+
+ // 5.3.4) we have a valid next version of the root.json. Check that the file has been signed
+ // by:
+ // a) a threshold (from step 2) of keys specified in the trusted metadata
+ // b) and a threshold of keys in the new root.json.
+ // Fail if either a or b aren't true.
+ var trustedRootKeys = trustedRoot.getSignedMeta().getKeys();
+ var newRootSignatures = newRoot.getSignatures();
+ byte[] newRootMetaBytes = newRoot.getCanonicalSignedBytes();
+ // Verify our new root meta against the trusted root keys.
+ RootRole trustedRootRoleMeta = trustedRoot.getSignedMeta().getRole(Role.Name.ROOT);
+ verifyDelegate(
+ newRootSignatures, trustedRootRoleMeta.getThreshold(), trustedRootKeys, newRootMetaBytes);
+
+ var newRootRoleMeta = newRoot.getSignedMeta().getRole(Role.Name.ROOT);
+ var newRootKeys = newRoot.getSignedMeta().getKeys();
+ // Verify our new root meta against the new root keys.
+ verifyDelegate(
+ newRootSignatures, newRootRoleMeta.getThreshold(), newRootKeys, newRootMetaBytes);
+
+ // 5.3.5) We've taken the liberty to modify 5.3.5 to just validate that the new root meta
+ // matches the version we pulled based off of the pattern {version}.root.json. We know due to
+ // the
+ // loop constraints that it is larger than the current version.
+ if (newRoot.getSignedMeta().getVersion() != nextVersion) {
+ throw new RoleVersionException(nextVersion, newRoot.getSignedMeta().getVersion());
+ }
+ // 5.3.7) set the trusted root metadata to the new root
+ trustedRoot = newRoot;
+ // 5.3.8) persist to repo
+ Path localTrustRoot = localStore.resolve("root.json");
+ if (localTrustRoot.toFile().exists()) {
+ // Backup the old root. (not sure if this is necessary)
+ Files.move(localTrustRoot, localStore.resolve((nextVersion - 1) + ".root.json"));
+ }
+ try (FileWriter fileWriter = new FileWriter(localTrustRoot.toFile())) {
+ fileWriter.write(GSON.get().toJson(trustedRoot));
+ }
+ // 5.3.9) see if there are more versions go back 5.3.3
+ nextVersion++;
+ }
+
+ // 5.3.10) Check expiration timestamp in trusted root is higher than fixed update start time. if
+ // not, throw an error.
+ ZonedDateTime expires = trustedRoot.getSignedMeta().getExpiresAsDate();
+ // If the
+ if (expires.isBefore(updateStartTime)) {
+ throw new RootExpiredException(mirror.toString(), updateStartTime, expires);
+ }
+ // 5.3.11) If the timestamp and / or snapshot keys have been rotated, then delete the trusted
+ // timestamp and snapshot metadata files.
+ if (hasNewKeys(preUpdateSnapshotRole, trustedRoot.getSignedMeta().getRole(Role.Name.SNAPSHOT))
+ || hasNewKeys(
+ preUpdateTimestampRole, trustedRoot.getSignedMeta().getRole(Role.Name.TIMESTAMP))) {
+ File snapshotMetaFile = localStore.resolve("snapshot.json").toFile();
+ if (snapshotMetaFile.exists()) {
+ Files.delete(snapshotMetaFile.toPath());
+ }
+ File timestampMetaFile = localStore.resolve("timestamp.json").toFile();
+ if (timestampMetaFile.exists()) {
+ Files.delete(timestampMetaFile.toPath());
+ }
+ }
+ }
+
+ private boolean hasNewKeys(RootRole oldRole, RootRole newRole) {
+ return newRole.getKeyids().stream().allMatch(s -> oldRole.getKeyids().contains(s));
+ }
+
+ /**
+ * Verifies that a delegate role has been signed by the threshold amount of keys.
+ *
+ * @param signatures these are the signatures on the role meta we're verifying
+ * @param threshold the minimum amount of signatures that must be verified by a trusted key.
+ * @param rootKeys a map of trusted keys used for signing, keyed by key_id
+ * @throws SignatureVerificationException if there are not enough verified signatures
+ */
+ private void verifyDelegate(
+ List signatures,
+ int threshold,
+ Map rootKeys,
+ byte[] verificationMaterial)
+ throws SignatureVerificationException, NoSuchAlgorithmException, InvalidKeyException,
+ InvalidKeySpecException {
+ // use set to not count the same key multiple times towards the threshold.
+ var goodSigs = new HashSet<>(signatures.size());
+ for (Signature signature : signatures) {
+ var key = rootKeys.get(signature.getKeyId());
+ if (key == null) {
+ // this signature doesn't match any keys, move on to the next.
+ continue;
+ }
+
+ // key bytes are in Hex not Base64! TUF also lies that their key is PEM Encoded. Don't believe
+ // them!
+ byte[] keyBytes = Hex.decode(key.getKeyVal().get("public"));
+ var pubKey = Keys.constructTufPublicKey(keyBytes, key.getScheme());
+ byte[] signatureBytes = Hex.decode(signature.getSignature());
+ try {
+ if (Verifiers.newVerifier(pubKey).verify(verificationMaterial, signatureBytes)) {
+ goodSigs.add(signature.getKeyId());
+ }
+ } catch (SignatureException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ if (goodSigs.size() < threshold) {
+ throw new SignatureVerificationException(threshold, goodSigs.size());
+ }
+ }
+
+ public void updateTimestamp() {
+ // 1) download the timestamp.json bytes up to few 10s of K max.
+
+ // 2) verify against threshold of keys as specified in trusted root,json
+
+ // 3) check that version of new timestamp.json is higher or equal than current, else fail.
+ // 3.2) check that timestamp.snapshot.version <= timestamp.version or fail
+
+ // 4) check expiration timestamp is after tuf update start time, else fail.
+
+ // 5) persist timestamp.json
+ }
+
+ public void updateSnapshot() {
+ // 1) download the snapshot.json bytes up to few 10s of K max.
+
+ // 2) check against timestamp.snapshot.hash
+
+ // 3) Check against threshold of root signing keys, else fail
+
+ // 4) Check snapshot.version matches timestamp.snapshot.version, else fail.
+
+ // 5) Ensure all targets and delegated targets in the trusted (old) snapshots file are less
+ // than or equal to the equivalent target in the new file. Check that no targets are missing
+ // in new file. Else fail.
+
+ // 6) Ensure expiration timestamp of snapshot is later than tuf update start time.
+
+ // 7) persist snapshot.
+ }
+
+ public void updateTargets() {
+ // 1) download the targets.json to max bytes
+
+ // 2) check hash against snapshot.targets.hash, else fail.
+
+ // 3) check against threshold of keys as specified by trusted root.json
+
+ // 4) check targets.version == snapshot.targets.version, else fail.
+
+ // 5) check expiration is after tuf update start time
+
+ // 6) persist targets metadata
+
+ // 7) starting at each top level target role:
+ // do pre-order DFS of metadata.
+ // skip already visited roles
+ // if maximum roles visited go to 8) downloading targets
+ // process delegations (not sure if we need this yet)
+
+ // 8) If target is missing metadata fail.
+
+ // 9) Download target up to length specified in bytes. verify against hash.
+
+ // Done!!
+ }
+}
diff --git a/sigstore-java/src/main/java/dev/sigstore/tuf/TufException.java b/sigstore-java/src/main/java/dev/sigstore/tuf/TufException.java
new file mode 100644
index 00000000..ebc7f4c5
--- /dev/null
+++ b/sigstore-java/src/main/java/dev/sigstore/tuf/TufException.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2022 The Sigstore Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package dev.sigstore.tuf;
+
+/** Catch-all TUF Exception. */
+public class TufException extends RuntimeException {
+ public TufException(String message) {
+ super(message);
+ }
+}
diff --git a/sigstore-java/src/main/java/dev/sigstore/tuf/model/Role.java b/sigstore-java/src/main/java/dev/sigstore/tuf/model/Role.java
index 89755439..814860f1 100644
--- a/sigstore-java/src/main/java/dev/sigstore/tuf/model/Role.java
+++ b/sigstore-java/src/main/java/dev/sigstore/tuf/model/Role.java
@@ -26,6 +26,17 @@
*/
public interface Role {
+ enum Name {
+ ROOT,
+ SNAPSHOT,
+ TIMESTAMP;
+
+ @Override
+ public String toString() {
+ return super.toString().toLowerCase();
+ }
+ }
+
/** A list of trusted keys for this role. */
List getKeyids();
diff --git a/sigstore-java/src/main/java/dev/sigstore/tuf/model/RootMeta.java b/sigstore-java/src/main/java/dev/sigstore/tuf/model/RootMeta.java
index 52626ca8..8751862c 100644
--- a/sigstore-java/src/main/java/dev/sigstore/tuf/model/RootMeta.java
+++ b/sigstore-java/src/main/java/dev/sigstore/tuf/model/RootMeta.java
@@ -31,10 +31,6 @@
@Value.Immutable
public interface RootMeta extends TufMeta {
- /** Returns the metadata type. In this case 'root'. */
- @Gson.Named("_type")
- String getType();
-
/**
* Typically {@code false} and unused for Sigstore TUF.
*
@@ -58,4 +54,8 @@ public interface RootMeta extends TufMeta {
* href="https://theupdateframework.io/metadata/#root-metadata-rootjson">role.
*/
Map getRoles();
+
+ default RootRole getRole(Role.Name name) {
+ return getRoles().get(name.toString());
+ }
}
diff --git a/sigstore-java/src/main/java/dev/sigstore/tuf/model/SignedTufMeta.java b/sigstore-java/src/main/java/dev/sigstore/tuf/model/SignedTufMeta.java
index 61c33db7..e51bfc67 100644
--- a/sigstore-java/src/main/java/dev/sigstore/tuf/model/SignedTufMeta.java
+++ b/sigstore-java/src/main/java/dev/sigstore/tuf/model/SignedTufMeta.java
@@ -15,7 +15,10 @@
*/
package dev.sigstore.tuf.model;
+import dev.sigstore.json.GsonSupplier;
+import java.io.IOException;
import java.util.List;
+import org.erdtman.jcs.JsonCanonicalizer;
import org.immutables.gson.Gson;
/**
@@ -30,4 +33,8 @@ public interface SignedTufMeta {
/** The role metadata that has been signed. */
@Gson.Named("signed")
T getSignedMeta();
+
+ default byte[] getCanonicalSignedBytes() throws IOException {
+ return new JsonCanonicalizer(GsonSupplier.GSON.get().toJson(getSignedMeta())).getEncodedUTF8();
+ }
}
diff --git a/sigstore-java/src/main/java/dev/sigstore/tuf/model/TufMeta.java b/sigstore-java/src/main/java/dev/sigstore/tuf/model/TufMeta.java
index 9b246715..08c40ba3 100644
--- a/sigstore-java/src/main/java/dev/sigstore/tuf/model/TufMeta.java
+++ b/sigstore-java/src/main/java/dev/sigstore/tuf/model/TufMeta.java
@@ -15,16 +15,23 @@
*/
package dev.sigstore.tuf.model;
-import java.time.LocalDateTime;
+import java.time.ZonedDateTime;
import org.immutables.gson.Gson;
/**
* Generic Tuf Metadata interface for various TUF resources such as Roles, Snapshots, and Targets.
*/
public interface TufMeta {
+ /** Returns the metadata type. In this case 'root'. */
+ @Gson.Named("_type")
+ String getType();
/** Date at which this data expires. */
- LocalDateTime getExpires();
+ String getExpires();
+
+ default ZonedDateTime getExpiresAsDate() {
+ return ZonedDateTime.parse(getExpires());
+ }
/** Spec version of this type. */
@Gson.Named("spec_version")
diff --git a/sigstore-java/src/main/resources/dev/sigstore/tuf/sigstore-tuf-root/1.root.json b/sigstore-java/src/main/resources/dev/sigstore/tuf/sigstore-tuf-root/1.root.json
index dcc71f96..41caf2c4 100644
--- a/sigstore-java/src/main/resources/dev/sigstore/tuf/sigstore-tuf-root/1.root.json
+++ b/sigstore-java/src/main/resources/dev/sigstore/tuf/sigstore-tuf-root/1.root.json
@@ -1,130 +1,120 @@
{
- "signatures": [
- {
- "keyid": "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
- "sig": "30450221008a35d51da0f845301a5eac98ad0df00a934f59b709c1eaf81c86be734d9356f80220742942325599749800f52675f6efe124345980a2a636c0dc76f9caf9fc3123b0"
- },
- {
- "keyid": "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62",
- "sig": "3045022100ef9157ece2a09baec1eab80adfc00b04da20b1f9a0d1b47c5dabc4506719ef2c022074f72acd57398e4ddc8c2a5040df902961e9615dca48f3fbe38cbb506e500066"
- },
- {
- "keyid": "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
- "sig": "30450220420fdc9a09cd069b8b15fd8db9cedf7d0dee75871bd1cfee77c926d4120a770002210097553b5ad0d6b4a13902ed37509638bb63a9009f78230cd56c802909ffbfead7"
- },
- {
- "keyid": "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
- "sig": "304502202aaf32e66f90752f658672b085ecfe45cc1ad31ee6cf5c9ad05f3267685f8d88022100b5df02acdaa371123db9d7a42219553fe079b230b168833e951be7ee56ded347"
- },
- {
- "keyid": "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209",
- "sig": "304402205d420c7d05c58980c1c9f7d221f53b5334aae27a447d2a91c2ceddd685269749022039ec83e51f8e1779d7f0142dfa4a5bbecfe327fc0b91b7416090fea2416fd53a"
- }
- ],
- "signed": {
- "_type": "root",
- "consistent_snapshot": false,
- "expires": "2021-12-18T13:28:12.99008-06:00",
- "keys": {
- "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97": {
- "keyid_hash_algorithms": [
- "sha256",
- "sha512"
- ],
- "keytype": "ecdsa-sha2-nistp256",
- "keyval": {
- "public": "04cbc5cab2684160323c25cd06c3307178a6b1d1c9b949328453ae473c5ba7527e35b13f298b41633382241f3fd8526c262d43b45adee5c618fa0642c82b8a9803"
- },
- "scheme": "ecdsa-sha2-nistp256"
- },
- "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62": {
- "keyid_hash_algorithms": [
- "sha256",
- "sha512"
- ],
- "keytype": "ecdsa-sha2-nistp256",
- "keyval": {
- "public": "04a71aacd835dc170ba6db3fa33a1a33dee751d4f8b0217b805b9bd3242921ee93672fdcfd840576c5bb0dc0ed815edf394c1ee48c2b5e02485e59bfc512f3adc7"
- },
- "scheme": "ecdsa-sha2-nistp256"
- },
- "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b": {
- "keyid_hash_algorithms": [
- "sha256",
- "sha512"
- ],
- "keytype": "ecdsa-sha2-nistp256",
- "keyval": {
- "public": "04117b33dd265715bf23315e368faa499728db8d1f0a377070a1c7b1aba2cc21be6ab1628e42f2cdd7a35479f2dce07b303a8ba646c55569a8d2a504ba7e86e447"
- },
- "scheme": "ecdsa-sha2-nistp256"
- },
- "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb": {
- "keyid_hash_algorithms": [
- "sha256",
- "sha512"
- ],
- "keytype": "ecdsa-sha2-nistp256",
- "keyval": {
- "public": "04cc1cd53a61c23e88cc54b488dfae168a257c34fac3e88811c55962b24cffbfecb724447999c54670e365883716302e49da57c79a33cd3e16f81fbc66f0bcdf48"
- },
- "scheme": "ecdsa-sha2-nistp256"
- },
- "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209": {
- "keyid_hash_algorithms": [
- "sha256",
- "sha512"
- ],
- "keytype": "ecdsa-sha2-nistp256",
- "keyval": {
- "public": "048a78a44ac01099890d787e5e62afc29c8ccb69a70ec6549a6b04033b0a8acbfb42ab1ab9c713d225cdb52b858886cf46c8e90a7f3b9e6371882f370c259e1c5b"
- },
- "scheme": "ecdsa-sha2-nistp256"
- }
- },
- "roles": {
- "root": {
- "keyids": [
- "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
- "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62",
- "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
- "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
- "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209"
- ],
- "threshold": 3
- },
- "snapshot": {
- "keyids": [
- "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
- "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62",
- "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
- "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
- "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209"
- ],
- "threshold": 3
- },
- "targets": {
- "keyids": [
- "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
- "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62",
- "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
- "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
- "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209"
- ],
- "threshold": 3
- },
- "timestamp": {
- "keyids": [
- "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
- "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62",
- "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
- "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
- "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209"
- ],
- "threshold": 3
- }
- },
- "spec_version": "1.0",
- "version": 1
- }
+ "_type": "root",
+ "consistent_snapshot": false,
+ "expires": "2023-01-12T18:22:02",
+ "keys": {
+ "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04cbc5cab2684160323c25cd06c3307178a6b1d1c9b949328453ae473c5ba7527e35b13f298b41633382241f3fd8526c262d43b45adee5c618fa0642c82b8a9803"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "75e867ab10e121fdef32094af634707f43ddd79c6bab8ad6c5ab9f03f4ea8c90": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04d2086b87dd8bc3562bde27465795aa0ad30307c0b1f83f21742e30d992cd2299554685462ec9186b782178cc8e8e227c90f8b5e5a436fffecffa88fb52f24f1b"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "b6710623a30c010738e64c5209d367df1c0a18cf90e6ab5292fb01680f83453d": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04fa1a3e42f2300cd3c5487a61509348feb1e936920fef2f83b7cd5dbe7ba045f538725ab8f18a666e6233edb7e0db8766c8dc336633449c5e1bbe0c182b02df0b"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04117b33dd265715bf23315e368faa499728db8d1f0a377070a1c7b1aba2cc21be6ab1628e42f2cdd7a35479f2dce07b303a8ba646c55569a8d2a504ba7e86e447"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04cc1cd53a61c23e88cc54b488dfae168a257c34fac3e88811c55962b24cffbfecb724447999c54670e365883716302e49da57c79a33cd3e16f81fbc66f0bcdf48"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "048a78a44ac01099890d787e5e62afc29c8ccb69a70ec6549a6b04033b0a8acbfb42ab1ab9c713d225cdb52b858886cf46c8e90a7f3b9e6371882f370c259e1c5b"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "fc61191ba8a516fe386c7d6c97d918e1d241e1589729add09b122725b8c32451": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "044c7793ab74b9ddd713054e587b8d9c75c5f6025633d0fef7ca855ed5b8d5a474b23598fe33eb4a63630d526f74d4bdaec8adcb51993ed65652d651d7c49203eb"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
+ "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
+ "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
+ "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209",
+ "75e867ab10e121fdef32094af634707f43ddd79c6bab8ad6c5ab9f03f4ea8c90"
+ ],
+ "threshold": 3
+ },
+ "snapshot": {
+ "keyids": [
+ "fc61191ba8a516fe386c7d6c97d918e1d241e1589729add09b122725b8c32451"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
+ "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
+ "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
+ "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209",
+ "75e867ab10e121fdef32094af634707f43ddd79c6bab8ad6c5ab9f03f4ea8c90"
+ ],
+ "threshold": 3
+ },
+ "timestamp": {
+ "keyids": [
+ "b6710623a30c010738e64c5209d367df1c0a18cf90e6ab5292fb01680f83453d"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 1
}
\ No newline at end of file
diff --git a/sigstore-java/src/test/java/dev/sigstore/encryption/KeysTest.java b/sigstore-java/src/test/java/dev/sigstore/encryption/KeysTest.java
index 16026de0..dee0c9c3 100644
--- a/sigstore-java/src/test/java/dev/sigstore/encryption/KeysTest.java
+++ b/sigstore-java/src/test/java/dev/sigstore/encryption/KeysTest.java
@@ -15,13 +15,15 @@
*/
package dev.sigstore.encryption;
-import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.*;
import com.google.common.io.Resources;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
+import org.bouncycastle.util.encoders.Hex;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledForJreRange;
@@ -83,6 +85,77 @@ void parsePublicKey_dsaShouldFail() {
() -> Keys.parsePublicKey(Resources.toByteArray(Resources.getResource(DSA_PUB_PATH))));
}
+ @Test
+ void parseTufPublicKey_ecdsa()
+ throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException {
+ PublicKey key =
+ Keys.constructTufPublicKey(
+ Hex.decode(
+ "04cbc5cab2684160323c25cd06c3307178a6b1d1c9b949328453ae473c5ba7527e35b13f298b41633382241f3fd8526c262d43b45adee5c618fa0642c82b8a9803"),
+ "ecdsa-sha2-nistp256");
+ assertNotNull(key);
+ assertEquals(key.getAlgorithm(), "ECDSA");
+ }
+
+ @Test
+ void parseTufPublicKey_ecdsaBad()
+ throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException {
+ Assertions.assertThrows(
+ RuntimeException.class,
+ () -> {
+ Keys.constructTufPublicKey(
+ Hex.decode(
+ "04cbcdcab2684160323c25cd06c3307178a6b1d1c9b949328453ae473c5ba7527e35b13f298b41633382241f3fd8526c262d43b45adee5c618fa0642c82b8a9803"),
+ "ecdsa-sha2-nistp256");
+ });
+ }
+
+ @Test
+ void parseTufPublicKey_ed25519()
+ throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException {
+ // {@code step crypto keypair ed25519.pub /dev/null --kty OKP --curve Ed25519}
+ // copy just the key part out of ed25519.pub removing PEM header and footer
+ // {@code echo $(copied content) | base64 -d | hexdump -v -e '/1 "%02x" '}
+ PublicKey key =
+ Keys.constructTufPublicKey(
+ Hex.decode(
+ "302a300506032b65700321008b2e369230c3b97f4627fd6a59eb054a83ec15ed929ab3d983a40ffd322a223d"),
+ "ed25519");
+ assertNotNull(key);
+ assertEquals(key.getAlgorithm(), "Ed25519");
+ }
+
+ @Test
+ void parseTufPublicKey_ed25519Bad() {
+
+ try {
+ PublicKey key =
+ Keys.constructTufPublicKey(
+ Hex.decode(
+ "302b300506032b65700321008b2e369230c3b97f4627fd6a59eb054a83ec15ed929ab3d983a40ffd322a223d"),
+ "ed25519");
+ fail();
+ } catch (Exception e) {
+ }
+ }
+
+ @Test
+ void parseTufPublicKey_rsa()
+ throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException {
+ // {@code step crypto keypair ed25519.pub /dev/null --kty OKP --curve Ed25519}
+ // copy just the key part out of ed25519.pub removing PEM header and footer
+ // {@code echo $(copied content) | base64 -d | hexdump -v -e '/1 "%02x" '}
+ try {
+ PublicKey key =
+ Keys.constructTufPublicKey(
+ Hex.decode(
+ "302a300506032b65700321008b2e369230c3b97f4627fd6a59eb054a83ec15ed929ab3d983a40ffd322a223d"),
+ "rsassa-pss-sha256");
+ fail();
+ } catch (RuntimeException e) {
+ }
+ }
+
@Test
void testGetJavaVersion() {
assertEquals(1, Keys.getJavaVersion("1.6.0_23"));
diff --git a/sigstore-java/src/test/java/dev/sigstore/tuf/TufClientTest.java b/sigstore-java/src/test/java/dev/sigstore/tuf/TufClientTest.java
new file mode 100644
index 00000000..75d1e3f6
--- /dev/null
+++ b/sigstore-java/src/test/java/dev/sigstore/tuf/TufClientTest.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2022 The Sigstore Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package dev.sigstore.tuf;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import dev.sigstore.json.GsonSupplier;
+import dev.sigstore.tuf.model.Root;
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.time.Clock;
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.handler.ResourceHandler;
+import org.eclipse.jetty.util.resource.Resource;
+import org.jetbrains.annotations.NotNull;
+import org.junit.jupiter.api.*;
+import org.junit.jupiter.api.io.TempDir;
+
+class TufClientTest {
+
+ public static final String TEST_STATIC_UPDATE_TIME = "2022-09-09T13:37:00.00Z";
+ static Server remote;
+ static String remoteUrl;
+ @TempDir Path localStore;
+ @TempDir static Path localMirror;
+ Path tufTestData = Paths.get("src/test/resources/dev/sigstore/tuf/");
+
+ @BeforeAll
+ static void startRemoteResourceServer() throws Exception {
+ remote = new Server();
+ ServerConnector connector = new ServerConnector(remote);
+ connector.setHost("127.0.0.1");
+ remote.addConnector(connector);
+ ResourceHandler handler = new ResourceHandler();
+ handler.setBaseResource(Resource.newResource(localMirror.toUri()));
+ handler.setDirectoriesListed(true);
+ handler.setAcceptRanges(true);
+ remote.setHandler(handler);
+ remote.start();
+ remoteUrl = "http://" + connector.getHost() + ":" + connector.getLocalPort();
+ System.out.println("TUF local server listening on: " + remoteUrl);
+ }
+
+ @Test
+ public void testRootUpdate_fromProdData()
+ throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException {
+ setupMirror("remote-repo-prod", "1.root.json", "2.root.json", "3.root.json", "4.root.json");
+ var client = createTufClient();
+ Path trustedRoot = tufTestData.resolve("trusted-root.json");
+ client.updateRoot(trustedRoot, new URL(remoteUrl), localStore);
+ assertStoreContains("root.json");
+ Root oldRoot = loadRoot(trustedRoot);
+ Root newRoot = loadRoot(localStore.resolve("root.json"));
+ assertRootVersionIncreased(oldRoot, newRoot);
+ assertRootNotExpired(newRoot);
+ }
+
+ @Test
+ public void testRootUpdate_notEnoughSignatures()
+ throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException {
+ setupMirror("remote-repo-unsigned", "2.root.json");
+ var client = createTufClient();
+ try {
+ client.updateRoot(tufTestData.resolve("trusted-root.json"), new URL(remoteUrl), localStore);
+ fail();
+ } catch (SignatureVerificationException e) {
+ assertEquals(3, e.requiredSignatures);
+ assertEquals(0, e.verifiedSignatures);
+ }
+ }
+
+ @Test
+ public void testRootUpdate_expiredRoot()
+ throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException {
+ setupMirror("remote-repo-expired", "2.root.json");
+ var client = createTufClient();
+ try {
+ client.updateRoot(tufTestData.resolve("trusted-root.json"), new URL(remoteUrl), localStore);
+ fail();
+ } catch (RootExpiredException e) {
+ assertEquals(ZonedDateTime.parse(TEST_STATIC_UPDATE_TIME), e.getUpdateTime());
+ // straight from remote-repo-expired/2.root.json
+ assertEquals(
+ ZonedDateTime.parse("2022-05-11T19:09:02.663975009Z"), e.getRootExpirationTime());
+ }
+ }
+
+ @Test
+ public void testRootUpdate_inconsistentVersion()
+ throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException {
+ setupMirror("remote-repo-inconsistent-version", "2.root.json");
+ var client = createTufClient();
+ try {
+ client.updateRoot(tufTestData.resolve("trusted-root.json"), new URL(remoteUrl), localStore);
+ fail();
+ } catch (RoleVersionException e) {
+ assertEquals(2, e.getExpectedVersion());
+ assertEquals(3, e.getFoundVersion());
+ }
+ }
+
+ @Test
+ public void testRootUpdate_metaFileTooBig()
+ throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException {
+ setupMirror("remote-repo-meta-file-too-big", "2.root.json");
+ var client = createTufClient();
+ try {
+ client.updateRoot(tufTestData.resolve("trusted-root.json"), new URL(remoteUrl), localStore);
+ fail();
+ } catch (MetaFileExceedsMaxException e) {
+ }
+ }
+
+ @NotNull
+ private static TufClient createTufClient() {
+ TufClient client = new TufClient();
+ // set a fixed time to ensure test results are reproducible.
+ client.clock = Clock.fixed(Instant.parse(TEST_STATIC_UPDATE_TIME), ZoneOffset.UTC);
+ return client;
+ }
+
+ private void setupMirror(String repoFolder, String... files) throws IOException {
+ for (String file : files) {
+ Files.copy(tufTestData.resolve(repoFolder).resolve(file), localMirror.resolve(file));
+ }
+ }
+
+ private void assertRootNotExpired(Root root) {
+ assertTrue(
+ root.getSignedMeta()
+ .getExpiresAsDate()
+ .isAfter(ZonedDateTime.parse(TEST_STATIC_UPDATE_TIME)));
+ }
+
+ private void assertRootVersionIncreased(Root oldRoot, Root newRoot) throws IOException {
+ assertTrue(oldRoot.getSignedMeta().getVersion() <= newRoot.getSignedMeta().getVersion());
+ }
+
+ private void assertStoreContains(String resource) {
+ assertTrue(localStore.resolve(resource).toFile().exists());
+ }
+
+ Root loadRoot(Path rootPath) throws IOException {
+ return GsonSupplier.GSON.get().fromJson(Files.readString(rootPath), Root.class);
+ }
+
+ @AfterEach
+ void clearLocalMirror() {
+ for (File file : localMirror.toFile().listFiles()) {
+ file.delete();
+ }
+ }
+
+ @AfterAll
+ static void shutdownRemoteResourceServer() throws Exception {
+ remote.stop();
+ }
+}
diff --git a/sigstore-java/src/test/java/dev/sigstore/tuf/model/TestTufJsonLoading.java b/sigstore-java/src/test/java/dev/sigstore/tuf/model/TestTufJsonLoading.java
index 0cd3c2dc..21cae60a 100644
--- a/sigstore-java/src/test/java/dev/sigstore/tuf/model/TestTufJsonLoading.java
+++ b/sigstore-java/src/test/java/dev/sigstore/tuf/model/TestTufJsonLoading.java
@@ -23,7 +23,7 @@
import java.io.IOException;
import java.io.Reader;
import java.nio.charset.Charset;
-import java.time.LocalDateTime;
+import java.time.ZonedDateTime;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.Test;
@@ -52,7 +52,7 @@ public void loadRootJson() throws IOException {
RootMeta signedMeta = trustRoot.getSignedMeta();
assertNotNull(signedMeta);
assertEquals(false, signedMeta.getConsistentSnapshot());
- assertEquals(LocalDateTime.of(2023, 01, 12, 18, 22, 2), signedMeta.getExpires());
+ assertEquals("2023-01-12T18:22:02Z", signedMeta.getExpires());
assertEquals(7, signedMeta.getKeys().entrySet().size());
Key key =
signedMeta
@@ -124,7 +124,8 @@ public void loadTargetsJson() throws IOException {
"3044022100d54e28736b8ac066410aa4b1560b2244d1c631a8b0192420c34e2db07248ed54021f1193d7e7ecbb0045533ce912f7685fc66cff42a80b56b3e1e7245c6c542bf1",
targets.getSignatures().get(0).getSignature());
TargetMeta signedMeta = targets.getSignedMeta();
- assertEquals(LocalDateTime.of(2023, 01, 12, 18, 22, 3), signedMeta.getExpires());
+ assertEquals("2023-01-12T18:22:03Z", signedMeta.getExpires());
+ assertEquals(ZonedDateTime.parse("2023-01-12T18:22:03Z"), signedMeta.getExpiresAsDate());
assertEquals("1.0", signedMeta.getSpecVersion());
assertEquals(4, signedMeta.getVersion());
Delegations delegations = signedMeta.getDelegations();
diff --git a/src/test/resources/dev/sigstore/tuf/README.md b/src/test/resources/dev/sigstore/tuf/README.md
new file mode 100644
index 00000000..e69de29b
diff --git a/src/test/resources/dev/sigstore/tuf/remote-repo-expired/2.root.json b/src/test/resources/dev/sigstore/tuf/remote-repo-expired/2.root.json
new file mode 100644
index 00000000..386ebe62
--- /dev/null
+++ b/src/test/resources/dev/sigstore/tuf/remote-repo-expired/2.root.json
@@ -0,0 +1,144 @@
+{
+ "signatures": [
+ {
+ "keyid": "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
+ "sig": "3046022100d3ea59490b253beae0926c6fa63f54336dea1ed700555be9f27ff55cd347639c0221009157d1ba012cead81948a4ab777d355451d57f5c4a2d333fc68d2e3f358093c2"
+ },
+ {
+ "keyid": "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62",
+ "sig": "304502206eaef40564403ce572c6d062e0c9b0aab5e0223576133e081e1b495e8deb9efd02210080fd6f3464d759601b4afec596bbd5952f3a224cd06ed1cdfc3c399118752ba2"
+ },
+ {
+ "keyid": "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
+ "sig": "304502207baace02f56d8e6069f10b6ff098a26e7f53a7f9324ad62cffa0557bdeb9036c022100fb3032baaa090d0040c3f2fd872571c84479309b773208601d65948df87a9720"
+ },
+ {
+ "keyid": "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
+ "sig": "304402205180c01905505dd88acd7a2dad979dd75c979b3722513a7bdedac88c6ae8dbeb022056d1ddf7a192f0b1c2c90ff487de2fb3ec9f0c03f66ea937c78d3b6a493504ca"
+ },
+ {
+ "keyid": "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209",
+ "sig": "3046022100c8806d4647c514d80fd8f707d3369444c4fd1d0812a2d25f828e564c99790e3f022100bb51f12e862ef17a7d3da2ac103bebc5c7e792237006c4cafacd76267b249c2f"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": false,
+ "expires": "2022-05-11T19:09:02.663975009Z",
+ "keys": {
+ "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04cbc5cab2684160323c25cd06c3307178a6b1d1c9b949328453ae473c5ba7527e35b13f298b41633382241f3fd8526c262d43b45adee5c618fa0642c82b8a9803"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "b6710623a30c010738e64c5209d367df1c0a18cf90e6ab5292fb01680f83453d": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04fa1a3e42f2300cd3c5487a61509348feb1e936920fef2f83b7cd5dbe7ba045f538725ab8f18a666e6233edb7e0db8766c8dc336633449c5e1bbe0c182b02df0b"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04a71aacd835dc170ba6db3fa33a1a33dee751d4f8b0217b805b9bd3242921ee93672fdcfd840576c5bb0dc0ed815edf394c1ee48c2b5e02485e59bfc512f3adc7"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04117b33dd265715bf23315e368faa499728db8d1f0a377070a1c7b1aba2cc21be6ab1628e42f2cdd7a35479f2dce07b303a8ba646c55569a8d2a504ba7e86e447"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04cc1cd53a61c23e88cc54b488dfae168a257c34fac3e88811c55962b24cffbfecb724447999c54670e365883716302e49da57c79a33cd3e16f81fbc66f0bcdf48"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "048a78a44ac01099890d787e5e62afc29c8ccb69a70ec6549a6b04033b0a8acbfb42ab1ab9c713d225cdb52b858886cf46c8e90a7f3b9e6371882f370c259e1c5b"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "fc61191ba8a516fe386c7d6c97d918e1d241e1589729add09b122725b8c32451": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "044c7793ab74b9ddd713054e587b8d9c75c5f6025633d0fef7ca855ed5b8d5a474b23598fe33eb4a63630d526f74d4bdaec8adcb51993ed65652d651d7c49203eb"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
+ "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62",
+ "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
+ "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
+ "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209"
+ ],
+ "threshold": 3
+ },
+ "snapshot": {
+ "keyids": [
+ "fc61191ba8a516fe386c7d6c97d918e1d241e1589729add09b122725b8c32451"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
+ "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62",
+ "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
+ "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
+ "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209"
+ ],
+ "threshold": 3
+ },
+ "timestamp": {
+ "keyids": [
+ "b6710623a30c010738e64c5209d367df1c0a18cf90e6ab5292fb01680f83453d"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 2
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources/dev/sigstore/tuf/remote-repo-hex-to-ecdsa-migration/1.root.json b/src/test/resources/dev/sigstore/tuf/remote-repo-hex-to-ecdsa-migration/1.root.json
new file mode 100644
index 00000000..7f13e2c4
--- /dev/null
+++ b/src/test/resources/dev/sigstore/tuf/remote-repo-hex-to-ecdsa-migration/1.root.json
@@ -0,0 +1,87 @@
+{
+ "signed": {
+ "_type": "root",
+ "spec_version": "1.0",
+ "version": 1,
+ "expires": "2022-12-08T17:26:05Z",
+ "keys": {
+ "04add5f7774bed64bae1a44fddb436cd66f630a879950cd4c3c5f5a8dcb69a75": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "bc46288ad651147bce0285b0082cb4cd934e232e9f0a2b83bfd69cbf849d7356"
+ }
+ },
+ "5c9ed687d43d731bb5048afcbb4f766deadbc8111255ec337637da1a45374347": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "c2fffff49d7364960f59727adf0295b171709eec578700fd35a2d8123fa5747d"
+ }
+ },
+ "912a13157d911e2176fbeaf319b7029171490b92ca9b65fcef7006336f5929e4": {
+ "keytype": "ecdsa-sha2-nistp256",
+ "scheme": "ecdsa-sha2-nistp256",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "04514c95b6170cbcf1a9ffeed93def29420d9dffa6194e96d379cd37a2c858f2b6a19e91be32ac99256c5c9bcdf3c061a8faf8132177a31ced5bf1be327b932ec0"
+ }
+ },
+ "959ffa7b34b7c47f351eb886e888a52fade0045c17a0a484e1c41736047f4b79": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "449f56ec5d9de1ec4c831e4cf8e6653130602ed3bdbab7f65d63442530d5f941"
+ }
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "912a13157d911e2176fbeaf319b7029171490b92ca9b65fcef7006336f5929e4"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "5c9ed687d43d731bb5048afcbb4f766deadbc8111255ec337637da1a45374347"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "959ffa7b34b7c47f351eb886e888a52fade0045c17a0a484e1c41736047f4b79"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "04add5f7774bed64bae1a44fddb436cd66f630a879950cd4c3c5f5a8dcb69a75"
+ ],
+ "threshold": 1
+ }
+ },
+ "consistent_snapshot": false
+ },
+ "signatures": [
+ {
+ "keyid": "912a13157d911e2176fbeaf319b7029171490b92ca9b65fcef7006336f5929e4",
+ "sig": "304502204f21aa89a7b8e44cf9a7a98d145831de734438d8de24ecf6dd777c1bc7762550022100fcbde7461b93b1ba1a00487cad7f102e6100257f59c0071e4a3a1f39789c10d3"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/test/resources/dev/sigstore/tuf/remote-repo-hex-to-ecdsa-migration/root.json b/src/test/resources/dev/sigstore/tuf/remote-repo-hex-to-ecdsa-migration/root.json
new file mode 100644
index 00000000..5c72f9d0
--- /dev/null
+++ b/src/test/resources/dev/sigstore/tuf/remote-repo-hex-to-ecdsa-migration/root.json
@@ -0,0 +1,91 @@
+{
+ "signed": {
+ "_type": "root",
+ "spec_version": "1.0",
+ "version": 2,
+ "expires": "2023-09-08T16:26:05Z",
+ "keys": {
+ "04add5f7774bed64bae1a44fddb436cd66f630a879950cd4c3c5f5a8dcb69a75": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "bc46288ad651147bce0285b0082cb4cd934e232e9f0a2b83bfd69cbf849d7356"
+ }
+ },
+ "5c9ed687d43d731bb5048afcbb4f766deadbc8111255ec337637da1a45374347": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "c2fffff49d7364960f59727adf0295b171709eec578700fd35a2d8123fa5747d"
+ }
+ },
+ "959ffa7b34b7c47f351eb886e888a52fade0045c17a0a484e1c41736047f4b79": {
+ "keytype": "ed25519",
+ "scheme": "ed25519",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "449f56ec5d9de1ec4c831e4cf8e6653130602ed3bdbab7f65d63442530d5f941"
+ }
+ },
+ "c4bfacf273fa543cdf24951a173d09f06d69badbd55ed8b67ff42e5a27250643": {
+ "keytype": "ecdsa-sha2-nistp256",
+ "scheme": "ecdsa-sha2-nistp256",
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keyval": {
+ "public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUUyVthcMvPGp/+7ZPe8pQg2d/6YZ\nTpbTec03oshY8rahnpG+MqyZJWxcm83zwGGo+vgTIXejHO1b8b4ye5MuwA==\n-----END PUBLIC KEY-----\n"
+ }
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "c4bfacf273fa543cdf24951a173d09f06d69badbd55ed8b67ff42e5a27250643"
+ ],
+ "threshold": 1
+ },
+ "snapshot": {
+ "keyids": [
+ "5c9ed687d43d731bb5048afcbb4f766deadbc8111255ec337637da1a45374347"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "959ffa7b34b7c47f351eb886e888a52fade0045c17a0a484e1c41736047f4b79"
+ ],
+ "threshold": 1
+ },
+ "timestamp": {
+ "keyids": [
+ "04add5f7774bed64bae1a44fddb436cd66f630a879950cd4c3c5f5a8dcb69a75"
+ ],
+ "threshold": 1
+ }
+ },
+ "consistent_snapshot": false
+ },
+ "signatures": [
+ {
+ "keyid": "912a13157d911e2176fbeaf319b7029171490b92ca9b65fcef7006336f5929e4",
+ "sig": "3044022069611604106fd24f2911ce73d27efda501e8de765f5cc9df289397a428eb095602202aa68fcb00c0ceb87d12ff1b680b8c1b9ca9aef996ebf69a46d235591878f378"
+ },
+ {
+ "keyid": "c4bfacf273fa543cdf24951a173d09f06d69badbd55ed8b67ff42e5a27250643",
+ "sig": "30460221009da029a6837e4be205ea2a5ad1c3de59ba6612580f7248c5cd54ea232fbadf43022100dc9789013fb1d9697dc75ea098a124d3d7780b5a7b405ddbd55eb98ee5975591"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/test/resources/dev/sigstore/tuf/remote-repo-inconsistent-version/2.root.json b/src/test/resources/dev/sigstore/tuf/remote-repo-inconsistent-version/2.root.json
new file mode 100644
index 00000000..8d69c515
--- /dev/null
+++ b/src/test/resources/dev/sigstore/tuf/remote-repo-inconsistent-version/2.root.json
@@ -0,0 +1,136 @@
+{
+ "signatures": [
+ {
+ "keyid": "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
+ "sig": "3046022100e7a80e4b03eb8768999d20f104925fd9149faf3f6f73ee80f8c2e8d5f998f48c022100d3f01eb8effee202a244e710dca09530b9c57c5e510ab35172bd5eddd373ccc8"
+ },
+ {
+ "keyid": "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
+ "sig": "304502200e45fde5cf750f8c533c4f259eb1469510600993b98ae2c3cb8f1922cda96e27022100f5151760d0ef0882a96c2531ccd9f5e4a7ff2b259d8eb34ead8bfdf60cb52fee"
+ },
+ {
+ "keyid": "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
+ "sig": "304502205a7ebeac3617bfb1aca957a6f74d37a02f2854afa54e5103fb3c891bb25836db022100f06614ca8d21f968e45edc29f826d8dbeed07c51d4cb473a734a2036171900de"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": false,
+ "expires": "2022-11-10T21:58:09.733402317Z",
+ "keys": {
+ "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04cbc5cab2684160323c25cd06c3307178a6b1d1c9b949328453ae473c5ba7527e35b13f298b41633382241f3fd8526c262d43b45adee5c618fa0642c82b8a9803"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "b6710623a30c010738e64c5209d367df1c0a18cf90e6ab5292fb01680f83453d": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04fa1a3e42f2300cd3c5487a61509348feb1e936920fef2f83b7cd5dbe7ba045f538725ab8f18a666e6233edb7e0db8766c8dc336633449c5e1bbe0c182b02df0b"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04a71aacd835dc170ba6db3fa33a1a33dee751d4f8b0217b805b9bd3242921ee93672fdcfd840576c5bb0dc0ed815edf394c1ee48c2b5e02485e59bfc512f3adc7"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04117b33dd265715bf23315e368faa499728db8d1f0a377070a1c7b1aba2cc21be6ab1628e42f2cdd7a35479f2dce07b303a8ba646c55569a8d2a504ba7e86e447"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04cc1cd53a61c23e88cc54b488dfae168a257c34fac3e88811c55962b24cffbfecb724447999c54670e365883716302e49da57c79a33cd3e16f81fbc66f0bcdf48"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "048a78a44ac01099890d787e5e62afc29c8ccb69a70ec6549a6b04033b0a8acbfb42ab1ab9c713d225cdb52b858886cf46c8e90a7f3b9e6371882f370c259e1c5b"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "fc61191ba8a516fe386c7d6c97d918e1d241e1589729add09b122725b8c32451": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "044c7793ab74b9ddd713054e587b8d9c75c5f6025633d0fef7ca855ed5b8d5a474b23598fe33eb4a63630d526f74d4bdaec8adcb51993ed65652d651d7c49203eb"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
+ "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62",
+ "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
+ "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
+ "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209"
+ ],
+ "threshold": 3
+ },
+ "snapshot": {
+ "keyids": [
+ "fc61191ba8a516fe386c7d6c97d918e1d241e1589729add09b122725b8c32451"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
+ "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62",
+ "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
+ "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
+ "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209"
+ ],
+ "threshold": 3
+ },
+ "timestamp": {
+ "keyids": [
+ "b6710623a30c010738e64c5209d367df1c0a18cf90e6ab5292fb01680f83453d"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 3
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources/dev/sigstore/tuf/remote-repo-meta-file-too-big/2.root.json b/src/test/resources/dev/sigstore/tuf/remote-repo-meta-file-too-big/2.root.json
new file mode 100644
index 00000000..da1dfb90
Binary files /dev/null and b/src/test/resources/dev/sigstore/tuf/remote-repo-meta-file-too-big/2.root.json differ
diff --git a/src/test/resources/dev/sigstore/tuf/remote-repo-meta-file-too-big/README.md b/src/test/resources/dev/sigstore/tuf/remote-repo-meta-file-too-big/README.md
new file mode 100644
index 00000000..8895704e
--- /dev/null
+++ b/src/test/resources/dev/sigstore/tuf/remote-repo-meta-file-too-big/README.md
@@ -0,0 +1 @@
+fallocate -l 100KiB 1.root.json
\ No newline at end of file
diff --git a/src/test/resources/dev/sigstore/tuf/remote-repo-prod/1.root.json b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/1.root.json
new file mode 100644
index 00000000..dcc71f96
--- /dev/null
+++ b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/1.root.json
@@ -0,0 +1,130 @@
+{
+ "signatures": [
+ {
+ "keyid": "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
+ "sig": "30450221008a35d51da0f845301a5eac98ad0df00a934f59b709c1eaf81c86be734d9356f80220742942325599749800f52675f6efe124345980a2a636c0dc76f9caf9fc3123b0"
+ },
+ {
+ "keyid": "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62",
+ "sig": "3045022100ef9157ece2a09baec1eab80adfc00b04da20b1f9a0d1b47c5dabc4506719ef2c022074f72acd57398e4ddc8c2a5040df902961e9615dca48f3fbe38cbb506e500066"
+ },
+ {
+ "keyid": "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
+ "sig": "30450220420fdc9a09cd069b8b15fd8db9cedf7d0dee75871bd1cfee77c926d4120a770002210097553b5ad0d6b4a13902ed37509638bb63a9009f78230cd56c802909ffbfead7"
+ },
+ {
+ "keyid": "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
+ "sig": "304502202aaf32e66f90752f658672b085ecfe45cc1ad31ee6cf5c9ad05f3267685f8d88022100b5df02acdaa371123db9d7a42219553fe079b230b168833e951be7ee56ded347"
+ },
+ {
+ "keyid": "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209",
+ "sig": "304402205d420c7d05c58980c1c9f7d221f53b5334aae27a447d2a91c2ceddd685269749022039ec83e51f8e1779d7f0142dfa4a5bbecfe327fc0b91b7416090fea2416fd53a"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": false,
+ "expires": "2021-12-18T13:28:12.99008-06:00",
+ "keys": {
+ "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04cbc5cab2684160323c25cd06c3307178a6b1d1c9b949328453ae473c5ba7527e35b13f298b41633382241f3fd8526c262d43b45adee5c618fa0642c82b8a9803"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04a71aacd835dc170ba6db3fa33a1a33dee751d4f8b0217b805b9bd3242921ee93672fdcfd840576c5bb0dc0ed815edf394c1ee48c2b5e02485e59bfc512f3adc7"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04117b33dd265715bf23315e368faa499728db8d1f0a377070a1c7b1aba2cc21be6ab1628e42f2cdd7a35479f2dce07b303a8ba646c55569a8d2a504ba7e86e447"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04cc1cd53a61c23e88cc54b488dfae168a257c34fac3e88811c55962b24cffbfecb724447999c54670e365883716302e49da57c79a33cd3e16f81fbc66f0bcdf48"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "048a78a44ac01099890d787e5e62afc29c8ccb69a70ec6549a6b04033b0a8acbfb42ab1ab9c713d225cdb52b858886cf46c8e90a7f3b9e6371882f370c259e1c5b"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
+ "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62",
+ "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
+ "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
+ "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209"
+ ],
+ "threshold": 3
+ },
+ "snapshot": {
+ "keyids": [
+ "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
+ "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62",
+ "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
+ "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
+ "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209"
+ ],
+ "threshold": 3
+ },
+ "targets": {
+ "keyids": [
+ "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
+ "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62",
+ "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
+ "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
+ "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209"
+ ],
+ "threshold": 3
+ },
+ "timestamp": {
+ "keyids": [
+ "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
+ "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62",
+ "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
+ "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
+ "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209"
+ ],
+ "threshold": 3
+ }
+ },
+ "spec_version": "1.0",
+ "version": 1
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources/dev/sigstore/tuf/remote-repo-prod/2.root.json b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/2.root.json
new file mode 100644
index 00000000..386ebe62
--- /dev/null
+++ b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/2.root.json
@@ -0,0 +1,144 @@
+{
+ "signatures": [
+ {
+ "keyid": "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
+ "sig": "3046022100d3ea59490b253beae0926c6fa63f54336dea1ed700555be9f27ff55cd347639c0221009157d1ba012cead81948a4ab777d355451d57f5c4a2d333fc68d2e3f358093c2"
+ },
+ {
+ "keyid": "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62",
+ "sig": "304502206eaef40564403ce572c6d062e0c9b0aab5e0223576133e081e1b495e8deb9efd02210080fd6f3464d759601b4afec596bbd5952f3a224cd06ed1cdfc3c399118752ba2"
+ },
+ {
+ "keyid": "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
+ "sig": "304502207baace02f56d8e6069f10b6ff098a26e7f53a7f9324ad62cffa0557bdeb9036c022100fb3032baaa090d0040c3f2fd872571c84479309b773208601d65948df87a9720"
+ },
+ {
+ "keyid": "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
+ "sig": "304402205180c01905505dd88acd7a2dad979dd75c979b3722513a7bdedac88c6ae8dbeb022056d1ddf7a192f0b1c2c90ff487de2fb3ec9f0c03f66ea937c78d3b6a493504ca"
+ },
+ {
+ "keyid": "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209",
+ "sig": "3046022100c8806d4647c514d80fd8f707d3369444c4fd1d0812a2d25f828e564c99790e3f022100bb51f12e862ef17a7d3da2ac103bebc5c7e792237006c4cafacd76267b249c2f"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": false,
+ "expires": "2022-05-11T19:09:02.663975009Z",
+ "keys": {
+ "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04cbc5cab2684160323c25cd06c3307178a6b1d1c9b949328453ae473c5ba7527e35b13f298b41633382241f3fd8526c262d43b45adee5c618fa0642c82b8a9803"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "b6710623a30c010738e64c5209d367df1c0a18cf90e6ab5292fb01680f83453d": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04fa1a3e42f2300cd3c5487a61509348feb1e936920fef2f83b7cd5dbe7ba045f538725ab8f18a666e6233edb7e0db8766c8dc336633449c5e1bbe0c182b02df0b"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04a71aacd835dc170ba6db3fa33a1a33dee751d4f8b0217b805b9bd3242921ee93672fdcfd840576c5bb0dc0ed815edf394c1ee48c2b5e02485e59bfc512f3adc7"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04117b33dd265715bf23315e368faa499728db8d1f0a377070a1c7b1aba2cc21be6ab1628e42f2cdd7a35479f2dce07b303a8ba646c55569a8d2a504ba7e86e447"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04cc1cd53a61c23e88cc54b488dfae168a257c34fac3e88811c55962b24cffbfecb724447999c54670e365883716302e49da57c79a33cd3e16f81fbc66f0bcdf48"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "048a78a44ac01099890d787e5e62afc29c8ccb69a70ec6549a6b04033b0a8acbfb42ab1ab9c713d225cdb52b858886cf46c8e90a7f3b9e6371882f370c259e1c5b"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "fc61191ba8a516fe386c7d6c97d918e1d241e1589729add09b122725b8c32451": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "044c7793ab74b9ddd713054e587b8d9c75c5f6025633d0fef7ca855ed5b8d5a474b23598fe33eb4a63630d526f74d4bdaec8adcb51993ed65652d651d7c49203eb"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
+ "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62",
+ "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
+ "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
+ "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209"
+ ],
+ "threshold": 3
+ },
+ "snapshot": {
+ "keyids": [
+ "fc61191ba8a516fe386c7d6c97d918e1d241e1589729add09b122725b8c32451"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
+ "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62",
+ "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
+ "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
+ "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209"
+ ],
+ "threshold": 3
+ },
+ "timestamp": {
+ "keyids": [
+ "b6710623a30c010738e64c5209d367df1c0a18cf90e6ab5292fb01680f83453d"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 2
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources/dev/sigstore/tuf/remote-repo-prod/3.root.json b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/3.root.json
new file mode 100644
index 00000000..8d69c515
--- /dev/null
+++ b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/3.root.json
@@ -0,0 +1,136 @@
+{
+ "signatures": [
+ {
+ "keyid": "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
+ "sig": "3046022100e7a80e4b03eb8768999d20f104925fd9149faf3f6f73ee80f8c2e8d5f998f48c022100d3f01eb8effee202a244e710dca09530b9c57c5e510ab35172bd5eddd373ccc8"
+ },
+ {
+ "keyid": "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
+ "sig": "304502200e45fde5cf750f8c533c4f259eb1469510600993b98ae2c3cb8f1922cda96e27022100f5151760d0ef0882a96c2531ccd9f5e4a7ff2b259d8eb34ead8bfdf60cb52fee"
+ },
+ {
+ "keyid": "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
+ "sig": "304502205a7ebeac3617bfb1aca957a6f74d37a02f2854afa54e5103fb3c891bb25836db022100f06614ca8d21f968e45edc29f826d8dbeed07c51d4cb473a734a2036171900de"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": false,
+ "expires": "2022-11-10T21:58:09.733402317Z",
+ "keys": {
+ "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04cbc5cab2684160323c25cd06c3307178a6b1d1c9b949328453ae473c5ba7527e35b13f298b41633382241f3fd8526c262d43b45adee5c618fa0642c82b8a9803"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "b6710623a30c010738e64c5209d367df1c0a18cf90e6ab5292fb01680f83453d": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04fa1a3e42f2300cd3c5487a61509348feb1e936920fef2f83b7cd5dbe7ba045f538725ab8f18a666e6233edb7e0db8766c8dc336633449c5e1bbe0c182b02df0b"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04a71aacd835dc170ba6db3fa33a1a33dee751d4f8b0217b805b9bd3242921ee93672fdcfd840576c5bb0dc0ed815edf394c1ee48c2b5e02485e59bfc512f3adc7"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04117b33dd265715bf23315e368faa499728db8d1f0a377070a1c7b1aba2cc21be6ab1628e42f2cdd7a35479f2dce07b303a8ba646c55569a8d2a504ba7e86e447"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04cc1cd53a61c23e88cc54b488dfae168a257c34fac3e88811c55962b24cffbfecb724447999c54670e365883716302e49da57c79a33cd3e16f81fbc66f0bcdf48"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "048a78a44ac01099890d787e5e62afc29c8ccb69a70ec6549a6b04033b0a8acbfb42ab1ab9c713d225cdb52b858886cf46c8e90a7f3b9e6371882f370c259e1c5b"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "fc61191ba8a516fe386c7d6c97d918e1d241e1589729add09b122725b8c32451": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "044c7793ab74b9ddd713054e587b8d9c75c5f6025633d0fef7ca855ed5b8d5a474b23598fe33eb4a63630d526f74d4bdaec8adcb51993ed65652d651d7c49203eb"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
+ "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62",
+ "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
+ "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
+ "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209"
+ ],
+ "threshold": 3
+ },
+ "snapshot": {
+ "keyids": [
+ "fc61191ba8a516fe386c7d6c97d918e1d241e1589729add09b122725b8c32451"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
+ "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62",
+ "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
+ "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
+ "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209"
+ ],
+ "threshold": 3
+ },
+ "timestamp": {
+ "keyids": [
+ "b6710623a30c010738e64c5209d367df1c0a18cf90e6ab5292fb01680f83453d"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 3
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources/dev/sigstore/tuf/remote-repo-prod/4.root.json b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/4.root.json
new file mode 100644
index 00000000..928ca109
--- /dev/null
+++ b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/4.root.json
@@ -0,0 +1,144 @@
+{
+ "signatures": [
+ {
+ "keyid": "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
+ "sig": "3046022100f7d4abde3d694fba01af172466629249a6743efd04c3999f958494842a7aee1f022100d19a295f9225247f17650fdb4ad50b99c2326700aadd0afaec4ae418941c7c59"
+ },
+ {
+ "keyid": "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
+ "sig": "3045022075ec28360b3e310db9d3de281a5286e37884aefd9f0b7193ad67c68ab6ee95a2022100aa08a93c58d74d9cb128cea765cae378efe86092f253b75fd427aede48ac7e22"
+ },
+ {
+ "keyid": "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
+ "sig": "304502201de38b2a56a58ae046f26e3be8673063cdde8f8b6a8733bc025ebaf0e09569c50221008f8620c960fa6f9cb52b7c39ce84a5ac18224be4a876a35e1bc8f5d76aa24e86"
+ },
+ {
+ "keyid": "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209",
+ "sig": "3044022070d86c3fbc3fb69783d54a451187e43776d97effe500c51f2558939c80ab2bb902201fb14ce51c6c4f40e8f2db792c3d56da18fe0c39499fa3fca9e841fc8bee17f1"
+ },
+ {
+ "keyid": "75e867ab10e121fdef32094af634707f43ddd79c6bab8ad6c5ab9f03f4ea8c90",
+ "sig": "3046022100aa1ff582569287b5160864e20bb343eff92dec316940cebe5742e47a56e8cabd0221009dc18bad12920a39b7427914ecb46e2ead58f17136935afbba488b7d6f3160ff"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": false,
+ "expires": "2023-01-12T18:22:02Z",
+ "keys": {
+ "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04cbc5cab2684160323c25cd06c3307178a6b1d1c9b949328453ae473c5ba7527e35b13f298b41633382241f3fd8526c262d43b45adee5c618fa0642c82b8a9803"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "75e867ab10e121fdef32094af634707f43ddd79c6bab8ad6c5ab9f03f4ea8c90": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04d2086b87dd8bc3562bde27465795aa0ad30307c0b1f83f21742e30d992cd2299554685462ec9186b782178cc8e8e227c90f8b5e5a436fffecffa88fb52f24f1b"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "b6710623a30c010738e64c5209d367df1c0a18cf90e6ab5292fb01680f83453d": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04fa1a3e42f2300cd3c5487a61509348feb1e936920fef2f83b7cd5dbe7ba045f538725ab8f18a666e6233edb7e0db8766c8dc336633449c5e1bbe0c182b02df0b"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04117b33dd265715bf23315e368faa499728db8d1f0a377070a1c7b1aba2cc21be6ab1628e42f2cdd7a35479f2dce07b303a8ba646c55569a8d2a504ba7e86e447"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04cc1cd53a61c23e88cc54b488dfae168a257c34fac3e88811c55962b24cffbfecb724447999c54670e365883716302e49da57c79a33cd3e16f81fbc66f0bcdf48"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "048a78a44ac01099890d787e5e62afc29c8ccb69a70ec6549a6b04033b0a8acbfb42ab1ab9c713d225cdb52b858886cf46c8e90a7f3b9e6371882f370c259e1c5b"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "fc61191ba8a516fe386c7d6c97d918e1d241e1589729add09b122725b8c32451": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "044c7793ab74b9ddd713054e587b8d9c75c5f6025633d0fef7ca855ed5b8d5a474b23598fe33eb4a63630d526f74d4bdaec8adcb51993ed65652d651d7c49203eb"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
+ "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
+ "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
+ "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209",
+ "75e867ab10e121fdef32094af634707f43ddd79c6bab8ad6c5ab9f03f4ea8c90"
+ ],
+ "threshold": 3
+ },
+ "snapshot": {
+ "keyids": [
+ "fc61191ba8a516fe386c7d6c97d918e1d241e1589729add09b122725b8c32451"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
+ "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
+ "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
+ "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209",
+ "75e867ab10e121fdef32094af634707f43ddd79c6bab8ad6c5ab9f03f4ea8c90"
+ ],
+ "threshold": 3
+ },
+ "timestamp": {
+ "keyids": [
+ "b6710623a30c010738e64c5209d367df1c0a18cf90e6ab5292fb01680f83453d"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 4
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources/dev/sigstore/tuf/remote-repo-prod/key.pem b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/key.pem
new file mode 100644
index 00000000..94482bd0
--- /dev/null
+++ b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/key.pem
@@ -0,0 +1,2 @@
+BMvFyrJoQWAyPCXNBsMwcXimsdHJuUkyhFOuRzxbp1J+NbE/KYtBYzOCJB8/2FJsJi1DtFre5cY
+Y+gZCyCuKmAM=
diff --git a/src/test/resources/dev/sigstore/tuf/remote-repo-prod/rekor.json b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/rekor.json
new file mode 100644
index 00000000..aafbe175
--- /dev/null
+++ b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/rekor.json
@@ -0,0 +1,29 @@
+{
+ "signatures": [
+ {
+ "keyid": "ae0c689c6347ada7359df48934991f4e013193d6ddf3482a5ffb293f74f3b217",
+ "sig": "3046022100d7b85d9ea7de9c8f535c928100f6e0915b7546a1710e87fa11da567eeee66658022100e85477a406a161ffaee627f4ab38b8f49d35142ed0f196d747ee8f7ad3da502f"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "expires": "2023-01-12T18:22:02Z",
+ "spec_version": "1.0",
+ "targets": {
+ "rekor.0.pub": {
+ "custom": {
+ "sigstore": {
+ "status": "Active",
+ "usage": "Rekor"
+ }
+ },
+ "hashes": {
+ "sha256": "dce5ef715502ec9f3cdfd11f8cc384b31a6141023d3e7595e9908a81cb6241bd",
+ "sha512": "0ae7705e02db33e814329746a4a0e5603c5bdcd91c96d072158d71011a2695788866565a2fec0fe363eb72cbcaeda39e54c5fe8d416daf9f3101fdba4217ef35"
+ },
+ "length": 178
+ }
+ },
+ "version": 3
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources/dev/sigstore/tuf/remote-repo-prod/revocation.json b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/revocation.json
new file mode 100644
index 00000000..d6e7cb9d
--- /dev/null
+++ b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/revocation.json
@@ -0,0 +1,29 @@
+{
+ "signatures": [
+ {
+ "keyid": "9e7d813e8e16062e60a4540346aa8e7c7782afb7098af0b944ea80a4033a176f",
+ "sig": "304502205aebc2daae399b1a6941ccb62a710fc0d95564eead3a433c2e5cae11c3699abf022100b3080a43a6334a714ab29641d1796ba95c7c219ea2bf7210a608aa7ea45bbf98"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "expires": "2023-01-18T17:33:23Z",
+ "spec_version": "1.0",
+ "targets": {
+ "revocation.list": {
+ "custom": {
+ "sigstore": {
+ "status": "Unknown",
+ "usage": "Unknown"
+ }
+ },
+ "hashes": {
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "sha512": "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"
+ },
+ "length": 0
+ }
+ },
+ "version": 1
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources/dev/sigstore/tuf/remote-repo-prod/root.json b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/root.json
new file mode 100644
index 00000000..0df392b3
--- /dev/null
+++ b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/root.json
@@ -0,0 +1,144 @@
+{
+ "signatures": [
+ {
+ "keyid": "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
+ "sig": "3046022100f7d4abde3d694fba01af172466629249a6743efd04c3999f958494842a7aee1f022100d19a295f9225247f17650fdb4ad50b99c2326700aadd0afaec4ae418941c7c59"
+ },
+ {
+ "keyid": "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
+ "sig": "3045022075ec28360b3e310db9d3de281a5286e37884aefd9f0b7193ad67c68ab6ee95a2022100aa08a93c58d74d9cb128cea765cae378efe86092f253b75fd427aede48ac7e22"
+ },
+ {
+ "keyid": "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
+ "sig": "304502201de38b2a56a58ae046f26e3be8673063cdde8f8b6a8733bc025ebaf0e09569c50221008f8620c960fa6f9cb52b7c39ce84a5ac18224be4a876a35e1bc8f5d76aa24e86"
+ },
+ {
+ "keyid": "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209",
+ "sig": "3044022070d86c3fbc3fb69783d54a451187e43776d97effe500c51f2558939c80ab2bb902201fb14ce51c6c4f40e8f2db792c3d56da18fe0c39499fa3fca9e841fc8bee17f1"
+ },
+ {
+ "keyid": "75e867ab10e121fdef32094af634707f43ddd79c6bab8ad6c5ab9f03f4ea8c90",
+ "sig": "3046022100aa1ff582569287b5160864e20bb343eff92dec316940cebe5742e47a56e8cabd0221009dc18bad12920a39b7427914ecb46e2ead58f17136935afbba488b7d6f3160ff"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": false,
+ "expires": "2023-01-12T18:22:02Z",
+ "keys": {
+ "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04cbc5cab2684160323c25cd06c3307178a6b1d1c9b949328453ae473c5ba7527e35b13f298b41633382241f3fd8526c262d43b45adee5c618fa0642c82b8a9803"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "75e867ab10e121fdef32094af634707f43ddd79c6bab8ad6c5ab9f03f4ea8c90": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04d2086b87dd8bc3562bde27465795aa0ad30307c0b1f83f21742e30d992cd2299554685462ec9186b782178cc8e8e227c90f8b5e5a436fffecffa88fb52f24f1b"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "b6710623a30c010738e64c5209d367df1c0a18cf90e6ab5292fb01680f83453d": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04fa1a3e42f2300cd3c5487a61509348feb1e936920fef2f83b7cd5dbe7ba045f538725ab8f18a666e6233edb7e0db8766c8dc336633449c5e1bbe0c182b02df0b"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04117b33dd265715bf23315e368faa499728db8d1f0a377070a1c7b1aba2cc21be6ab1628e42f2cdd7a35479f2dce07b303a8ba646c55569a8d2a504ba7e86e447"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04cc1cd53a61c23e88cc54b488dfae168a257c34fac3e88811c55962b24cffbfecb724447999c54670e365883716302e49da57c79a33cd3e16f81fbc66f0bcdf48"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "048a78a44ac01099890d787e5e62afc29c8ccb69a70ec6549a6b04033b0a8acbfb42ab1ab9c713d225cdb52b858886cf46c8e90a7f3b9e6371882f370c259e1c5b"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "fc61191ba8a516fe386c7d6c97d918e1d241e1589729add09b122725b8c32451": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "044c7793ab74b9ddd713054e587b8d9c75c5f6025633d0fef7ca855ed5b8d5a474b23598fe33eb4a63630d526f74d4bdaec8adcb51993ed65652d651d7c49203eb"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
+ "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
+ "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
+ "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209",
+ "75e867ab10e121fdef32094af634707f43ddd79c6bab8ad6c5ab9f03f4ea8c90"
+ ],
+ "threshold": 3
+ },
+ "snapshot": {
+ "keyids": [
+ "fc61191ba8a516fe386c7d6c97d918e1d241e1589729add09b122725b8c32451"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
+ "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
+ "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
+ "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209",
+ "75e867ab10e121fdef32094af634707f43ddd79c6bab8ad6c5ab9f03f4ea8c90"
+ ],
+ "threshold": 3
+ },
+ "timestamp": {
+ "keyids": [
+ "b6710623a30c010738e64c5209d367df1c0a18cf90e6ab5292fb01680f83453d"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 1
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources/dev/sigstore/tuf/remote-repo-prod/snapshot.json b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/snapshot.json
new file mode 100644
index 00000000..0d067cf3
--- /dev/null
+++ b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/snapshot.json
@@ -0,0 +1,56 @@
+{
+ "signatures": [
+ {
+ "keyid": "fc61191ba8a516fe386c7d6c97d918e1d241e1589729add09b122725b8c32451",
+ "sig": "304502202c64259b34ce58411d61d5ced5165bddc2e004e5374e558884deadc1f60dbba3022100a9055dce3d8da1a8f1423d63735bac980aa9afdac71425008b89818530c17173"
+ }
+ ],
+ "signed": {
+ "_type": "snapshot",
+ "expires": "2022-08-22T00:11:31Z",
+ "meta": {
+ "rekor.json": {
+ "hashes": {
+ "sha256": "9d2e1a5842937d8e0d3e3759170b0ad15c56c5df36afc5cf73583ddd283a463b",
+ "sha512": "176e9e710ddddd1b357a7d7970831bae59763395a0c18976110cbd35b25e5412dc50f356ec421a7a30265670cf7aec9ed84ee944ba700ec2394b9c876645b960"
+ },
+ "length": 797,
+ "version": 3
+ },
+ "revocation.json": {
+ "hashes": {
+ "sha256": "fd46a3ad04afd31e164b91d5d399d3e6e4d22e1c98ad463aeca0fa3d2f8cb6dd",
+ "sha512": "6cacdb83edac8283bcbf6e4d7011f98ae85f05c22034e4466de96496739df224d8a9fd580abfc3e4d4901e2170dbe0d93865a1e861bf85556e03f36d00cc5b5b"
+ },
+ "length": 800,
+ "version": 1
+ },
+ "root.json": {
+ "hashes": {
+ "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c",
+ "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42"
+ },
+ "length": 5297,
+ "version": 2
+ },
+ "staging.json": {
+ "hashes": {
+ "sha256": "cda57759abac5375397eea3531d7ca51e3a67da9a2dc93f2cdab749e2ae73149",
+ "sha512": "e9e59587bde453144c7079884a880c706f1d43f26e8bb23fac2b96a99569a2a30ae6cf51ec51c2454f760ce83d4c20915e062aede7f319b3da6a6ed1d26ca281"
+ },
+ "length": 401,
+ "version": 2
+ },
+ "targets.json": {
+ "hashes": {
+ "sha256": "730ce11e76f9c91f3814556f97f84566d95f6908b246a0013d7d66db52167117",
+ "sha512": "7c6d8beacd11aa9011a700a0509fead75fcf54d836a414f5633a2c29ee3c10415797a38aa85396953a3c6b206a052225852ce2cc85ae8b110d849855d32d0f59"
+ },
+ "length": 5984,
+ "version": 4
+ }
+ },
+ "spec_version": "1.0",
+ "version": 41
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources/dev/sigstore/tuf/remote-repo-prod/staging.json b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/staging.json
new file mode 100644
index 00000000..f2c93a84
--- /dev/null
+++ b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/staging.json
@@ -0,0 +1,15 @@
+{
+ "signatures": [
+ {
+ "keyid": "b811bd53f2d7adcf5d93e6bb4a8ed2e0ca0f83d454a3e51f105c8e8376bc80d4",
+ "sig": "3046022100a23c17bc46bedeb579901b00a0efcb8b75ed3a9de1201c55c6355695e8ff397d022100e6fe6456c421b09caaeae127f50dec03445856c1adcad14d3d1b6b83e465edba"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "expires": "2022-11-10T21:58:10Z",
+ "spec_version": "1.0",
+ "targets": {},
+ "version": 2
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources/dev/sigstore/tuf/remote-repo-prod/targets.json b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/targets.json
new file mode 100644
index 00000000..1d54b4db
--- /dev/null
+++ b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/targets.json
@@ -0,0 +1,189 @@
+{
+ "signatures": [
+ {
+ "keyid": "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
+ "sig": "3044022100d54e28736b8ac066410aa4b1560b2244d1c631a8b0192420c34e2db07248ed54021f1193d7e7ecbb0045533ce912f7685fc66cff42a80b56b3e1e7245c6c542bf1"
+ },
+ {
+ "keyid": "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
+ "sig": "3045022100d2e35d4a8f5be08e8e2f192cdaaad3a7af0ab58132f87e7fc306ef63c236ac2102200f04bf6f8eeeef872a2f9341f4fd0b7e8dd361639858b98b15190189e4b5b1de"
+ },
+ {
+ "keyid": "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
+ "sig": "304402207e795995e9af059179cee6a9c0f82348ffcb91d79a238cf0f4c9a53cc53bce570220206c8559f03fb7cae0e12030a46a4d27b3113652ee500cc9a425678b64ebb6f3"
+ },
+ {
+ "keyid": "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209",
+ "sig": "304402207a02f01b2a88a63b9acfd4c0f2a7c6a000b9b45254c884fe274362905b35e23e02204e1a5bbdae25a748eeb6bf364fe83ccb08999b7f9116fed673a7b90b79a5ff4b"
+ },
+ {
+ "keyid": "75e867ab10e121fdef32094af634707f43ddd79c6bab8ad6c5ab9f03f4ea8c90",
+ "sig": "3045022064dc6b069511c264ae26b3f37c3fdcefe746902b86a3b8b17403f89ee8907548022100cce2acdb08842fdcbfd7109aaf1936cff2bac742b6eddbdc34ce726110ab4247"
+ }
+ ],
+ "signed": {
+ "_type": "targets",
+ "delegations": {
+ "keys": {
+ "9e7d813e8e16062e60a4540346aa8e7c7782afb7098af0b944ea80a4033a176f": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "042e5916fa6da3d05086e760576bf07e2b9c8bf624f38ab6697b449979d0bc3276baf9021200afd6072ed751d974dbcc93ead6cd749e11cecaf2a5b210a1180af1"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "ae0c689c6347ada7359df48934991f4e013193d6ddf3482a5ffb293f74f3b217": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "043463588ae9df33a419d1099761245af52aaf7e638b2047bc0f739a62de9808c50a21ea8a1a273799f857f31a1bcb66e6661dd9d5ac7ac3ca260b0b8130c3fed8"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "b811bd53f2d7adcf5d93e6bb4a8ed2e0ca0f83d454a3e51f105c8e8376bc80d4": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "041b4b13a6e7110292d284c0dbfc3962a12d2a779a800c99aff59c6afe779296943c75d84aa5bad0be28e4061cf93e0cd3d372d9b2f75ea9f29b907cbccd82006f"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ }
+ },
+ "roles": [
+ {
+ "keyids": [
+ "ae0c689c6347ada7359df48934991f4e013193d6ddf3482a5ffb293f74f3b217"
+ ],
+ "name": "rekor",
+ "paths": [
+ "rekor.*.pub"
+ ],
+ "terminating": true,
+ "threshold": 1
+ },
+ {
+ "keyids": [
+ "b811bd53f2d7adcf5d93e6bb4a8ed2e0ca0f83d454a3e51f105c8e8376bc80d4"
+ ],
+ "name": "staging",
+ "paths": [
+ "*"
+ ],
+ "terminating": false,
+ "threshold": 1
+ },
+ {
+ "keyids": [
+ "9e7d813e8e16062e60a4540346aa8e7c7782afb7098af0b944ea80a4033a176f"
+ ],
+ "name": "revocation",
+ "paths": [
+ "*"
+ ],
+ "terminating": false,
+ "threshold": 1
+ }
+ ]
+ },
+ "expires": "2023-01-12T18:22:03Z",
+ "spec_version": "1.0",
+ "targets": {
+ "artifact.pub": {
+ "custom": {
+ "sigstore": {
+ "status": "Active",
+ "usage": "Unknown"
+ }
+ },
+ "hashes": {
+ "sha256": "59ebf97a9850aecec4bc39c1f5c1dc46e6490a6b5fd2a6cacdcac0c3a6fc4cbf",
+ "sha512": "308fd1d1d95d7f80aa33b837795251cc3e886792982275e062409e13e4e236ffc34d676682aa96fdc751414de99c864bf132dde71581fa651c6343905e3bf988"
+ },
+ "length": 177
+ },
+ "ctfe.pub": {
+ "custom": {
+ "sigstore": {
+ "status": "Active",
+ "usage": "CTFE"
+ }
+ },
+ "hashes": {
+ "sha256": "7fcb94a5d0ed541260473b990b99a6c39864c1fb16f3f3e594a5a3cebbfe138a",
+ "sha512": "4b20747d1afe2544238ad38cc0cc3010921b177d60ac743767e0ef675b915489bd01a36606c0ff83c06448622d7160f0d866c83d20f0c0f44653dcc3f9aa0bd4"
+ },
+ "length": 177
+ },
+ "fulcio.crt.pem": {
+ "custom": {
+ "sigstore": {
+ "status": "Expired",
+ "usage": "Fulcio"
+ }
+ },
+ "hashes": {
+ "sha256": "f360c53b2e13495a628b9b8096455badcb6d375b185c4816d95a5d746ff29908",
+ "sha512": "0713252a7fd17f7f3ab12f88a64accf2eb14b8ad40ca711d7fe8b4ecba3b24db9e9dffadb997b196d3867b8f9ff217faf930d80e4dab4e235c7fc3f07be69224"
+ },
+ "length": 744
+ },
+ "fulcio_intermediate_v1.crt.pem": {
+ "custom": {
+ "sigstore": {
+ "status": "Active",
+ "usage": "Fulcio"
+ }
+ },
+ "hashes": {
+ "sha256": "f8cbecf186db7714624a5f4e99da31a917cbef70a94dd6921f5c3ca969dfe30a",
+ "sha512": "0f99f47dbc26c5f1e3cba0bfd9af4245a26e5cb735d6ef005792ec7e603f66fdb897de985973a6e50940ca7eff5e1849719e967b5ad2dac74a29115a41cf6f21"
+ },
+ "length": 789
+ },
+ "fulcio_v1.crt.pem": {
+ "custom": {
+ "sigstore": {
+ "status": "Active",
+ "usage": "Fulcio"
+ }
+ },
+ "hashes": {
+ "sha256": "f989aa23def87c549404eadba767768d2a3c8d6d30a8b793f9f518a8eafd2cf5",
+ "sha512": "f2e33a6dc208cee1f51d33bbea675ab0f0ced269617497985f9a0680689ee7073e4b6f8fef64c91bda590d30c129b3070dddce824c05bc165ac9802f0705cab6"
+ },
+ "length": 740
+ },
+ "rekor.0.pub": {
+ "hashes": {
+ "sha256": "dce5ef715502ec9f3cdfd11f8cc384b31a6141023d3e7595e9908a81cb6241bd",
+ "sha512": "0ae7705e02db33e814329746a4a0e5603c5bdcd91c96d072158d71011a2695788866565a2fec0fe363eb72cbcaeda39e54c5fe8d416daf9f3101fdba4217ef35"
+ },
+ "length": 178
+ },
+ "rekor.pub": {
+ "custom": {
+ "sigstore": {
+ "status": "Active",
+ "usage": "Rekor"
+ }
+ },
+ "hashes": {
+ "sha256": "dce5ef715502ec9f3cdfd11f8cc384b31a6141023d3e7595e9908a81cb6241bd",
+ "sha512": "0ae7705e02db33e814329746a4a0e5603c5bdcd91c96d072158d71011a2695788866565a2fec0fe363eb72cbcaeda39e54c5fe8d416daf9f3101fdba4217ef35"
+ },
+ "length": 178
+ }
+ },
+ "version": 4
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources/dev/sigstore/tuf/remote-repo-prod/targets/artifact.pub b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/targets/artifact.pub
new file mode 100644
index 00000000..d6e745bd
--- /dev/null
+++ b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/targets/artifact.pub
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEhyQCx0E9wQWSFI9ULGwy3BuRklnt
+IqozONbbdbqz11hlRJy9c7SG+hdcFl9jE9uE/dwtuwU2MqU9T/cN0YkWww==
+-----END PUBLIC KEY-----
\ No newline at end of file
diff --git a/src/test/resources/dev/sigstore/tuf/remote-repo-prod/targets/ctfe.pub b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/targets/ctfe.pub
new file mode 100644
index 00000000..1bb1488c
--- /dev/null
+++ b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/targets/ctfe.pub
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbfwR+RJudXscgRBRpKX1XFDy3Pyu
+dDxz/SfnRi1fT8ekpfBd2O1uoz7jr3Z8nKzxA69EUQ+eFCFI3zeubPWU7w==
+-----END PUBLIC KEY-----
\ No newline at end of file
diff --git a/src/test/resources/dev/sigstore/tuf/remote-repo-prod/targets/fulcio.crt.pem b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/targets/fulcio.crt.pem
new file mode 100644
index 00000000..6a06ff30
--- /dev/null
+++ b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/targets/fulcio.crt.pem
@@ -0,0 +1,13 @@
+-----BEGIN CERTIFICATE-----
+MIIB+DCCAX6gAwIBAgITNVkDZoCiofPDsy7dfm6geLbuhzAKBggqhkjOPQQDAzAq
+MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIx
+MDMwNzAzMjAyOVoXDTMxMDIyMzAzMjAyOVowKjEVMBMGA1UEChMMc2lnc3RvcmUu
+ZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLSy
+A7Ii5k+pNO8ZEWY0ylemWDowOkNa3kL+GZE5Z5GWehL9/A9bRNA3RbrsZ5i0Jcas
+taRL7Sp5fp/jD5dxqc/UdTVnlvS16an+2Yfswe/QuLolRUCrcOE2+2iA5+tzd6Nm
+MGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYE
+FMjFHQBBmiQpMlEk6w2uSu1KBtPsMB8GA1UdIwQYMBaAFMjFHQBBmiQpMlEk6w2u
+Su1KBtPsMAoGCCqGSM49BAMDA2gAMGUCMH8liWJfMui6vXXBhjDgY4MwslmN/TJx
+Ve/83WrFomwmNf056y1X48F9c4m3a3ozXAIxAKjRay5/aj/jsKKGIkmQatjI8uup
+Hr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ==
+-----END CERTIFICATE-----
\ No newline at end of file
diff --git a/src/test/resources/dev/sigstore/tuf/remote-repo-prod/targets/fulcio_intermediate_v1.crt.pem b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/targets/fulcio_intermediate_v1.crt.pem
new file mode 100644
index 00000000..6d1c298b
--- /dev/null
+++ b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/targets/fulcio_intermediate_v1.crt.pem
@@ -0,0 +1,14 @@
+-----BEGIN CERTIFICATE-----
+MIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMw
+KjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y
+MjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3Jl
+LmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0C
+AQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV7
+7LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS
+0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYB
+BQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjp
+KFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZI
+zj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJR
+nZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsP
+mygUY7Ii2zbdCdliiow=
+-----END CERTIFICATE-----
\ No newline at end of file
diff --git a/src/test/resources/dev/sigstore/tuf/remote-repo-prod/targets/fulcio_v1.crt.pem b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/targets/fulcio_v1.crt.pem
new file mode 100644
index 00000000..3afc46bb
--- /dev/null
+++ b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/targets/fulcio_v1.crt.pem
@@ -0,0 +1,13 @@
+-----BEGIN CERTIFICATE-----
+MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMw
+KjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y
+MTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3Jl
+LmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7
+XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxex
+X69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92j
+YzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRY
+wB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQ
+KsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCM
+WP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9
+TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ
+-----END CERTIFICATE-----
\ No newline at end of file
diff --git a/src/test/resources/dev/sigstore/tuf/remote-repo-prod/targets/rekor.0.pub b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/targets/rekor.0.pub
new file mode 100644
index 00000000..050ef601
--- /dev/null
+++ b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/targets/rekor.0.pub
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2G2Y+2tabdTV5BcGiBIx0a9fAFwr
+kBbmLSGtks4L3qX6yYY0zufBnhC8Ur/iy55GhWP/9A/bY2LhC30M9+RYtw==
+-----END PUBLIC KEY-----
diff --git a/src/test/resources/dev/sigstore/tuf/remote-repo-prod/targets/rekor.pub b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/targets/rekor.pub
new file mode 100644
index 00000000..050ef601
--- /dev/null
+++ b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/targets/rekor.pub
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2G2Y+2tabdTV5BcGiBIx0a9fAFwr
+kBbmLSGtks4L3qX6yYY0zufBnhC8Ur/iy55GhWP/9A/bY2LhC30M9+RYtw==
+-----END PUBLIC KEY-----
diff --git a/src/test/resources/dev/sigstore/tuf/remote-repo-prod/targets/revocation.list b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/targets/revocation.list
new file mode 100644
index 00000000..e69de29b
diff --git a/src/test/resources/dev/sigstore/tuf/remote-repo-prod/timestamp.json b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/timestamp.json
new file mode 100644
index 00000000..cc620c98
--- /dev/null
+++ b/src/test/resources/dev/sigstore/tuf/remote-repo-prod/timestamp.json
@@ -0,0 +1,24 @@
+{
+ "signatures": [
+ {
+ "keyid": "b6710623a30c010738e64c5209d367df1c0a18cf90e6ab5292fb01680f83453d",
+ "sig": "304402207d996af0556f2b8935db01d4a7caf19bf3f3dc951c305c162c93bcea56d9817c022070c2cf87269be4839b5d1de0aefdb0582dfb67891e1d79f5023d0e85eca810d5"
+ }
+ ],
+ "signed": {
+ "_type": "timestamp",
+ "expires": "2022-08-15T00:11:32Z",
+ "meta": {
+ "snapshot.json": {
+ "hashes": {
+ "sha256": "9bd1703f27c1bc35393eca10e887a4b07bcf31726d17b010634a699408ee479c",
+ "sha512": "f6581b543c94f00c3ba29e5e806bf820b089ddd46a22c316f19ba2dd4f116c33eb22ea495c5c684bdd53a82ba6d62d8b13b313dda00cd2e63914cef00ca3ed18"
+ },
+ "length": 1975,
+ "version": 41
+ }
+ },
+ "spec_version": "1.0",
+ "version": 41
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources/dev/sigstore/tuf/remote-repo-unsigned/2.root.json b/src/test/resources/dev/sigstore/tuf/remote-repo-unsigned/2.root.json
new file mode 100644
index 00000000..6c9dd6dc
--- /dev/null
+++ b/src/test/resources/dev/sigstore/tuf/remote-repo-unsigned/2.root.json
@@ -0,0 +1,139 @@
+{
+ "signatures": [
+ {
+ "keyid": "269398002671c9040dc68378dc82195141f4a22bb6f062dda9fc6ba7e98a1c32",
+ "sig": "3046022100d3ea59490b253beae0926c6fa63f54336dea1ed700555be9f27ff55cd347639c0221009157d1ba012cead81948a4ab777d355451d57f5c4a2d333fc68d2e3f358093c2"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": false,
+ "expires": "2023-05-11T19:09:02.663975009Z",
+ "keys": {
+ "269398002671c9040dc68378dc82195141f4a22bb6f062dda9fc6ba7e98a1c32": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "3059301306072a8648ce3d020106082a8648ce3d03010703420004c9e86a3e0f9d44970b1388d02db6eb6a498a22124033389da3f0733d7498b0c292d7b8f40165e1ab3d714562189f910e05c67576818d520a5927a8cf17a49e7c"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04cbc5cab2684160323c25cd06c3307178a6b1d1c9b949328453ae473c5ba7527e35b13f298b41633382241f3fd8526c262d43b45adee5c618fa0642c82b8a9803"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "b6710623a30c010738e64c5209d367df1c0a18cf90e6ab5292fb01680f83453d": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04fa1a3e42f2300cd3c5487a61509348feb1e936920fef2f83b7cd5dbe7ba045f538725ab8f18a666e6233edb7e0db8766c8dc336633449c5e1bbe0c182b02df0b"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04a71aacd835dc170ba6db3fa33a1a33dee751d4f8b0217b805b9bd3242921ee93672fdcfd840576c5bb0dc0ed815edf394c1ee48c2b5e02485e59bfc512f3adc7"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04117b33dd265715bf23315e368faa499728db8d1f0a377070a1c7b1aba2cc21be6ab1628e42f2cdd7a35479f2dce07b303a8ba646c55569a8d2a504ba7e86e447"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04cc1cd53a61c23e88cc54b488dfae168a257c34fac3e88811c55962b24cffbfecb724447999c54670e365883716302e49da57c79a33cd3e16f81fbc66f0bcdf48"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "048a78a44ac01099890d787e5e62afc29c8ccb69a70ec6549a6b04033b0a8acbfb42ab1ab9c713d225cdb52b858886cf46c8e90a7f3b9e6371882f370c259e1c5b"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "fc61191ba8a516fe386c7d6c97d918e1d241e1589729add09b122725b8c32451": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "044c7793ab74b9ddd713054e587b8d9c75c5f6025633d0fef7ca855ed5b8d5a474b23598fe33eb4a63630d526f74d4bdaec8adcb51993ed65652d651d7c49203eb"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
+ "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62",
+ "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
+ "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
+ "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209"
+ ],
+ "threshold": 3
+ },
+ "snapshot": {
+ "keyids": [
+ "fc61191ba8a516fe386c7d6c97d918e1d241e1589729add09b122725b8c32451"
+ ],
+ "threshold": 1
+ },
+ "targets": {
+ "keyids": [
+ "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
+ "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62",
+ "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
+ "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
+ "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209"
+ ],
+ "threshold": 3
+ },
+ "timestamp": {
+ "keyids": [
+ "b6710623a30c010738e64c5209d367df1c0a18cf90e6ab5292fb01680f83453d"
+ ],
+ "threshold": 1
+ }
+ },
+ "spec_version": "1.0",
+ "version": 2
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources/dev/sigstore/tuf/remote-repo-unsigned/README.md b/src/test/resources/dev/sigstore/tuf/remote-repo-unsigned/README.md
new file mode 100644
index 00000000..19936344
--- /dev/null
+++ b/src/test/resources/dev/sigstore/tuf/remote-repo-unsigned/README.md
@@ -0,0 +1 @@
+step crypto keypair test.pub test.key --no-password --insecure
diff --git a/src/test/resources/dev/sigstore/tuf/trusted-root.json b/src/test/resources/dev/sigstore/tuf/trusted-root.json
new file mode 100644
index 00000000..9f78bf2b
--- /dev/null
+++ b/src/test/resources/dev/sigstore/tuf/trusted-root.json
@@ -0,0 +1,130 @@
+{
+ "signatures": [
+ {
+ "keyid": "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
+ "sig": "30450221008a35d51da0f845301a5eac98ad0df00a934f59b709c1eaf81c86be734d9356f80220742942325599749800f52675f6efe124345980a2a636c0dc76f9caf9fc3123b0"
+ },
+ {
+ "keyid": "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62",
+ "sig": "3045022100ef9157ece2a09baec1eab80adfc00b04da20b1f9a0d1b47c5dabc4506719ef2c022074f72acd57398e4ddc8c2a5040df902961e9615dca48f3fbe38cbb506e500066"
+ },
+ {
+ "keyid": "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
+ "sig": "30450220420fdc9a09cd069b8b15fd8db9cedf7d0dee75871bd1cfee77c926d4120a770002210097553b5ad0d6b4a13902ed37509638bb63a9009f78230cd56c802909ffbfead7"
+ },
+ {
+ "keyid": "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
+ "sig": "304502202aaf32e66f90752f658672b085ecfe45cc1ad31ee6cf5c9ad05f3267685f8d88022100b5df02acdaa371123db9d7a42219553fe079b230b168833e951be7ee56ded347"
+ },
+ {
+ "keyid": "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209",
+ "sig": "304402205d420c7d05c58980c1c9f7d221f53b5334aae27a447d2a91c2ceddd685269749022039ec83e51f8e1779d7f0142dfa4a5bbecfe327fc0b91b7416090fea2416fd53a"
+ }
+ ],
+ "signed": {
+ "_type": "root",
+ "consistent_snapshot": false,
+ "expires": "2021-12-18T13:28:12.99008-06:00",
+ "keys": {
+ "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04cbc5cab2684160323c25cd06c3307178a6b1d1c9b949328453ae473c5ba7527e35b13f298b41633382241f3fd8526c262d43b45adee5c618fa0642c82b8a9803"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04a71aacd835dc170ba6db3fa33a1a33dee751d4f8b0217b805b9bd3242921ee93672fdcfd840576c5bb0dc0ed815edf394c1ee48c2b5e02485e59bfc512f3adc7"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04117b33dd265715bf23315e368faa499728db8d1f0a377070a1c7b1aba2cc21be6ab1628e42f2cdd7a35479f2dce07b303a8ba646c55569a8d2a504ba7e86e447"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "04cc1cd53a61c23e88cc54b488dfae168a257c34fac3e88811c55962b24cffbfecb724447999c54670e365883716302e49da57c79a33cd3e16f81fbc66f0bcdf48"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ },
+ "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209": {
+ "keyid_hash_algorithms": [
+ "sha256",
+ "sha512"
+ ],
+ "keytype": "ecdsa-sha2-nistp256",
+ "keyval": {
+ "public": "048a78a44ac01099890d787e5e62afc29c8ccb69a70ec6549a6b04033b0a8acbfb42ab1ab9c713d225cdb52b858886cf46c8e90a7f3b9e6371882f370c259e1c5b"
+ },
+ "scheme": "ecdsa-sha2-nistp256"
+ }
+ },
+ "roles": {
+ "root": {
+ "keyids": [
+ "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
+ "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62",
+ "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
+ "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
+ "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209"
+ ],
+ "threshold": 3
+ },
+ "snapshot": {
+ "keyids": [
+ "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
+ "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62",
+ "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
+ "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
+ "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209"
+ ],
+ "threshold": 3
+ },
+ "targets": {
+ "keyids": [
+ "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
+ "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62",
+ "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
+ "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
+ "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209"
+ ],
+ "threshold": 3
+ },
+ "timestamp": {
+ "keyids": [
+ "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97",
+ "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62",
+ "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b",
+ "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb",
+ "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209"
+ ],
+ "threshold": 3
+ }
+ },
+ "spec_version": "1.0",
+ "version": 1
+ }
+}
\ No newline at end of file