Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Separate meta fetching from target fetching #827

Merged
merged 1 commit into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ResourceFetcher?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should it be?


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 @@ -75,7 +75,7 @@ public Builder usePublicGoodInstance() {
}
try {
tufMirror(
new URL("https://tuf-repo-cdn.sigstore.dev"),
new URL("https://tuf-repo-cdn.sigstore.dev/"),
RootProvider.fromResource(PUBLIC_GOOD_ROOT_RESOURCE));
} catch (MalformedURLException e) {
throw new AssertionError(e);
Expand Down Expand Up @@ -126,11 +126,18 @@ public SigstoreTufClient build() throws IOException {
if (!Files.isDirectory(tufCacheLocation)) {
Files.createDirectories(tufCacheLocation);
}
var normalizedRemoteMirror =
remoteMirror.toString().endsWith("/")
? remoteMirror
: new URL(remoteMirror.toExternalForm() + "/");
var targetsLocation = new URL(normalizedRemoteMirror.toExternalForm() + "targets");
var tufUpdater =
Updater.builder()
.setTrustedRootPath(trustedRoot)
.setLocalStore(FileSystemTufStore.newFileSystemStore(tufCacheLocation))
.setFetcher(HttpMetaFetcher.newFetcher(remoteMirror))
.setMetaFetcher(
MetaFetcher.newFetcher(HttpFetcher.newFetcher(normalizedRemoteMirror)))
.setTargetFetcher(HttpFetcher.newFetcher(targetsLocation))
.build();
return new SigstoreTufClient(tufUpdater, cacheValidity);
}
Expand Down
Loading
Loading