Skip to content

Commit

Permalink
Separate meta fetching from target fetching
Browse files Browse the repository at this point in the history
Signed-off-by: Appu Goundan <[email protected]>
  • Loading branch information
loosebazooka committed Oct 10, 2024
1 parent 8628fc0 commit 726cf1a
Show file tree
Hide file tree
Showing 8 changed files with 119 additions and 122 deletions.
26 changes: 26 additions & 0 deletions sigstore-java/src/main/java/dev/sigstore/tuf/Fetcher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* 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 java.io.IOException;

public interface Fetcher {

String getSource();

byte[] fetchResource(String filename, int maxLength)
throws IOException, FileExceedsMaxLengthException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public static MutableTufStore newFileSystemStore(Path repoBaseDir) throws IOExce
return newFileSystemStore(repoBaseDir, defaultTargetsCache);
}

static MutableTufStore newFileSystemStore(Path repoBaseDir, Path targetsCache) {
public static MutableTufStore newFileSystemStore(Path repoBaseDir, Path targetsCache) {
if (!Files.isDirectory(repoBaseDir)) {
throw new IllegalArgumentException(repoBaseDir + " must be a file system directory.");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2022 The Sigstore Authors.
* 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.
Expand All @@ -15,85 +15,35 @@
*/
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 com.google.common.base.Preconditions;
import dev.sigstore.http.HttpClients;
import dev.sigstore.http.ImmutableHttpParams;
import dev.sigstore.tuf.model.Root;
import dev.sigstore.tuf.model.SignedTufMeta;
import dev.sigstore.tuf.model.TufMeta;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
import java.util.Optional;
import javax.annotation.Nullable;

public class HttpMetaFetcher implements MetaFetcher {
public class HttpFetcher implements Fetcher {

private static final int MAX_META_BYTES = 99 * 1024; // 99 KB
private final URL mirror;

HttpMetaFetcher(URL mirror) {
private HttpFetcher(URL mirror) {
this.mirror = mirror;
}

public static HttpMetaFetcher newFetcher(URL mirror) throws MalformedURLException {
public static HttpFetcher newFetcher(URL mirror) throws MalformedURLException {
if (mirror.toString().endsWith("/")) {
return new HttpMetaFetcher(mirror);
return new HttpFetcher(mirror);
}
return new HttpMetaFetcher(new URL(mirror.toExternalForm() + "/"));
return new HttpFetcher(new URL(mirror.toExternalForm() + "/"));
}

@Override
public String getSource() {
return mirror.toString();
}

@Override
public Optional<MetaFetchResult<Root>> getRootAtVersion(int version)
throws IOException, FileExceedsMaxLengthException {
String versionFileName = version + ".root.json";
return getMeta(versionFileName, Root.class, null);
}

@Override
public <T extends SignedTufMeta<? extends TufMeta>> Optional<MetaFetchResult<T>> getMeta(
String role, Class<T> t) throws IOException, FileExceedsMaxLengthException {
return getMeta(getFileName(role, null), t, null);
}

@Override
public <T extends SignedTufMeta<? extends TufMeta>> Optional<MetaFetchResult<T>> getMeta(
String role, int version, Class<T> t, Integer maxSize)
throws IOException, FileExceedsMaxLengthException {
Preconditions.checkArgument(version > 0, "version should be positive, got: %s", version);
return getMeta(getFileName(role, version), t, maxSize);
}

private static String getFileName(String role, @Nullable Integer version) {
return version == null
? role + ".json"
: String.format(Locale.ROOT, "%d.%s.json", version, role);
}

<T extends SignedTufMeta> Optional<MetaFetchResult<T>> getMeta(
String filename, Class<T> t, Integer maxSize)
throws IOException, FileExceedsMaxLengthException {
byte[] roleBytes = fetchResource(filename, maxSize == null ? MAX_META_BYTES : maxSize);
if (roleBytes == null) {
return Optional.empty();
}
var result =
new MetaFetchResult<T>(
roleBytes, GSON.get().fromJson(new String(roleBytes, StandardCharsets.UTF_8), t));
return Optional.of(result);
}

@Override
public byte[] fetchResource(String filename, int maxLength)
throws IOException, FileExceedsMaxLengthException {
Expand Down
98 changes: 54 additions & 44 deletions sigstore-java/src/main/java/dev/sigstore/tuf/MetaFetcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,59 +15,69 @@
*/
package dev.sigstore.tuf;

import static dev.sigstore.json.GsonSupplier.GSON;

import com.google.common.base.Preconditions;
import dev.sigstore.tuf.model.Root;
import dev.sigstore.tuf.model.SignedTufMeta;
import dev.sigstore.tuf.model.TufMeta;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
import java.util.Optional;
import javax.annotation.Nullable;

/** Retrieves TUF metadata. */
public interface MetaFetcher {
public class MetaFetcher {

/**
* Describes the source of the metadata being fetched from. e.g "http://mirror.bla/mirror",
* "mock", "c:/tmp".
*/
String getSource();
private static final int MAX_META_BYTES = 99 * 1024; // 99 KB
private final Fetcher fetcher;

/**
* Fetch the {@link Root} at the specified {@code version}.
*
* @throws FileExceedsMaxLengthException when the retrieved file is larger than the maximum
* allowed by the client
*/
Optional<MetaFetchResult<Root>> getRootAtVersion(int version)
throws IOException, FileExceedsMaxLengthException;
private MetaFetcher(Fetcher fetcher) {
this.fetcher = fetcher;
}

/**
* Fetches the unversioned specified role meta from the source
*
* @param name TUF role name
* @param roleType this should be the type you expect in return
* @return the latest fully de-serialized role if it was present at the source
* @throws IOException in case of IO errors
* @throws FileExceedsMaxLengthException if the role meta at source exceeds client specified max
* size
*/
<T extends SignedTufMeta<? extends TufMeta>> Optional<MetaFetchResult<T>> getMeta(
String name, Class<T> roleType) throws IOException, FileExceedsMaxLengthException;
public static MetaFetcher newFetcher(Fetcher fetcher) {
return new MetaFetcher(fetcher);
}

/**
* Fetches the specified role meta from the source
*
* @param name TUF role name
* @param version the version of the file to download
* @param roleType this should be the type you expect in return
* @param maxSize max file size in bytes
* @return the fully de-serialized role if it was present at the source
* @throws IOException in case of IO errors
* @throws FileExceedsMaxLengthException if the role meta at source exceeds client specified max
* size
*/
<T extends SignedTufMeta<? extends TufMeta>> Optional<MetaFetchResult<T>> getMeta(
String name, int version, Class<T> roleType, Integer maxSize)
throws IOException, FileExceedsMaxLengthException;
public String getSource() {
return fetcher.getSource();
}

public Optional<MetaFetchResult<Root>> getRootAtVersion(int version)
throws IOException, FileExceedsMaxLengthException {
String versionFileName = version + ".root.json";
return getMeta(versionFileName, Root.class, null);
}

byte[] fetchResource(String filename, int maxLength)
throws IOException, FileExceedsMaxLengthException;
public <T extends SignedTufMeta<? extends TufMeta>> Optional<MetaFetchResult<T>> getMeta(
String role, Class<T> t) throws IOException, FileExceedsMaxLengthException {
return getMeta(getFileName(role, null), t, null);
}

public <T extends SignedTufMeta<? extends TufMeta>> Optional<MetaFetchResult<T>> getMeta(
String role, int version, Class<T> t, Integer maxSize)
throws IOException, FileExceedsMaxLengthException {
Preconditions.checkArgument(version > 0, "version should be positive, got: %s", version);
return getMeta(getFileName(role, version), t, maxSize);
}

private static String getFileName(String role, @Nullable Integer version) {
return version == null
? role + ".json"
: String.format(Locale.ROOT, "%d.%s.json", version, role);
}

<T extends SignedTufMeta<? extends TufMeta>> Optional<MetaFetchResult<T>> getMeta(
String filename, Class<T> t, Integer maxSize)
throws IOException, FileExceedsMaxLengthException {
byte[] roleBytes = fetcher.fetchResource(filename, maxSize == null ? MAX_META_BYTES : maxSize);
if (roleBytes == null) {
return Optional.empty();
}
var result =
new MetaFetchResult<T>(
roleBytes, GSON.get().fromJson(new String(roleBytes, StandardCharsets.UTF_8), t));
return Optional.of(result);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ public SigstoreTufClient build() throws IOException {
Updater.builder()
.setTrustedRootPath(trustedRoot)
.setLocalStore(FileSystemTufStore.newFileSystemStore(tufCacheLocation))
.setFetcher(HttpMetaFetcher.newFetcher(remoteMirror))
.setMetaFetcher(MetaFetcher.newFetcher(HttpFetcher.newFetcher(remoteMirror)))
.setTargetFetcher(HttpFetcher.newFetcher(remoteMirror))
.build();
return new SigstoreTufClient(tufUpdater, cacheValidity);
}
Expand Down
43 changes: 26 additions & 17 deletions sigstore-java/src/main/java/dev/sigstore/tuf/Updater.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,22 +59,25 @@ public class Updater {

private Clock clock;
private Verifiers.Supplier verifiers;
private MetaFetcher fetcher;
private MetaFetcher metaFetcher;
private Fetcher targetFetcher;
private ZonedDateTime updateStartTime;
private RootProvider trustedRootPath;
private MutableTufStore localStore;

Updater(
Clock clock,
Verifiers.Supplier verifiers,
MetaFetcher fetcher,
MetaFetcher metaFetcher,
Fetcher targetFetcher,
RootProvider trustedRootPath,
MutableTufStore localStore) {
this.clock = clock;
this.verifiers = verifiers;
this.trustedRootPath = trustedRootPath;
this.localStore = localStore;
this.fetcher = fetcher;
this.metaFetcher = metaFetcher;
this.targetFetcher = targetFetcher;
}

public static Builder builder() {
Expand Down Expand Up @@ -121,7 +124,7 @@ Root updateRoot()
// 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
var newRootMaybe = fetcher.getRootAtVersion(nextVersion);
var newRootMaybe = metaFetcher.getRootAtVersion(nextVersion);
if (newRootMaybe.isEmpty()) {
// No newer versions, go to 5.3.10.
break;
Expand Down Expand Up @@ -168,7 +171,7 @@ Root updateRoot()

private void throwIfExpired(ZonedDateTime expires) {
if (expires.isBefore(updateStartTime)) {
throw new RoleExpiredException(fetcher.getSource(), updateStartTime, expires);
throw new RoleExpiredException(metaFetcher.getSource(), updateStartTime, expires);
}
}

Expand Down Expand Up @@ -267,9 +270,9 @@ Optional<Timestamp> updateTimestamp(Root root)
FileNotFoundException, SignatureVerificationException {
// 1) download the timestamp.json bytes.
var timestamp =
fetcher
metaFetcher
.getMeta(RootRole.TIMESTAMP, Timestamp.class)
.orElseThrow(() -> new FileNotFoundException("timestamp.json", fetcher.getSource()))
.orElseThrow(() -> new FileNotFoundException("timestamp.json", metaFetcher.getSource()))
.getMetaResource();

// 2) verify against threshold of keys as specified in trusted root.json
Expand Down Expand Up @@ -303,14 +306,14 @@ Snapshot updateSnapshot(Root root, Timestamp timestamp)
// 1) download the snapshot.json bytes up to timestamp's snapshot length.
int timestampSnapshotVersion = timestamp.getSignedMeta().getSnapshotMeta().getVersion();
var snapshotResult =
fetcher.getMeta(
metaFetcher.getMeta(
RootRole.SNAPSHOT,
timestampSnapshotVersion,
Snapshot.class,
timestamp.getSignedMeta().getSnapshotMeta().getLengthOrDefault());
if (snapshotResult.isEmpty()) {
throw new FileNotFoundException(
timestampSnapshotVersion + ".snapshot.json", fetcher.getSource());
timestampSnapshotVersion + ".snapshot.json", metaFetcher.getSource());
}
// 2) check against timestamp.snapshot.hash, this is optional, the fallback is
// that the version must match, which is handled in (4).
Expand Down Expand Up @@ -393,14 +396,14 @@ Targets updateTargets(Root root, Snapshot snapshot)
// 1) download the targets.json up to targets.json length in bytes.
SnapshotMeta.SnapshotTarget targetMeta = snapshot.getSignedMeta().getTargetMeta("targets.json");
var targetsResultMaybe =
fetcher.getMeta(
metaFetcher.getMeta(
RootRole.TARGETS,
targetMeta.getVersion(),
Targets.class,
targetMeta.getLengthOrDefault());
if (targetsResultMaybe.isEmpty()) {
throw new FileNotFoundException(
targetMeta.getVersion() + ".targets.json", fetcher.getSource());
targetMeta.getVersion() + ".targets.json", metaFetcher.getSource());
}
var targetsResult = targetsResultMaybe.get();
// 2) check hash against snapshot.targets.hash, else just make sure versions match, handled
Expand Down Expand Up @@ -451,9 +454,9 @@ void downloadTargets(Targets targets)
}

var targetBytes =
fetcher.fetchResource("targets/" + versionedTargetName, targetData.getLength());
targetFetcher.fetchResource("targets/" + versionedTargetName, targetData.getLength());
if (targetBytes == null) {
throw new FileNotFoundException(targetName, fetcher.getSource());
throw new FileNotFoundException(targetName, targetFetcher.getSource());
}
verifyHashes(entry.getKey(), targetBytes, targetData.getHashes());

Expand All @@ -472,7 +475,8 @@ public static class Builder {
private Clock clock = Clock.systemUTC();
private Verifiers.Supplier verifiers = Verifiers::newVerifier;

private MetaFetcher fetcher;
private MetaFetcher metaFetcher;
private Fetcher targetFetcher;
private RootProvider trustedRootPath;
private MutableTufStore localStore;

Expand All @@ -496,13 +500,18 @@ public Builder setTrustedRootPath(RootProvider trustedRootPath) {
return this;
}

public Builder setFetcher(MetaFetcher fetcher) {
this.fetcher = fetcher;
public Builder setMetaFetcher(MetaFetcher metaFetcher) {
this.metaFetcher = metaFetcher;
return this;
}

public Builder setTargetFetcher(Fetcher fetcher) {
this.targetFetcher = fetcher;
return this;
}

public Updater build() {
return new Updater(clock, verifiers, fetcher, trustedRootPath, localStore);
return new Updater(clock, verifiers, metaFetcher, targetFetcher, trustedRootPath, localStore);
}
}
}
Loading

0 comments on commit 726cf1a

Please sign in to comment.