From d0ab6ebec5be7e57ae264ad499b3edcabf8e65ba Mon Sep 17 00:00:00 2001 From: Patrick Flynn Date: Mon, 19 Sep 2022 18:18:46 -0400 Subject: [PATCH 1/3] Introduce a MetaFetcher interface to aid code reuse across resource types and facilitate testing. (includes some drive by refactoring to support testing) Signed-off-by: Patrick Flynn --- .../dev/sigstore/tuf/HttpMetaFetcher.java | 76 +++++++++++++++ .../java/dev/sigstore/tuf/MetaFetcher.java | 32 +++++++ .../main/java/dev/sigstore/tuf/TufClient.java | 78 +++++++--------- .../java/dev/sigstore/tuf/TufClientTest.java | 93 ++++++++++--------- 4 files changed, 192 insertions(+), 87 deletions(-) create mode 100644 sigstore-java/src/main/java/dev/sigstore/tuf/HttpMetaFetcher.java create mode 100644 sigstore-java/src/main/java/dev/sigstore/tuf/MetaFetcher.java diff --git a/sigstore-java/src/main/java/dev/sigstore/tuf/HttpMetaFetcher.java b/sigstore-java/src/main/java/dev/sigstore/tuf/HttpMetaFetcher.java new file mode 100644 index 00000000..083a5404 --- /dev/null +++ b/sigstore-java/src/main/java/dev/sigstore/tuf/HttpMetaFetcher.java @@ -0,0 +1,76 @@ +/* + * 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.http.HttpClients; +import dev.sigstore.http.ImmutableHttpParams; +import dev.sigstore.tuf.model.Root; +import java.io.IOException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Optional; + +public class HttpMetaFetcher implements MetaFetcher { + + private static final int MAX_META_BYTES = 99 * 1024; // 99 KB + private URL mirror; + + HttpMetaFetcher(URL mirror) { + this.mirror = mirror; + } + + public static HttpMetaFetcher newFetcher(URL mirror) { + return new HttpMetaFetcher(mirror); + } + + @Override + public Optional getRootAtVersion(int version) + throws IOException, MetaFileExceedsMaxException { + String versionFileName = version + ".root.json"; + GenericUrl nextVersionUrl = new GenericUrl(mirror + "/" + versionFileName); + 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) { + return Optional.empty(); + } + 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); + } + return Optional.of( + GSON.get().fromJson(new String(rootBytes, StandardCharsets.UTF_8), Root.class)); + } +} diff --git a/sigstore-java/src/main/java/dev/sigstore/tuf/MetaFetcher.java b/sigstore-java/src/main/java/dev/sigstore/tuf/MetaFetcher.java new file mode 100644 index 00000000..3379ccfd --- /dev/null +++ b/sigstore-java/src/main/java/dev/sigstore/tuf/MetaFetcher.java @@ -0,0 +1,32 @@ +/* + * 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 dev.sigstore.tuf.model.Root; +import java.io.IOException; +import java.util.Optional; + +/** Retrieves TUF metadata. */ +public interface MetaFetcher { + + /** + * Fetch the {@link Root} at the specified {@code version}. + * + * @throws MetaFileExceedsMaxException when the retrieved file is larger than the maximum allowed + * by the client + */ + Optional getRootAtVersion(int version) throws IOException, MetaFileExceedsMaxException; +} diff --git a/sigstore-java/src/main/java/dev/sigstore/tuf/TufClient.java b/sigstore-java/src/main/java/dev/sigstore/tuf/TufClient.java index 633618c0..c8c2a528 100644 --- a/sigstore-java/src/main/java/dev/sigstore/tuf/TufClient.java +++ b/sigstore-java/src/main/java/dev/sigstore/tuf/TufClient.java @@ -17,17 +17,12 @@ import static dev.sigstore.json.GsonSupplier.GSON; -import com.google.api.client.http.GenericUrl; -import com.google.api.client.json.gson.GsonFactory; import com.google.common.annotations.VisibleForTesting; 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.IOException; import java.net.URL; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.security.InvalidKeyException; @@ -40,6 +35,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.function.Function; import org.bouncycastle.util.encoders.Hex; /** @@ -52,7 +48,6 @@ */ 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 as expressed in // 5.3.3 of spec. @@ -60,17 +55,12 @@ public class TufClient { private Clock clock; private Verifiers.Supplier verifiers; - TufClient(Clock clock, Verifiers.Supplier verifiers) { + private Function fetcherSupplier; + + TufClient(Clock clock, Verifiers.Supplier verifiers, Function fetcherSupplier) { this.clock = clock; this.verifiers = verifiers; - } - - TufClient(Verifiers.Supplier verifiers) { - this(Clock.systemUTC(), verifiers); - } - - TufClient() { - this(Clock.systemUTC(), Verifiers::newVerifier); + this.fetcherSupplier = fetcherSupplier; } private ZonedDateTime updateStartTime; @@ -80,6 +70,7 @@ public void updateRoot(Path trustedRootPath, URL mirror, TufLocalStore localStor throws IOException, RootExpiredException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, MetaFileExceedsMaxException, RoleVersionException, SignatureVerificationException { + var fetcher = fetcherSupplier.apply(mirror); // 5.3.1) record the time at start and use for expiration checks consistently throughout the // update. updateStartTime = ZonedDateTime.now(clock); @@ -98,36 +89,12 @@ public void updateRoot(Path trustedRootPath, URL mirror, TufLocalStore localStor // 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) { + var newRootMaybe = fetcher.getRootAtVersion(nextVersion); + if (newRootMaybe.isEmpty()) { // 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); - + var newRoot = newRootMaybe.get(); // 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 @@ -230,7 +197,7 @@ void verifyDelegate( 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 + // 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 @@ -283,4 +250,29 @@ public void updateTargets() { // Done!! } + + public static class Builder { + private Clock clock = Clock.systemUTC(); + private Verifiers.Supplier verifiers = Verifiers::newVerifier; + private Function fetcherSupplier = HttpMetaFetcher::newFetcher; + + public Builder setClock(Clock clock) { + this.clock = clock; + return this; + } + + public Builder setVerifiers(Verifiers.Supplier verifiers) { + this.verifiers = verifiers; + return this; + } + + public Builder setFetcherSupplier(Function fetcherSupplier) { + this.fetcherSupplier = fetcherSupplier; + return this; + } + + public TufClient build() { + return new TufClient(clock, verifiers, fetcherSupplier); + } + } } diff --git a/sigstore-java/src/test/java/dev/sigstore/tuf/TufClientTest.java b/sigstore-java/src/test/java/dev/sigstore/tuf/TufClientTest.java index 546e713b..8df0e3da 100644 --- a/sigstore-java/src/test/java/dev/sigstore/tuf/TufClientTest.java +++ b/sigstore-java/src/test/java/dev/sigstore/tuf/TufClientTest.java @@ -53,6 +53,7 @@ class TufClientTest { public static final String TEST_STATIC_UPDATE_TIME = "2022-09-09T13:37:00.00Z"; + static Server remote; static String remoteUrl; private final Path trustedRoot = TestResources.CLIENT_TRUSTED_ROOT; @@ -209,27 +210,7 @@ public void testVerifyDelegate_verificationFailed() Map publicKeys = ImmutableMap.of(PUB_KEY_1.getLeft(), PUB_KEY_1.getRight()); Role delegate = ImmutableRootRole.builder().addKeyids(PUB_KEY_1.getLeft()).threshold(1).build(); byte[] verificationMaterial = "alksdjfas".getBytes(StandardCharsets.UTF_8); - var client = - new TufClient( - publicKey -> - new Verifier() { - @Override - public PublicKey getPublicKey() { - return null; - } - - @Override - public boolean verify(byte[] artifact, byte[] signature) - throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { - return false; - } - - @Override - public boolean verifyDigest(byte[] artifactDigest, byte[] signature) - throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { - return false; - } - }); + var client = new TufClient.Builder().setVerifiers(ALWAYS_FAILS).build(); try { client.verifyDelegate(sigs, publicKeys, delegate, verificationMaterial); fail("This should have failed since the public key for PUB_KEY_1 should fail to verify."); @@ -324,33 +305,16 @@ static Key newKey(String keyContents) { @NotNull private static TufClient createTimeStaticTufClient() { - return new TufClient( - Clock.fixed(Instant.parse(TEST_STATIC_UPDATE_TIME), ZoneOffset.UTC), - Verifiers::newVerifier); + return new TufClient.Builder() + .setClock(Clock.fixed(Instant.parse(TEST_STATIC_UPDATE_TIME), ZoneOffset.UTC)) + .setVerifiers(Verifiers::newVerifier) + .setFetcherSupplier(HttpMetaFetcher::newFetcher) + .build(); } @NotNull private static TufClient createAlwaysVerifyingTufClient() { - return new TufClient( - publicKey -> - new Verifier() { - @Override - public PublicKey getPublicKey() { - return null; - } - - @Override - public boolean verify(byte[] artifact, byte[] signature) - throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { - return true; - } - - @Override - public boolean verifyDigest(byte[] artifactDigest, byte[] signature) - throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { - return true; - } - }); + return new TufClient.Builder().setVerifiers(ALWAYS_VERIFIES).build(); } private static void setupMirror(String repoName, String... files) throws IOException { @@ -383,4 +347,45 @@ void clearLocalMirror() { static void shutdownRemoteResourceServer() throws Exception { remote.stop(); } + + public static final Verifiers.Supplier ALWAYS_VERIFIES = + publicKey -> + new Verifier() { + @Override + public PublicKey getPublicKey() { + return null; + } + + @Override + public boolean verify(byte[] artifact, byte[] signature) + throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + return true; + } + + @Override + public boolean verifyDigest(byte[] artifactDigest, byte[] signature) + throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + return true; + } + }; + public static final Verifiers.Supplier ALWAYS_FAILS = + publicKey -> + new Verifier() { + @Override + public PublicKey getPublicKey() { + return null; + } + + @Override + public boolean verify(byte[] artifact, byte[] signature) + throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + return false; + } + + @Override + public boolean verifyDigest(byte[] artifactDigest, byte[] signature) + throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + return false; + } + }; } From b6ed9c571702f86fa61ba769ad147551e5603cdb Mon Sep 17 00:00:00 2001 From: Patrick Flynn Date: Mon, 19 Sep 2022 20:43:33 -0400 Subject: [PATCH 2/3] static builder factory method Signed-off-by: Patrick Flynn --- sigstore-java/src/main/java/dev/sigstore/tuf/TufClient.java | 5 ++++- .../src/test/java/dev/sigstore/tuf/TufClientTest.java | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/sigstore-java/src/main/java/dev/sigstore/tuf/TufClient.java b/sigstore-java/src/main/java/dev/sigstore/tuf/TufClient.java index c8c2a528..9c1f50ec 100644 --- a/sigstore-java/src/main/java/dev/sigstore/tuf/TufClient.java +++ b/sigstore-java/src/main/java/dev/sigstore/tuf/TufClient.java @@ -56,6 +56,7 @@ public class TufClient { private Verifiers.Supplier verifiers; private Function fetcherSupplier; + private ZonedDateTime updateStartTime; TufClient(Clock clock, Verifiers.Supplier verifiers, Function fetcherSupplier) { this.clock = clock; @@ -63,7 +64,9 @@ public class TufClient { this.fetcherSupplier = fetcherSupplier; } - private ZonedDateTime updateStartTime; + public static Builder builder() { + return new Builder(); + } // https://theupdateframework.github.io/specification/latest/#detailed-client-workflow public void updateRoot(Path trustedRootPath, URL mirror, TufLocalStore localStore) diff --git a/sigstore-java/src/test/java/dev/sigstore/tuf/TufClientTest.java b/sigstore-java/src/test/java/dev/sigstore/tuf/TufClientTest.java index 8df0e3da..f66f9d21 100644 --- a/sigstore-java/src/test/java/dev/sigstore/tuf/TufClientTest.java +++ b/sigstore-java/src/test/java/dev/sigstore/tuf/TufClientTest.java @@ -210,7 +210,7 @@ public void testVerifyDelegate_verificationFailed() Map publicKeys = ImmutableMap.of(PUB_KEY_1.getLeft(), PUB_KEY_1.getRight()); Role delegate = ImmutableRootRole.builder().addKeyids(PUB_KEY_1.getLeft()).threshold(1).build(); byte[] verificationMaterial = "alksdjfas".getBytes(StandardCharsets.UTF_8); - var client = new TufClient.Builder().setVerifiers(ALWAYS_FAILS).build(); + var client = TufClient.builder().setVerifiers(ALWAYS_FAILS).build(); try { client.verifyDelegate(sigs, publicKeys, delegate, verificationMaterial); fail("This should have failed since the public key for PUB_KEY_1 should fail to verify."); @@ -305,7 +305,7 @@ static Key newKey(String keyContents) { @NotNull private static TufClient createTimeStaticTufClient() { - return new TufClient.Builder() + return TufClient.builder() .setClock(Clock.fixed(Instant.parse(TEST_STATIC_UPDATE_TIME), ZoneOffset.UTC)) .setVerifiers(Verifiers::newVerifier) .setFetcherSupplier(HttpMetaFetcher::newFetcher) @@ -314,7 +314,7 @@ private static TufClient createTimeStaticTufClient() { @NotNull private static TufClient createAlwaysVerifyingTufClient() { - return new TufClient.Builder().setVerifiers(ALWAYS_VERIFIES).build(); + return TufClient.builder().setVerifiers(ALWAYS_VERIFIES).build(); } private static void setupMirror(String repoName, String... files) throws IOException { From b2844882f63094ddf406ccd3afb7401fc8c8b8fe Mon Sep 17 00:00:00 2001 From: Patrick Flynn Date: Mon, 19 Sep 2022 22:02:38 -0400 Subject: [PATCH 3/3] Change TufClient API to make update configuration instance state rather than as arguments to the update method Signed-off-by: Patrick Flynn --- .../dev/sigstore/tuf/HttpMetaFetcher.java | 5 ++ .../java/dev/sigstore/tuf/MetaFetcher.java | 6 +++ .../main/java/dev/sigstore/tuf/TufClient.java | 47 +++++++++++++------ .../java/dev/sigstore/tuf/TufClientTest.java | 34 ++++++-------- 4 files changed, 59 insertions(+), 33 deletions(-) diff --git a/sigstore-java/src/main/java/dev/sigstore/tuf/HttpMetaFetcher.java b/sigstore-java/src/main/java/dev/sigstore/tuf/HttpMetaFetcher.java index 083a5404..d82f9ca2 100644 --- a/sigstore-java/src/main/java/dev/sigstore/tuf/HttpMetaFetcher.java +++ b/sigstore-java/src/main/java/dev/sigstore/tuf/HttpMetaFetcher.java @@ -40,6 +40,11 @@ public static HttpMetaFetcher newFetcher(URL mirror) { return new HttpMetaFetcher(mirror); } + @Override + public String getSource() { + return mirror.toString(); + } + @Override public Optional getRootAtVersion(int version) throws IOException, MetaFileExceedsMaxException { diff --git a/sigstore-java/src/main/java/dev/sigstore/tuf/MetaFetcher.java b/sigstore-java/src/main/java/dev/sigstore/tuf/MetaFetcher.java index 3379ccfd..8f1b3cee 100644 --- a/sigstore-java/src/main/java/dev/sigstore/tuf/MetaFetcher.java +++ b/sigstore-java/src/main/java/dev/sigstore/tuf/MetaFetcher.java @@ -22,6 +22,12 @@ /** Retrieves TUF metadata. */ public interface MetaFetcher { + /** + * Describes the source of the metadata being fetched from. e.g "http://mirror.bla/mirror", + * "mock", "c:/tmp". + */ + String getSource(); + /** * Fetch the {@link Root} at the specified {@code version}. * diff --git a/sigstore-java/src/main/java/dev/sigstore/tuf/TufClient.java b/sigstore-java/src/main/java/dev/sigstore/tuf/TufClient.java index 9c1f50ec..c204347c 100644 --- a/sigstore-java/src/main/java/dev/sigstore/tuf/TufClient.java +++ b/sigstore-java/src/main/java/dev/sigstore/tuf/TufClient.java @@ -22,7 +22,6 @@ import dev.sigstore.encryption.signers.Verifiers; import dev.sigstore.tuf.model.*; import java.io.IOException; -import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.security.InvalidKeyException; @@ -35,7 +34,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.function.Function; import org.bouncycastle.util.encoders.Hex; /** @@ -54,14 +52,23 @@ public class TufClient { private Clock clock; private Verifiers.Supplier verifiers; - - private Function fetcherSupplier; + private MetaFetcher fetcher; private ZonedDateTime updateStartTime; - - TufClient(Clock clock, Verifiers.Supplier verifiers, Function fetcherSupplier) { + private Path trustedRootPath; + private TufLocalStore localStore; + + TufClient( + Clock clock, + Verifiers.Supplier verifiers, + MetaFetcher fetcher, + Path trustedRootPath, + TufLocalStore localStore) { this.clock = clock; this.verifiers = verifiers; - this.fetcherSupplier = fetcherSupplier; + this.fetcher = fetcher; + this.trustedRootPath = trustedRootPath; + this.localStore = localStore; + this.fetcher = fetcher; } public static Builder builder() { @@ -69,11 +76,10 @@ public static Builder builder() { } // https://theupdateframework.github.io/specification/latest/#detailed-client-workflow - public void updateRoot(Path trustedRootPath, URL mirror, TufLocalStore localStore) + public void updateRoot() throws IOException, RootExpiredException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, MetaFileExceedsMaxException, RoleVersionException, SignatureVerificationException { - var fetcher = fetcherSupplier.apply(mirror); // 5.3.1) record the time at start and use for expiration checks consistently throughout the // update. updateStartTime = ZonedDateTime.now(clock); @@ -133,7 +139,7 @@ public void updateRoot(Path trustedRootPath, URL mirror, TufLocalStore localStor // otherwise throw error. ZonedDateTime expires = trustedRoot.getSignedMeta().getExpiresAsDate(); if (expires.isBefore(updateStartTime)) { - throw new RootExpiredException(mirror.toString(), updateStartTime, expires); + throw new RootExpiredException(fetcher.getSource(), updateStartTime, expires); } // 5.3.11) If the timestamp and / or snapshot keys have been rotated, then delete the trusted // timestamp and snapshot metadata files. @@ -257,7 +263,10 @@ public void updateTargets() { public static class Builder { private Clock clock = Clock.systemUTC(); private Verifiers.Supplier verifiers = Verifiers::newVerifier; - private Function fetcherSupplier = HttpMetaFetcher::newFetcher; + + private MetaFetcher fetcher; + private Path trustedRootPath; + private TufLocalStore localStore; public Builder setClock(Clock clock) { this.clock = clock; @@ -269,13 +278,23 @@ public Builder setVerifiers(Verifiers.Supplier verifiers) { return this; } - public Builder setFetcherSupplier(Function fetcherSupplier) { - this.fetcherSupplier = fetcherSupplier; + public Builder setLocalStore(TufLocalStore store) { + this.localStore = store; + return this; + } + + public Builder setTrustedRootPath(Path trustedRootPath) { + this.trustedRootPath = trustedRootPath; + return this; + } + + public Builder setFetcher(MetaFetcher fetcher) { + this.fetcher = fetcher; return this; } public TufClient build() { - return new TufClient(clock, verifiers, fetcherSupplier); + return new TufClient(clock, verifiers, fetcher, trustedRootPath, localStore); } } } diff --git a/sigstore-java/src/test/java/dev/sigstore/tuf/TufClientTest.java b/sigstore-java/src/test/java/dev/sigstore/tuf/TufClientTest.java index f66f9d21..5267ec84 100644 --- a/sigstore-java/src/test/java/dev/sigstore/tuf/TufClientTest.java +++ b/sigstore-java/src/test/java/dev/sigstore/tuf/TufClientTest.java @@ -56,7 +56,7 @@ class TufClientTest { static Server remote; static String remoteUrl; - private final Path trustedRoot = TestResources.CLIENT_TRUSTED_ROOT; + private static final Path trustedRoot = TestResources.CLIENT_TRUSTED_ROOT; @TempDir Path localStore; @TempDir static Path localMirror; Path tufTestData = Paths.get("src/test/resources/dev/sigstore/tuf/"); @@ -81,10 +81,8 @@ static void startRemoteResourceServer() throws Exception { 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 = createTimeStaticTufClient(); - Path trustedRoot = tufTestData.resolve("trusted-root.json"); - client.updateRoot( - trustedRoot, new URL(remoteUrl), FileSystemTufStore.newFileSystemStore(localStore)); + var client = createTimeStaticTufClient(localStore); + client.updateRoot(); assertStoreContains("root.json"); Root oldRoot = TestResources.loadRoot(trustedRoot); Root newRoot = TestResources.loadRoot(localStore.resolve("root.json")); @@ -96,10 +94,9 @@ public void testRootUpdate_fromProdData() public void testRootUpdate_notEnoughSignatures() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { setupMirror("remote-repo-unsigned", "2.root.json"); - var client = createTimeStaticTufClient(); + var client = createTimeStaticTufClient(localStore); try { - client.updateRoot( - trustedRoot, new URL(remoteUrl), FileSystemTufStore.newFileSystemStore(localStore)); + client.updateRoot(); fail(); } catch (SignatureVerificationException e) { assertEquals(3, e.getRequiredSignatures()); @@ -111,10 +108,9 @@ public void testRootUpdate_notEnoughSignatures() public void testRootUpdate_expiredRoot() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { setupMirror("remote-repo-expired", "2.root.json"); - var client = createTimeStaticTufClient(); + var client = createTimeStaticTufClient(localStore); try { - client.updateRoot( - trustedRoot, new URL(remoteUrl), FileSystemTufStore.newFileSystemStore(localStore)); + client.updateRoot(); fail(); } catch (RootExpiredException e) { assertEquals(ZonedDateTime.parse(TEST_STATIC_UPDATE_TIME), e.getUpdateTime()); @@ -128,10 +124,9 @@ public void testRootUpdate_expiredRoot() public void testRootUpdate_inconsistentVersion() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { setupMirror("remote-repo-inconsistent-version", "2.root.json"); - var client = createTimeStaticTufClient(); + var client = createTimeStaticTufClient(localStore); try { - client.updateRoot( - trustedRoot, new URL(remoteUrl), FileSystemTufStore.newFileSystemStore(localStore)); + client.updateRoot(); fail(); } catch (RoleVersionException e) { assertEquals(2, e.getExpectedVersion()); @@ -143,10 +138,9 @@ public void testRootUpdate_inconsistentVersion() public void testRootUpdate_metaFileTooBig() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { setupMirror("remote-repo-meta-file-too-big", "2.root.json"); - var client = createTimeStaticTufClient(); + var client = createTimeStaticTufClient(localStore); try { - client.updateRoot( - trustedRoot, new URL(remoteUrl), FileSystemTufStore.newFileSystemStore(localStore)); + client.updateRoot(); fail(); } catch (MetaFileExceedsMaxException e) { } @@ -304,11 +298,13 @@ static Key newKey(String keyContents) { } @NotNull - private static TufClient createTimeStaticTufClient() { + private static TufClient createTimeStaticTufClient(Path localStore) throws IOException { return TufClient.builder() .setClock(Clock.fixed(Instant.parse(TEST_STATIC_UPDATE_TIME), ZoneOffset.UTC)) .setVerifiers(Verifiers::newVerifier) - .setFetcherSupplier(HttpMetaFetcher::newFetcher) + .setFetcher(HttpMetaFetcher.newFetcher(new URL(remoteUrl))) + .setTrustedRootPath(trustedRoot) + .setLocalStore(FileSystemTufStore.newFileSystemStore(localStore)) .build(); }