From 70ebdbead213fd34ba372eb7b31453257b2063d7 Mon Sep 17 00:00:00 2001 From: Appu Goundan Date: Thu, 24 Oct 2024 20:00:53 -0400 Subject: [PATCH] Store meta state in memory Delegated targets should be loaded at target search time, so keep state in memory so we can use it as necessary. Signed-off-by: Appu Goundan --- .../java/dev/sigstore/tuf/TrustedMeta.java | 114 +++++++ .../main/java/dev/sigstore/tuf/Updater.java | 112 ++++--- .../dev/sigstore/tuf/TrustedMetaTest.java | 182 +++++++++++ .../java/dev/sigstore/tuf/UpdaterTest.java | 300 +++++++----------- 4 files changed, 471 insertions(+), 237 deletions(-) create mode 100644 sigstore-java/src/main/java/dev/sigstore/tuf/TrustedMeta.java create mode 100644 sigstore-java/src/test/java/dev/sigstore/tuf/TrustedMetaTest.java diff --git a/sigstore-java/src/main/java/dev/sigstore/tuf/TrustedMeta.java b/sigstore-java/src/main/java/dev/sigstore/tuf/TrustedMeta.java new file mode 100644 index 00000000..cd737d6e --- /dev/null +++ b/sigstore-java/src/main/java/dev/sigstore/tuf/TrustedMeta.java @@ -0,0 +1,114 @@ +/* + * Copyright 2024 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 dev.sigstore.tuf.model.Root; +import dev.sigstore.tuf.model.RootRole; +import dev.sigstore.tuf.model.Snapshot; +import dev.sigstore.tuf.model.Targets; +import dev.sigstore.tuf.model.Timestamp; +import java.io.IOException; +import java.util.Optional; + +// An in memory cache that will pass through to a provided local tuf store +class TrustedMeta { + private final MutableTufStore localStore; + private Root root; + private Snapshot snapshot; + private Timestamp timestamp; + private Targets targets; + + private TrustedMeta(MutableTufStore localStore) { + this.localStore = localStore; + } + + static TrustedMeta newTrustedMeta(MutableTufStore localStore) { + return new TrustedMeta(localStore); + } + + public void setRoot(Root root) throws IOException { + // call storeTrustedRoot instead of generic storeMeta because it does doesn't extra work + localStore.storeTrustedRoot(root); + this.root = root; + } + + public Root getRoot() throws IOException { + return findRoot().orElseThrow(() -> new IllegalStateException("No cached root to load")); + } + + public Optional findRoot() throws IOException { + if (root == null) { + root = localStore.loadTrustedRoot().orElse(null); + } + return Optional.ofNullable(root); + } + + public void setTimestamp(Timestamp timestamp) throws IOException { + localStore.storeMeta(RootRole.TIMESTAMP, timestamp); + this.timestamp = timestamp; + } + + public Timestamp getTimestamp() throws IOException { + return findTimestamp() + .orElseThrow(() -> new IllegalStateException("No cached timestamp to load")); + } + + public Optional findTimestamp() throws IOException { + if (timestamp == null) { + timestamp = localStore.loadTimestamp().orElse(null); + } + return Optional.ofNullable(timestamp); + } + + public void setSnapshot(Snapshot snapshot) throws IOException { + localStore.storeMeta(RootRole.SNAPSHOT, snapshot); + this.snapshot = snapshot; + } + + public Snapshot getSnapshot() throws IOException { + return findSnapshot() + .orElseThrow(() -> new IllegalStateException("No cached snapshot to load")); + } + + public Optional findSnapshot() throws IOException { + if (snapshot == null) { + snapshot = localStore.loadSnapshot().orElse(null); + } + return Optional.ofNullable(snapshot); + } + + public void setTargets(Targets targets) throws IOException { + localStore.storeMeta(RootRole.TARGETS, targets); + this.targets = targets; + } + + public Targets getTargets() throws IOException { + return findTargets().orElseThrow(() -> new IllegalStateException("No cached targets to load")); + } + + public Optional findTargets() throws IOException { + if (targets == null) { + targets = localStore.loadTargets().orElse(null); + } + return Optional.ofNullable(targets); + } + + public void clearMetaDueToKeyRotation() throws IOException { + localStore.clearMetaDueToKeyRotation(); + timestamp = null; + snapshot = null; + } +} diff --git a/sigstore-java/src/main/java/dev/sigstore/tuf/Updater.java b/sigstore-java/src/main/java/dev/sigstore/tuf/Updater.java index 05f622c9..c2580b52 100644 --- a/sigstore-java/src/main/java/dev/sigstore/tuf/Updater.java +++ b/sigstore-java/src/main/java/dev/sigstore/tuf/Updater.java @@ -36,6 +36,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.logging.Level; import java.util.logging.Logger; @@ -57,13 +58,17 @@ public class Updater { private static final Logger log = Logger.getLogger(Updater.class.getName()); - private Clock clock; - private Verifiers.Supplier verifiers; - private MetaFetcher metaFetcher; - private Fetcher targetFetcher; + private final Clock clock; + private final Verifiers.Supplier verifiers; + private final MetaFetcher metaFetcher; + private final Fetcher targetFetcher; + private final RootProvider trustedRootPath; + // TODO: this should be replaced by a dedicated target store + private final MutableTufStore localStore; + + // Mutable State private ZonedDateTime updateStartTime; - private RootProvider trustedRootPath; - private MutableTufStore localStore; + private TrustedMeta trustedMeta; Updater( Clock clock, @@ -78,6 +83,7 @@ public class Updater { this.localStore = localStore; this.metaFetcher = metaFetcher; this.targetFetcher = targetFetcher; + this.trustedMeta = TrustedMeta.newTrustedMeta(localStore); } public static Builder builder() { @@ -86,28 +92,36 @@ public static Builder builder() { public void update() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { - var root = updateRoot(); - // only returns a timestamp value if a more recent timestamp file has been found. - var timestampMaybe = updateTimestamp(root); - if (timestampMaybe.isPresent()) { - var snapshot = updateSnapshot(root, timestampMaybe.get()); - var targets = updateTargets(root, snapshot); - downloadTargets(targets); + updateMeta(); + downloadTargets(trustedMeta.getTargets()); + } + + void updateMeta() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { + updateRoot(); + var oldTimestamp = trustedMeta.findTimestamp(); + updateTimestamp(); + if (Objects.equals(oldTimestamp.orElse(null), trustedMeta.getTimestamp()) + && trustedMeta.findSnapshot().isPresent() + && trustedMeta.findTargets().isPresent()) { + return; } + // if we need to update or we can't find targets/timestamps locally then grab new snapshot and + // targets from remote + updateSnapshot(); + updateTargets(); } // https://theupdateframework.github.io/specification/latest/#detailed-client-workflow - Root updateRoot() + void updateRoot() throws IOException, RoleExpiredException, NoSuchAlgorithmException, InvalidKeySpecException, - InvalidKeyException, FileExceedsMaxLengthException, RollbackVersionException, - SignatureVerificationException { + FileExceedsMaxLengthException, RollbackVersionException, SignatureVerificationException { // 5.3.1) record the time at start and use for expiration checks consistently throughout the // update. updateStartTime = ZonedDateTime.now(clock); // 5.3.2) load the trust metadata file (root.json), get version of root.json and the role // signature threshold value - Optional localRoot = localStore.loadTrustedRoot(); + Optional localRoot = trustedMeta.findRoot(); Root trustedRoot; if (localRoot.isPresent()) { trustedRoot = localRoot.get(); @@ -148,7 +162,7 @@ Root updateRoot() // 5.3.7) set the trusted root metadata to the new root trustedRoot = newRoot; // 5.3.8) persist to repo - localStore.storeTrustedRoot(trustedRoot); + trustedMeta.setRoot(trustedRoot); // 5.3.9) see if there are more versions go back 5.3.3 nextVersion++; } @@ -164,9 +178,9 @@ Root updateRoot() || hasNewKeys( preUpdateTimestampRole, trustedRoot.getSignedMeta().getRoles().get(RootRole.TIMESTAMP))) { - localStore.clearMetaDueToKeyRotation(); + trustedMeta.clearMetaDueToKeyRotation(); } - return trustedRoot; + trustedMeta.setRoot(trustedRoot); } private void throwIfExpired(ZonedDateTime expires) { @@ -265,9 +279,9 @@ void verifyDelegate( } } - Optional updateTimestamp(Root root) - throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, - FileNotFoundException, SignatureVerificationException { + void updateTimestamp() + throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, FileNotFoundException, + SignatureVerificationException { // 1) download the timestamp.json bytes. var timestamp = metaFetcher @@ -276,12 +290,12 @@ Optional updateTimestamp(Root root) .getMetaResource(); // 2) verify against threshold of keys as specified in trusted root.json - verifyDelegate(root, timestamp); + verifyDelegate(trustedMeta.getRoot(), timestamp); // 3) If the new timestamp file has a lesser version than our current trusted timestamp file - // report a rollback attack. If it is equal abort the update as there should be no changes. If - // it is higher than continue update. - Optional localTimestampMaybe = localStore.loadTimestamp(); + // report a rollback attack. If it is equal, just return the original timestamp there should + // be no changes. If it is higher than continue update. + Optional localTimestampMaybe = trustedMeta.findTimestamp(); if (localTimestampMaybe.isPresent()) { Timestamp localTimestamp = localTimestampMaybe.get(); if (localTimestamp.getSignedMeta().getVersion() > timestamp.getSignedMeta().getVersion()) { @@ -289,28 +303,28 @@ Optional updateTimestamp(Root root) localTimestamp.getSignedMeta().getVersion(), timestamp.getSignedMeta().getVersion()); } if (localTimestamp.getSignedMeta().getVersion() == timestamp.getSignedMeta().getVersion()) { - return Optional.empty(); + trustedMeta.setTimestamp(localTimestamp); + return; } } // 4) check expiration timestamp is after tuf update start time, else fail. throwIfExpired(timestamp.getSignedMeta().getExpiresAsDate()); // 5) persist timestamp.json - localStore.storeMeta(RootRole.TIMESTAMP, timestamp); - return Optional.of(timestamp); + trustedMeta.setTimestamp(timestamp); } - Snapshot updateSnapshot(Root root, Timestamp timestamp) + void updateSnapshot() throws IOException, FileNotFoundException, InvalidHashesException, - SignatureVerificationException, NoSuchAlgorithmException, InvalidKeySpecException, - InvalidKeyException { + SignatureVerificationException, NoSuchAlgorithmException, InvalidKeySpecException { // 1) download the snapshot.json bytes up to timestamp's snapshot length. - int timestampSnapshotVersion = timestamp.getSignedMeta().getSnapshotMeta().getVersion(); + int timestampSnapshotVersion = + trustedMeta.getTimestamp().getSignedMeta().getSnapshotMeta().getVersion(); var snapshotResult = metaFetcher.getMeta( RootRole.SNAPSHOT, timestampSnapshotVersion, Snapshot.class, - timestamp.getSignedMeta().getSnapshotMeta().getLengthOrDefault()); + trustedMeta.getTimestamp().getSignedMeta().getSnapshotMeta().getLengthOrDefault()); if (snapshotResult.isEmpty()) { throw new FileNotFoundException( timestampSnapshotVersion + ".snapshot.json", metaFetcher.getSource()); @@ -318,14 +332,14 @@ Snapshot updateSnapshot(Root root, Timestamp timestamp) // 2) check against timestamp.snapshot.hash, this is optional, the fallback is // that the version must match, which is handled in (4). var snapshot = snapshotResult.get(); - if (timestamp.getSignedMeta().getSnapshotMeta().getHashes().isPresent()) { + if (trustedMeta.getTimestamp().getSignedMeta().getSnapshotMeta().getHashes().isPresent()) { verifyHashes( "snapshot", snapshot.getRawBytes(), - timestamp.getSignedMeta().getSnapshotMeta().getHashes().get()); + trustedMeta.getTimestamp().getSignedMeta().getSnapshotMeta().getHashes().get()); } // 3) Check against threshold of root signing keys, else fail - verifyDelegate(root, snapshot.getMetaResource()); + verifyDelegate(trustedMeta.getRoot(), snapshot.getMetaResource()); // 4) Check snapshot.version matches timestamp.snapshot.version, else fail. int snapshotVersion = snapshot.getMetaResource().getSignedMeta().getVersion(); if (snapshotVersion != timestampSnapshotVersion) { @@ -334,7 +348,7 @@ Snapshot updateSnapshot(Root root, Timestamp timestamp) // 5) Ensure all targets and delegated targets in the trusted (old) snapshots file have versions // which are less than or equal to the equivalent target in the new file. Check that no targets // are missing in new file. Else fail. - var trustedSnapshotMaybe = localStore.loadSnapshot(); + var trustedSnapshotMaybe = trustedMeta.findSnapshot(); if (trustedSnapshotMaybe.isPresent()) { var trustedSnapshot = trustedSnapshotMaybe.get(); for (Map.Entry trustedTargetEntry : @@ -356,8 +370,7 @@ Snapshot updateSnapshot(Root root, Timestamp timestamp) // 6) Ensure expiration timestamp of snapshot is later than tuf update start time. throwIfExpired(snapshot.getMetaResource().getSignedMeta().getExpiresAsDate()); // 7) persist snapshot. - localStore.storeMeta(RootRole.SNAPSHOT, snapshot.getMetaResource()); - return snapshot.getMetaResource(); + trustedMeta.setSnapshot(snapshot.getMetaResource()); } // this method feels very wrong. I would not show it to a friend. @@ -389,12 +402,13 @@ static void verifyHashes(String name, byte[] data, Hashes hashes) throws Invalid } } - Targets updateTargets(Root root, Snapshot snapshot) + void updateTargets() throws IOException, FileNotFoundException, InvalidHashesException, SignatureVerificationException, NoSuchAlgorithmException, InvalidKeySpecException, - InvalidKeyException, FileExceedsMaxLengthException { + FileExceedsMaxLengthException { // 1) download the targets.json up to targets.json length in bytes. - SnapshotMeta.SnapshotTarget targetMeta = snapshot.getSignedMeta().getTargetMeta("targets.json"); + SnapshotMeta.SnapshotTarget targetMeta = + trustedMeta.getSnapshot().getSignedMeta().getTargetMeta("targets.json"); var targetsResultMaybe = metaFetcher.getMeta( RootRole.TARGETS, @@ -415,7 +429,7 @@ Targets updateTargets(Root root, Snapshot snapshot) targetMeta.getHashes().get()); } // 3) check against threshold of keys as specified by trusted root.json - verifyDelegate(root, targetsResult.getMetaResource()); + verifyDelegate(trustedMeta.getRoot(), targetsResult.getMetaResource()); // 4) check targets.version == snapshot.targets.version, else fail. int targetsVersion = targetsResult.getMetaResource().getSignedMeta().getVersion(); int snapshotTargetsVersion = targetMeta.getVersion(); @@ -426,8 +440,7 @@ Targets updateTargets(Root root, Snapshot snapshot) throwIfExpired(targetsResult.getMetaResource().getSignedMeta().getExpiresAsDate()); // 6) persist targets metadata // why do we persist the - localStore.storeMeta(RootRole.TARGETS, targetsResult.getMetaResource()); - return targetsResult.getMetaResource(); + trustedMeta.setTargets(targetsResult.getMetaResource()); } void downloadTargets(Targets targets) @@ -470,6 +483,11 @@ MutableTufStore getLocalStore() { return localStore; } + @VisibleForTesting + TrustedMeta getTrustedMeta() { + return trustedMeta; + } + public static class Builder { private Clock clock = Clock.systemUTC(); private Verifiers.Supplier verifiers = Verifiers::newVerifier; diff --git a/sigstore-java/src/test/java/dev/sigstore/tuf/TrustedMetaTest.java b/sigstore-java/src/test/java/dev/sigstore/tuf/TrustedMetaTest.java new file mode 100644 index 00000000..57e47a4e --- /dev/null +++ b/sigstore-java/src/test/java/dev/sigstore/tuf/TrustedMetaTest.java @@ -0,0 +1,182 @@ +/* + * Copyright 2024 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 static org.junit.jupiter.api.Assertions.assertTrue; + +import com.google.common.io.Resources; +import dev.sigstore.tuf.model.Root; +import dev.sigstore.tuf.model.Snapshot; +import dev.sigstore.tuf.model.Targets; +import dev.sigstore.tuf.model.Timestamp; +import java.io.BufferedWriter; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class TrustedMetaTest { + + @TempDir Path localStore; + private MutableTufStore tufStore; + private TrustedMeta trustedMeta; + + private static Root root; + private static Timestamp timestamp; + private static Snapshot snapshot; + private static Targets targets; + + @BeforeAll + public static void readAllMeta() throws IOException, URISyntaxException { + Path rootResource = + Path.of(Resources.getResource("dev/sigstore/tuf/real/prod/root.json").getPath()); + root = GSON.get().fromJson(Files.newBufferedReader(rootResource), Root.class); + Path timestampResource = + Path.of(Resources.getResource("dev/sigstore/tuf/real/prod/timestamp.json").getPath()); + timestamp = GSON.get().fromJson(Files.newBufferedReader(timestampResource), Timestamp.class); + Path snapshotResource = + Path.of(Resources.getResource("dev/sigstore/tuf/real/prod/snapshot.json").getPath()); + snapshot = GSON.get().fromJson(Files.newBufferedReader(snapshotResource), Snapshot.class); + Path targetsResource = + Path.of(Resources.getResource("dev/sigstore/tuf/real/prod/targets.json").getPath()); + targets = GSON.get().fromJson(Files.newBufferedReader(targetsResource), Targets.class); + } + + @BeforeEach + public void setup() throws IOException { + tufStore = FileSystemTufStore.newFileSystemStore(localStore); + trustedMeta = TrustedMeta.newTrustedMeta(tufStore); + } + + @Test + public void root_test() throws Exception { + assertTrue(tufStore.loadTrustedRoot().isEmpty()); + assertTrue(trustedMeta.findRoot().isEmpty()); + Assertions.assertThrows(IllegalStateException.class, trustedMeta::getRoot); + + trustedMeta.setRoot(root); + + assertTrue(tufStore.loadTrustedRoot().isPresent()); + assertTrue(trustedMeta.findRoot().isPresent()); + Assertions.assertEquals(root, trustedMeta.getRoot()); + } + + @Test + public void root_canInitFromDisk() throws Exception { + assertTrue(tufStore.loadTrustedRoot().isEmpty()); + assertTrue(trustedMeta.findRoot().isEmpty()); + Assertions.assertThrows(IllegalStateException.class, trustedMeta::getRoot); + + try (BufferedWriter fileWriter = Files.newBufferedWriter(localStore.resolve("root.json"))) { + GSON.get().toJson(root, fileWriter); + } + + assertTrue(tufStore.loadTrustedRoot().isPresent()); + assertTrue(trustedMeta.findRoot().isPresent()); + Assertions.assertEquals(root, trustedMeta.getRoot()); + } + + @Test + public void timestamp_test() throws Exception { + assertTrue(tufStore.loadTimestamp().isEmpty()); + assertTrue(trustedMeta.findTimestamp().isEmpty()); + Assertions.assertThrows(IllegalStateException.class, trustedMeta::getTimestamp); + + trustedMeta.setTimestamp(timestamp); + + assertTrue(tufStore.loadTimestamp().isPresent()); + assertTrue(trustedMeta.findTimestamp().isPresent()); + Assertions.assertEquals(timestamp, trustedMeta.getTimestamp()); + } + + @Test + public void timestamp_canInitFromDisk() throws Exception { + assertTrue(tufStore.loadTimestamp().isEmpty()); + assertTrue(trustedMeta.findTimestamp().isEmpty()); + Assertions.assertThrows(IllegalStateException.class, trustedMeta::getTimestamp); + + try (BufferedWriter fileWriter = + Files.newBufferedWriter(localStore.resolve("timestamp.json"))) { + GSON.get().toJson(timestamp, fileWriter); + } + + assertTrue(tufStore.loadTimestamp().isPresent()); + assertTrue(trustedMeta.findTimestamp().isPresent()); + Assertions.assertEquals(timestamp, trustedMeta.getTimestamp()); + } + + @Test + public void snapshot_test() throws Exception { + assertTrue(tufStore.loadSnapshot().isEmpty()); + assertTrue(trustedMeta.findSnapshot().isEmpty()); + Assertions.assertThrows(IllegalStateException.class, trustedMeta::getSnapshot); + + trustedMeta.setSnapshot(snapshot); + + assertTrue(tufStore.loadSnapshot().isPresent()); + assertTrue(trustedMeta.findSnapshot().isPresent()); + Assertions.assertEquals(snapshot, trustedMeta.getSnapshot()); + } + + @Test + public void snapshot_canInitFromDisk() throws Exception { + assertTrue(tufStore.loadSnapshot().isEmpty()); + assertTrue(trustedMeta.findSnapshot().isEmpty()); + Assertions.assertThrows(IllegalStateException.class, trustedMeta::getSnapshot); + + try (BufferedWriter fileWriter = Files.newBufferedWriter(localStore.resolve("snapshot.json"))) { + GSON.get().toJson(snapshot, fileWriter); + } + + assertTrue(tufStore.loadSnapshot().isPresent()); + assertTrue(trustedMeta.findSnapshot().isPresent()); + Assertions.assertEquals(snapshot, trustedMeta.getSnapshot()); + } + + @Test + public void targets_test() throws Exception { + assertTrue(tufStore.loadTargets().isEmpty()); + assertTrue(trustedMeta.findTargets().isEmpty()); + Assertions.assertThrows(IllegalStateException.class, trustedMeta::getTargets); + + trustedMeta.setTargets(targets); + + assertTrue(tufStore.loadTargets().isPresent()); + assertTrue(trustedMeta.findTargets().isPresent()); + Assertions.assertEquals(targets, trustedMeta.getTargets()); + } + + @Test + public void targets_canInitFromDisk() throws Exception { + assertTrue(tufStore.loadTargets().isEmpty()); + assertTrue(trustedMeta.findTargets().isEmpty()); + Assertions.assertThrows(IllegalStateException.class, trustedMeta::getTargets); + + try (BufferedWriter fileWriter = Files.newBufferedWriter(localStore.resolve("targets.json"))) { + GSON.get().toJson(targets, fileWriter); + } + + assertTrue(tufStore.loadTargets().isPresent()); + assertTrue(trustedMeta.findTargets().isPresent()); + Assertions.assertEquals(targets, trustedMeta.getTargets()); + } +} diff --git a/sigstore-java/src/test/java/dev/sigstore/tuf/UpdaterTest.java b/sigstore-java/src/test/java/dev/sigstore/tuf/UpdaterTest.java index 8aebf0ea..2fa2903b 100644 --- a/sigstore-java/src/test/java/dev/sigstore/tuf/UpdaterTest.java +++ b/sigstore-java/src/test/java/dev/sigstore/tuf/UpdaterTest.java @@ -15,6 +15,7 @@ */ package dev.sigstore.tuf; +import static dev.sigstore.json.GsonSupplier.GSON; import static dev.sigstore.testkit.tuf.TestResources.UPDATER_REAL_TRUSTED_ROOT; import static dev.sigstore.testkit.tuf.TestResources.UPDATER_SYNTHETIC_TRUSTED_ROOT; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; @@ -40,10 +41,8 @@ import dev.sigstore.tuf.model.Role; import dev.sigstore.tuf.model.Root; import dev.sigstore.tuf.model.Signature; -import dev.sigstore.tuf.model.Snapshot; import dev.sigstore.tuf.model.TargetMeta; import dev.sigstore.tuf.model.Targets; -import dev.sigstore.tuf.model.Timestamp; import io.github.netmikey.logunit.api.LogCapturer; import java.io.File; import java.io.IOException; @@ -71,6 +70,8 @@ import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ResourceHandler; import org.eclipse.jetty.util.resource.Resource; +import org.hamcrest.CoreMatchers; +import org.hamcrest.MatcherAssert; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.junit.jupiter.api.AfterAll; @@ -124,8 +125,7 @@ static void startRemoteResourceServer() throws Exception { } @Test - public void testRootUpdate_fromProdData() - throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + public void testRootUpdate_fromProdData() throws Exception { setupMirror( "real/prod", "1.root.json", "2.root.json", "3.root.json", "4.root.json", "5.root.json"); var updater = createTimeStaticUpdater(localStorePath, UPDATER_REAL_TRUSTED_ROOT); @@ -193,8 +193,7 @@ public void testRootUpdate_newRootHasInvalidSignatures() throws Exception { } @Test - public void testRootUpdate_expiredRoot() - throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + public void testRootUpdate_expiredRoot() throws Exception { setupMirror("synthetic/test-template", "2.root.json"); // root expires 2023-03-09T18:02:21Z var updater = @@ -211,9 +210,7 @@ public void testRootUpdate_expiredRoot() } @Test - public void testRootUpdate_wrongVersion() - throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, - SignatureVerificationException { + public void testRootUpdate_wrongVersion() throws Exception { setupMirror("synthetic/root-wrong-version", "2.root.json"); var updater = createTimeStaticUpdater(localStorePath, UPDATER_SYNTHETIC_TRUSTED_ROOT); try { @@ -226,8 +223,7 @@ public void testRootUpdate_wrongVersion() } @Test - public void testRootUpdate_metaFileTooBig() - throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + public void testRootUpdate_metaFileTooBig() throws Exception { setupMirror("synthetic/root-too-big", "2.root.json"); var updater = createTimeStaticUpdater(localStorePath, UPDATER_SYNTHETIC_TRUSTED_ROOT); try { @@ -239,85 +235,70 @@ public void testRootUpdate_metaFileTooBig() } @Test - public void testTimestampUpdate_throwMetaNotFoundException() throws IOException { + public void testTimestampUpdate_throwMetaNotFoundException() throws Exception { setupMirror("synthetic/test-template", "2.root.json"); var updater = createTimeStaticUpdater(localStorePath, UPDATER_SYNTHETIC_TRUSTED_ROOT); - assertThrows(FileNotFoundException.class, () -> updater.updateTimestamp(updater.updateRoot())); + var ex = assertThrows(FileNotFoundException.class, updater::update); + MatcherAssert.assertThat( + ex.getMessage(), CoreMatchers.startsWith("file (timestamp.json) was not found at source")); } @Test - public void testTimestampUpdate_throwsSignatureVerificationException() - throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + public void testTimestampUpdate_throwsSignatureVerificationException() throws Exception { setupMirror("synthetic/timestamp-unsigned", "2.root.json", "timestamp.json"); var updater = createTimeStaticUpdater(localStorePath, UPDATER_SYNTHETIC_TRUSTED_ROOT); - try { - updater.updateTimestamp(updater.updateRoot()); - fail("The timestamp was not signed so should have thown a SignatureVerificationException."); - } catch (SignatureVerificationException e) { - assertEquals(0, e.getVerifiedSignatures(), "verified signature threshold did not match"); - assertEquals(1, e.getRequiredSignatures(), "required signatures found did not match"); - } + var ex = + assertThrows( + SignatureVerificationException.class, + updater::update, + "The timestamp was not signed so should have thown a SignatureVerificationException."); + assertEquals(0, ex.getVerifiedSignatures(), "verified signature threshold did not match"); + assertEquals(1, ex.getRequiredSignatures(), "required signatures found did not match"); } @Test - public void testTimestampUpdate_throwsRollbackVersionException() - throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + public void testTimestampUpdate_throwsRollbackVersionException() throws Exception { bootstrapLocalStore(localStorePath, "synthetic/test-template", "root.json", "timestamp.json"); setupMirror("synthetic/timestamp-rollback-version", "2.root.json", "timestamp.json"); var updater = createTimeStaticUpdater(localStorePath, UPDATER_SYNTHETIC_TRUSTED_ROOT); - try { - updater.updateTimestamp(updater.updateRoot()); - fail( - "The repo in this test provides an older signed timestamp version that should have caused a RoleVersionException."); - } catch (RollbackVersionException e) { - assertEquals(3, e.getCurrentVersion(), "expected timestamp version did not match"); - assertEquals(1, e.getFoundVersion(), "found timestamp version did not match"); - } + var ex = + assertThrows( + RollbackVersionException.class, + updater::update, + "The repo in this test provides an older signed timestamp version that should have caused a RoleVersionException."); + assertEquals(3, ex.getCurrentVersion(), "expected timestamp version did not match"); + assertEquals(1, ex.getFoundVersion(), "found timestamp version did not match"); } @Test - public void testTimestampUpdate_noUpdate() - throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { - bootstrapLocalStore(localStorePath, "synthetic/test-template", "2.root.json", "timestamp.json"); - setupMirror("synthetic/test-template", "2.root.json", "timestamp.json"); - var updater = createTimeStaticUpdater(localStorePath, UPDATER_SYNTHETIC_TRUSTED_ROOT); - assertTrue( - updater.updateTimestamp(updater.updateRoot()).isEmpty(), - "We expect the updater to return an empty timestamp if there are no updates"); - } - - @Test - public void testTimestampUpdate_throwsRoleExpiredException() - throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + public void testTimestampUpdate_throwsRoleExpiredException() throws Exception { setupMirror("synthetic/test-template", "2.root.json", "timestamp.json"); // timestamp expires 2022-12-10T18:07:30Z var updater = createTimeStaticUpdater( localStorePath, UPDATER_SYNTHETIC_TRUSTED_ROOT, "2023-02-13T15:37:49Z"); - try { - updater.updateTimestamp(updater.updateRoot()); - fail("Expects a RoleExpiredException as the repo timestamp.json should be expired."); - } catch (RoleExpiredException e) { - // expected. - } + + assertThrows( + RoleExpiredException.class, + updater::update, + "Expects a RoleExpiredException as the repo timestamp.json should be expired."); } @Test - public void testTimestampUpdate_noPreviousTimestamp_success() - throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + public void testTimestampUpdate_noPreviousTimestamp_success() throws Exception { setupMirror("synthetic/test-template", "2.root.json", "timestamp.json"); var updater = createTimeStaticUpdater(localStorePath, UPDATER_SYNTHETIC_TRUSTED_ROOT); - updater.updateTimestamp(updater.updateRoot()); + updater.updateRoot(); + updater.updateTimestamp(); assertStoreContains("timestamp.json"); assertEquals( 3, - updater.getLocalStore().loadTimestamp().get().getSignedMeta().getVersion(), + updater.getTrustedMeta().getTimestamp().getSignedMeta().getVersion(), "timestamp version did not match expectations"); } @Test - public void testTimestampUpdate_updateExistingTimestamp_success() - throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + public void testTimestampUpdate_updateExistingTimestamp_success() throws Exception { bootstrapLocalStore( localStorePath, "synthetic/test-template", "1.root.json", "1.timestamp.json"); setupMirror("synthetic/test-template", "1.root.json", "2.root.json", "timestamp.json"); @@ -326,62 +307,51 @@ public void testTimestampUpdate_updateExistingTimestamp_success() 1, updater.getLocalStore().loadTimestamp().get().getSignedMeta().getVersion(), "timestamp version should start at 1 before the update."); - updater.updateTimestamp(updater.updateRoot()); + updater.updateRoot(); + updater.updateTimestamp(); assertStoreContains("timestamp.json"); assertEquals( 3, - updater.getLocalStore().loadTimestamp().get().getSignedMeta().getVersion(), + updater.getTrustedMeta().getTimestamp().getSignedMeta().getVersion(), "timestamp version did not match expectations."); } @Test - public void testSnapshotUpdate_snapshotMetaMissing() - throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + public void testSnapshotUpdate_snapshotMetaMissing() throws Exception { setupMirror("synthetic/test-template", "2.root.json", "timestamp.json"); var updater = createTimeStaticUpdater(localStorePath, UPDATER_SYNTHETIC_TRUSTED_ROOT); - var root = updater.updateRoot(); - var timestamp = updater.updateTimestamp(root); + updater.updateRoot(); + updater.updateTimestamp(); assertThrows( FileNotFoundException.class, - () -> updater.updateSnapshot(root, timestamp.get()), + updater::updateSnapshot, "Expected remote with no snapshot.json to throw FileNotFoundException."); } @Test - public void testSnapshotUpdate_invalidHash() - throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + public void testSnapshotUpdate_invalidHash() throws Exception { setupMirror( "synthetic/snapshot-invalid-hash", "2.root.json", "timestamp.json", "3.snapshot.json"); var updater = createTimeStaticUpdater(localStorePath, UPDATER_SYNTHETIC_TRUSTED_ROOT); - var root = updater.updateRoot(); - var timestamp = updater.updateTimestamp(root); assertThrows( InvalidHashesException.class, - () -> { - updater.updateSnapshot(root, timestamp.get()); - }, + updater::update, "snapshot.json edited and should fail hash test."); } @Test - public void testSnapshotUpdate_timestampSnapshotVersionMismatch() - throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + public void testSnapshotUpdate_timestampSnapshotVersionMismatch() throws Exception { setupMirror( "synthetic/snapshot-version-mismatch", "2.root.json", "timestamp.json", "3.snapshot.json"); var updater = createTimeStaticUpdater(localStorePath, UPDATER_SYNTHETIC_TRUSTED_ROOT); - var root = updater.updateRoot(); - var timestamp = updater.updateTimestamp(root); assertThrows( SnapshotVersionMismatchException.class, - () -> { - updater.updateSnapshot(root, timestamp.get()); - }, + updater::update, "snapshot version should not match the timestamp metadata."); } @Test - public void testSnapshotUpdate_snapshotTargetMissing() - throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + public void testSnapshotUpdate_snapshotTargetMissing() throws Exception { bootstrapLocalStore( localStorePath, "synthetic/test-template", @@ -391,19 +361,14 @@ public void testSnapshotUpdate_snapshotTargetMissing() setupMirror( "synthetic/snapshot-target-missing", "2.root.json", "timestamp.json", "4.snapshot.json"); var updater = createTimeStaticUpdater(localStorePath, UPDATER_SYNTHETIC_TRUSTED_ROOT); - var root = updater.updateRoot(); - var timestamp = updater.updateTimestamp(root); assertThrows( SnapshotTargetMissingException.class, - () -> { - updater.updateSnapshot(root, timestamp.get()); - }, + updater::update, "All targets from previous versions of snapshot should be contained in future versions of snapshot."); } @Test - public void testSnapshotUpdate_snapshotTargetVersionRollback() - throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + public void testSnapshotUpdate_snapshotTargetVersionRollback() throws Exception { bootstrapLocalStore( localStorePath, "synthetic/test-template", @@ -416,30 +381,23 @@ public void testSnapshotUpdate_snapshotTargetVersionRollback() "timestamp.json", "3.snapshot.json"); var updater = createTimeStaticUpdater(localStorePath, UPDATER_SYNTHETIC_TRUSTED_ROOT); - var root = updater.updateRoot(); - var timestamp = updater.updateTimestamp(root); assertThrows( SnapshotTargetVersionException.class, - () -> { - updater.updateSnapshot(root, timestamp.get()); - }, + updater::update, "The new snapshot.json has a targets.json version that is lower than the current target and so we expect a SnapshotTargetVersionException."); } @Test - public void testSnapshotUpdate_success() - throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + public void testSnapshotUpdate_success() throws Exception { setupMirror("synthetic/test-template", "2.root.json", "timestamp.json", "3.snapshot.json"); var updater = createTimeStaticUpdater(localStorePath, UPDATER_SYNTHETIC_TRUSTED_ROOT); - Root root = updater.updateRoot(); - Timestamp timestamp = updater.updateTimestamp(root).get(); - Snapshot result = updater.updateSnapshot(root, timestamp); - assertNotNull(result); + updater.updateRoot(); + updater.updateTimestamp(); + updater.updateSnapshot(); } @Test - public void testSnapshotUpdate_expired() - throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + public void testSnapshotUpdate_expired() throws Exception { setupMirror("synthetic/snapshot-expired", "2.root.json", "timestamp.json", "3.snapshot.json"); // snapshot expires 2022-11-19T18:07:27Z var updater = @@ -447,33 +405,24 @@ public void testSnapshotUpdate_expired() localStorePath, UPDATER_SYNTHETIC_TRUSTED_ROOT, "2022-11-20T18:07:27Z"); // one day after - Root root = updater.updateRoot(); - Timestamp timestamp = updater.updateTimestamp(root).get(); - try { - updater.updateSnapshot(root, timestamp); - fail("Expects a RoleExpiredException as the repo snapshot.json should be expired."); - } catch (RoleExpiredException e) { - // pass - } + assertThrows( + RoleExpiredException.class, + updater::update, + "Expects a RoleExpiredException as the repo snapshot.json should be expired."); } @Test - public void testTargetsUpdate_targetMetaMissing() - throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + public void testTargetsUpdate_targetMetaMissing() throws Exception { setupMirror("synthetic/test-template", "2.root.json", "timestamp.json", "3.snapshot.json"); var updater = createTimeStaticUpdater(localStorePath, UPDATER_SYNTHETIC_TRUSTED_ROOT); - var root = updater.updateRoot(); - var timestamp = updater.updateTimestamp(root); - var snapshot = updater.updateSnapshot(root, timestamp.get()); assertThrows( FileNotFoundException.class, - () -> updater.updateTargets(root, snapshot), + updater::update, "Expected remote with no target.json to throw FileNotFoundException."); } @Test - public void testTargetsUpdate_invalidHash() - throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + public void testTargetsUpdate_invalidHash() throws Exception { setupMirror( "synthetic/targets-invalid-hash", "2.root.json", @@ -481,18 +430,14 @@ public void testTargetsUpdate_invalidHash() "3.snapshot.json", "3.targets.json"); var updater = createTimeStaticUpdater(localStorePath, UPDATER_SYNTHETIC_TRUSTED_ROOT); - var root = updater.updateRoot(); - var timestamp = updater.updateTimestamp(root); - var snapshot = updater.updateSnapshot(root, timestamp.get()); assertThrows( InvalidHashesException.class, - () -> updater.updateTargets(root, snapshot), + updater::update, "targets.json has been modified to have an invalid hash."); } @Test - public void testTargetsUpdate_snapshotVersionMismatch() - throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + public void testTargetsUpdate_snapshotVersionMismatch() throws Exception { setupMirror( "synthetic/targets-snapshot-version-mismatch", "2.root.json", @@ -500,18 +445,14 @@ public void testTargetsUpdate_snapshotVersionMismatch() "3.snapshot.json", "3.targets.json"); var updater = createTimeStaticUpdater(localStorePath, UPDATER_SYNTHETIC_TRUSTED_ROOT); - var root = updater.updateRoot(); - var timestamp = updater.updateTimestamp(root); - var snapshot = updater.updateSnapshot(root, timestamp.get()); assertThrows( SnapshotVersionMismatchException.class, - () -> updater.updateTargets(root, snapshot), + updater::update, "targets version should not match the snapshot targets metadata."); } @Test - public void testTargetsUpdate_targetExpired() - throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + public void testTargetsUpdate_targetExpired() throws Exception { // targets expires 2022-11-19T18:07:27Z setupMirror( "synthetic/targets-expired", @@ -524,12 +465,9 @@ public void testTargetsUpdate_targetExpired() localStorePath, UPDATER_SYNTHETIC_TRUSTED_ROOT, "2022-11-20T18:07:27Z"); // one day after - var root = updater.updateRoot(); - var timestamp = updater.updateTimestamp(root); - var snapshot = updater.updateSnapshot(root, timestamp.get()); assertThrows( RoleExpiredException.class, - () -> updater.updateTargets(root, snapshot), + updater::update, "targets are out of date and should cause RoleExpiredException."); } @@ -545,19 +483,18 @@ public void testTargetsUpdate_success() var updater = createTimeStaticUpdater( localStorePath, UPDATER_SYNTHETIC_TRUSTED_ROOT, "2022-11-20T18:07:27Z"); - var root = updater.updateRoot(); - var timestamp = updater.updateTimestamp(root); - var snapshot = updater.updateSnapshot(root, timestamp.get()); - var targets = updater.updateTargets(root, snapshot); - assertNotNull(targets); - assertNotNull(updater.getLocalStore().loadTargets()); - assertEquals( - targets.getSignedMeta(), updater.getLocalStore().loadTargets().get().getSignedMeta()); + updater.updateMeta(); + var localTargets = updater.getTrustedMeta().getTargets(); + assertNotNull(localTargets); + var remoteTargets = + GSON.get() + .fromJson( + Files.newBufferedReader(localMirrorPath.resolve("3.targets.json")), Targets.class); + assertEquals(localTargets.getSignedMeta(), remoteTargets.getSignedMeta()); } @Test - public void testTargetsDownload_targetMissingTargetMetadata() - throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + public void testTargetsDownload_targetMissingTargetMetadata() throws Exception { setupMirror( "synthetic/targets-download-missing-target-metadata", "2.root.json", @@ -565,13 +502,15 @@ public void testTargetsDownload_targetMissingTargetMetadata() "3.snapshot.json", "3.targets.json"); var updater = createTimeStaticUpdater(localStorePath, UPDATER_SYNTHETIC_TRUSTED_ROOT); - var root = updater.updateRoot(); - var timestamp = updater.updateTimestamp(root); - var snapshot = updater.updateSnapshot(root, timestamp.get()); - assertThrows( - JsonSyntaxException.class, - () -> updater.updateTargets(root, snapshot), - "targets.json data should be causing a gson error due to missing TargetData. If at some point we support nullable TargetData this test should be updated to expect TargetMetadataMissingException while calling downloadTargets()."); + var ex = + assertThrows( + JsonSyntaxException.class, + updater::update, + "targets.json data should be causing a gson error due to missing TargetData. If at some point we support nullable TargetData this test should be updated to expect TargetMetadataMissingException while calling downloadTargets()."); + MatcherAssert.assertThat( + ex.getMessage(), + CoreMatchers.endsWith( + "Cannot build TargetData, some of required attributes are not set [hashes, length]")); } @Test @@ -584,13 +523,10 @@ public void testTargetsDownload_targetFileNotFound() "3.snapshot.json", "3.targets.json"); var updater = createTimeStaticUpdater(localStorePath, UPDATER_SYNTHETIC_TRUSTED_ROOT); - var root = updater.updateRoot(); - var timestamp = updater.updateTimestamp(root); - var snapshot = updater.updateSnapshot(root, timestamp.get()); - var targets = updater.updateTargets(root, snapshot); + updater.updateMeta(); assertThrows( FileNotFoundException.class, - () -> updater.downloadTargets(targets), + () -> updater.downloadTargets(updater.getTrustedMeta().getTargets()), "the target file for download should be missing from the repo and cause an exception."); } @@ -605,13 +541,10 @@ public void testTargetsDownload_targetInvalidLength() "3.targets.json", "targets/860de8f9a858eea7190fcfa1b53fe55914d3c38f17f8f542273012d19cc9509bb423f37b7c13c577a56339ad7f45273b479b1d0df837cb6e20a550c27cce0885.test.txt"); var updater = createTimeStaticUpdater(localStorePath, UPDATER_SYNTHETIC_TRUSTED_ROOT); - var root = updater.updateRoot(); - var timestamp = updater.updateTimestamp(root); - var snapshot = updater.updateSnapshot(root, timestamp.get()); - var targets = updater.updateTargets(root, snapshot); + updater.updateMeta(); assertThrows( FileExceedsMaxLengthException.class, - () -> updater.downloadTargets(targets), + () -> updater.downloadTargets(updater.getTrustedMeta().getTargets()), "The target file is expected to not match the length specified in targets.json target data."); } @@ -626,19 +559,15 @@ public void testTargetsDownload_targetFileInvalidHash() "3.targets.json", "targets/860de8f9a858eea7190fcfa1b53fe55914d3c38f17f8f542273012d19cc9509bb423f37b7c13c577a56339ad7f45273b479b1d0df837cb6e20a550c27cce0885.test.txt"); var updater = createTimeStaticUpdater(localStorePath, UPDATER_SYNTHETIC_TRUSTED_ROOT); - var root = updater.updateRoot(); - var timestamp = updater.updateTimestamp(root); - var snapshot = updater.updateSnapshot(root, timestamp.get()); - var targets = updater.updateTargets(root, snapshot); + updater.updateMeta(); assertThrows( InvalidHashesException.class, - () -> updater.downloadTargets(targets), + () -> updater.downloadTargets(updater.getTrustedMeta().getTargets()), "The target file has been modified and should not match the expected hash"); } @Test - public void testTargetsDownload_success() - throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + public void testTargetsDownload_success() throws Exception { setupMirror( "synthetic/test-template", "2.root.json", @@ -649,20 +578,15 @@ public void testTargetsDownload_success() "targets/32005f02eac21b4cf161a02495330b6c14b548622b5f7e19d59ecfa622de650603ecceea39ed86cc322749a813503a72ad14ce5462c822b511eaf2f2cd2ad8f2.test.txt.v2", "targets/53904bc6216230bf8da0ec42d34004a3f36764de698638641870e37d270e4fd13e1079285f8bca73c2857a279f6f7fbc82038274c3eb48ec5bb2da9b2e30491a.test2.txt"); var updater = createTimeStaticUpdater(localStorePath, UPDATER_SYNTHETIC_TRUSTED_ROOT); - var root = updater.updateRoot(); - var timestamp = updater.updateTimestamp(root); - var snapshot = updater.updateSnapshot(root, timestamp.get()); - var targets = updater.updateTargets(root, snapshot); - updater.downloadTargets(targets); - assertTrue(updater.getLocalStore().getTargetFile("test.txt") != null); - assertTrue(updater.getLocalStore().getTargetFile("test.txt.v2") != null); - assertTrue(updater.getLocalStore().getTargetFile("test2.txt") != null); + updater.update(); + assertNotNull(updater.getLocalStore().getTargetFile("test.txt")); + assertNotNull(updater.getLocalStore().getTargetFile("test.txt.v2")); + assertNotNull(updater.getLocalStore().getTargetFile("test2.txt")); } // Ensure we accept sha256 or sha512 on hashes for targets @Test - public void testTargetsDownload_sha256Only() - throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + public void testTargetsDownload_sha256Only() throws Exception { setupMirror( "synthetic/targets-sha256-or-sha512", "1.root.json", @@ -677,11 +601,7 @@ public void testTargetsDownload_sha256Only() Resources.getResource("dev/sigstore/tuf/synthetic/targets-sha256-or-sha512/root.json") .getPath()); var updater = createTimeStaticUpdater(localStorePath, UPDATER_ROOT); - var root = updater.updateRoot(); - var timestamp = updater.updateTimestamp(root); - var snapshot = updater.updateSnapshot(root, timestamp.get()); - var targets = updater.updateTargets(root, snapshot); - assertDoesNotThrow(() -> updater.downloadTargets(targets)); + assertDoesNotThrow(updater::update); } // End to end sanity test on the actual prod sigstore repo. @@ -901,8 +821,7 @@ public void testVerifyDelegate_verified() } @Test - public void testVerifyDelegate_verificationFailed() - throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, IOException { + public void testVerifyDelegate_verificationFailed() throws Exception { List sigs = ImmutableList.of(SIG_1, SIG_2); Map publicKeys = ImmutableMap.of(PUB_KEY_1.getLeft(), PUB_KEY_1.getRight()); @@ -920,8 +839,7 @@ public void testVerifyDelegate_verificationFailed() } @Test - public void testVerifyDelegate_belowThreshold() - throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, IOException { + public void testVerifyDelegate_belowThreshold() throws Exception { List sigs = ImmutableList.of(SIG_1, SIG_2); Map publicKeys = ImmutableMap.of(PUB_KEY_1.getLeft(), PUB_KEY_1.getRight()); @@ -997,8 +915,7 @@ public void testVerifyDelegate_goodSigsAndKeysButNotInRole() } @Test - public void testUpdate_snapshotsAndTimestampHaveNoSizeAndNoHashesInMeta() - throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { + public void testUpdate_snapshotsAndTimestampHaveNoSizeAndNoHashesInMeta() throws Exception { setupMirror( "synthetic/no-size-no-hash-snapshot-timestamp", "2.root.json", @@ -1009,9 +926,12 @@ public void testUpdate_snapshotsAndTimestampHaveNoSizeAndNoHashesInMeta() localStorePath, UPDATER_SYNTHETIC_TRUSTED_ROOT, "2022-11-20T18:07:27Z"); // one day after - Root root = updater.updateRoot(); - Timestamp timestamp = updater.updateTimestamp(root).get(); - Snapshot snapshot = updater.updateSnapshot(root, timestamp); + updater.updateRoot(); + updater.updateTimestamp(); + updater.updateSnapshot(); + + var timestamp = updater.getTrustedMeta().getTimestamp(); + var snapshot = updater.getTrustedMeta().getSnapshot(); Assertions.assertTrue(timestamp.getSignedMeta().getSnapshotMeta().getHashes().isEmpty()); Assertions.assertTrue(timestamp.getSignedMeta().getSnapshotMeta().getLength().isEmpty());