Skip to content

Commit 726cf1a

Browse files
committed
Separate meta fetching from target fetching
Signed-off-by: Appu Goundan <[email protected]>
1 parent 8628fc0 commit 726cf1a

File tree

8 files changed

+119
-122
lines changed

8 files changed

+119
-122
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2024 The Sigstore Authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package dev.sigstore.tuf;
17+
18+
import java.io.IOException;
19+
20+
public interface Fetcher {
21+
22+
String getSource();
23+
24+
byte[] fetchResource(String filename, int maxLength)
25+
throws IOException, FileExceedsMaxLengthException;
26+
}

sigstore-java/src/main/java/dev/sigstore/tuf/FileSystemTufStore.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public static MutableTufStore newFileSystemStore(Path repoBaseDir) throws IOExce
5353
return newFileSystemStore(repoBaseDir, defaultTargetsCache);
5454
}
5555

56-
static MutableTufStore newFileSystemStore(Path repoBaseDir, Path targetsCache) {
56+
public static MutableTufStore newFileSystemStore(Path repoBaseDir, Path targetsCache) {
5757
if (!Files.isDirectory(repoBaseDir)) {
5858
throw new IllegalArgumentException(repoBaseDir + " must be a file system directory.");
5959
}

sigstore-java/src/main/java/dev/sigstore/tuf/HttpMetaFetcher.java renamed to sigstore-java/src/main/java/dev/sigstore/tuf/HttpFetcher.java

Lines changed: 6 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2022 The Sigstore Authors.
2+
* Copyright 2024 The Sigstore Authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,85 +15,35 @@
1515
*/
1616
package dev.sigstore.tuf;
1717

18-
import static dev.sigstore.json.GsonSupplier.GSON;
19-
2018
import com.google.api.client.http.GenericUrl;
2119
import com.google.api.client.json.gson.GsonFactory;
22-
import com.google.common.base.Preconditions;
2320
import dev.sigstore.http.HttpClients;
2421
import dev.sigstore.http.ImmutableHttpParams;
25-
import dev.sigstore.tuf.model.Root;
26-
import dev.sigstore.tuf.model.SignedTufMeta;
27-
import dev.sigstore.tuf.model.TufMeta;
2822
import java.io.IOException;
2923
import java.net.MalformedURLException;
3024
import java.net.URL;
31-
import java.nio.charset.StandardCharsets;
3225
import java.util.Locale;
33-
import java.util.Optional;
34-
import javax.annotation.Nullable;
3526

36-
public class HttpMetaFetcher implements MetaFetcher {
27+
public class HttpFetcher implements Fetcher {
3728

38-
private static final int MAX_META_BYTES = 99 * 1024; // 99 KB
3929
private final URL mirror;
4030

41-
HttpMetaFetcher(URL mirror) {
31+
private HttpFetcher(URL mirror) {
4232
this.mirror = mirror;
4333
}
4434

45-
public static HttpMetaFetcher newFetcher(URL mirror) throws MalformedURLException {
35+
public static HttpFetcher newFetcher(URL mirror) throws MalformedURLException {
4636
if (mirror.toString().endsWith("/")) {
47-
return new HttpMetaFetcher(mirror);
37+
return new HttpFetcher(mirror);
4838
}
49-
return new HttpMetaFetcher(new URL(mirror.toExternalForm() + "/"));
39+
return new HttpFetcher(new URL(mirror.toExternalForm() + "/"));
5040
}
5141

5242
@Override
5343
public String getSource() {
5444
return mirror.toString();
5545
}
5646

57-
@Override
58-
public Optional<MetaFetchResult<Root>> getRootAtVersion(int version)
59-
throws IOException, FileExceedsMaxLengthException {
60-
String versionFileName = version + ".root.json";
61-
return getMeta(versionFileName, Root.class, null);
62-
}
63-
64-
@Override
65-
public <T extends SignedTufMeta<? extends TufMeta>> Optional<MetaFetchResult<T>> getMeta(
66-
String role, Class<T> t) throws IOException, FileExceedsMaxLengthException {
67-
return getMeta(getFileName(role, null), t, null);
68-
}
69-
70-
@Override
71-
public <T extends SignedTufMeta<? extends TufMeta>> Optional<MetaFetchResult<T>> getMeta(
72-
String role, int version, Class<T> t, Integer maxSize)
73-
throws IOException, FileExceedsMaxLengthException {
74-
Preconditions.checkArgument(version > 0, "version should be positive, got: %s", version);
75-
return getMeta(getFileName(role, version), t, maxSize);
76-
}
77-
78-
private static String getFileName(String role, @Nullable Integer version) {
79-
return version == null
80-
? role + ".json"
81-
: String.format(Locale.ROOT, "%d.%s.json", version, role);
82-
}
83-
84-
<T extends SignedTufMeta> Optional<MetaFetchResult<T>> getMeta(
85-
String filename, Class<T> t, Integer maxSize)
86-
throws IOException, FileExceedsMaxLengthException {
87-
byte[] roleBytes = fetchResource(filename, maxSize == null ? MAX_META_BYTES : maxSize);
88-
if (roleBytes == null) {
89-
return Optional.empty();
90-
}
91-
var result =
92-
new MetaFetchResult<T>(
93-
roleBytes, GSON.get().fromJson(new String(roleBytes, StandardCharsets.UTF_8), t));
94-
return Optional.of(result);
95-
}
96-
9747
@Override
9848
public byte[] fetchResource(String filename, int maxLength)
9949
throws IOException, FileExceedsMaxLengthException {

sigstore-java/src/main/java/dev/sigstore/tuf/MetaFetcher.java

Lines changed: 54 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -15,59 +15,69 @@
1515
*/
1616
package dev.sigstore.tuf;
1717

18+
import static dev.sigstore.json.GsonSupplier.GSON;
19+
20+
import com.google.common.base.Preconditions;
1821
import dev.sigstore.tuf.model.Root;
1922
import dev.sigstore.tuf.model.SignedTufMeta;
2023
import dev.sigstore.tuf.model.TufMeta;
2124
import java.io.IOException;
25+
import java.nio.charset.StandardCharsets;
26+
import java.util.Locale;
2227
import java.util.Optional;
28+
import javax.annotation.Nullable;
2329

24-
/** Retrieves TUF metadata. */
25-
public interface MetaFetcher {
30+
public class MetaFetcher {
2631

27-
/**
28-
* Describes the source of the metadata being fetched from. e.g "http://mirror.bla/mirror",
29-
* "mock", "c:/tmp".
30-
*/
31-
String getSource();
32+
private static final int MAX_META_BYTES = 99 * 1024; // 99 KB
33+
private final Fetcher fetcher;
3234

33-
/**
34-
* Fetch the {@link Root} at the specified {@code version}.
35-
*
36-
* @throws FileExceedsMaxLengthException when the retrieved file is larger than the maximum
37-
* allowed by the client
38-
*/
39-
Optional<MetaFetchResult<Root>> getRootAtVersion(int version)
40-
throws IOException, FileExceedsMaxLengthException;
35+
private MetaFetcher(Fetcher fetcher) {
36+
this.fetcher = fetcher;
37+
}
4138

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

55-
/**
56-
* Fetches the specified role meta from the source
57-
*
58-
* @param name TUF role name
59-
* @param version the version of the file to download
60-
* @param roleType this should be the type you expect in return
61-
* @param maxSize max file size in bytes
62-
* @return the fully de-serialized role if it was present at the source
63-
* @throws IOException in case of IO errors
64-
* @throws FileExceedsMaxLengthException if the role meta at source exceeds client specified max
65-
* size
66-
*/
67-
<T extends SignedTufMeta<? extends TufMeta>> Optional<MetaFetchResult<T>> getMeta(
68-
String name, int version, Class<T> roleType, Integer maxSize)
69-
throws IOException, FileExceedsMaxLengthException;
43+
public String getSource() {
44+
return fetcher.getSource();
45+
}
46+
47+
public Optional<MetaFetchResult<Root>> getRootAtVersion(int version)
48+
throws IOException, FileExceedsMaxLengthException {
49+
String versionFileName = version + ".root.json";
50+
return getMeta(versionFileName, Root.class, null);
51+
}
7052

71-
byte[] fetchResource(String filename, int maxLength)
72-
throws IOException, FileExceedsMaxLengthException;
53+
public <T extends SignedTufMeta<? extends TufMeta>> Optional<MetaFetchResult<T>> getMeta(
54+
String role, Class<T> t) throws IOException, FileExceedsMaxLengthException {
55+
return getMeta(getFileName(role, null), t, null);
56+
}
57+
58+
public <T extends SignedTufMeta<? extends TufMeta>> Optional<MetaFetchResult<T>> getMeta(
59+
String role, int version, Class<T> t, Integer maxSize)
60+
throws IOException, FileExceedsMaxLengthException {
61+
Preconditions.checkArgument(version > 0, "version should be positive, got: %s", version);
62+
return getMeta(getFileName(role, version), t, maxSize);
63+
}
64+
65+
private static String getFileName(String role, @Nullable Integer version) {
66+
return version == null
67+
? role + ".json"
68+
: String.format(Locale.ROOT, "%d.%s.json", version, role);
69+
}
70+
71+
<T extends SignedTufMeta<? extends TufMeta>> Optional<MetaFetchResult<T>> getMeta(
72+
String filename, Class<T> t, Integer maxSize)
73+
throws IOException, FileExceedsMaxLengthException {
74+
byte[] roleBytes = fetcher.fetchResource(filename, maxSize == null ? MAX_META_BYTES : maxSize);
75+
if (roleBytes == null) {
76+
return Optional.empty();
77+
}
78+
var result =
79+
new MetaFetchResult<T>(
80+
roleBytes, GSON.get().fromJson(new String(roleBytes, StandardCharsets.UTF_8), t));
81+
return Optional.of(result);
82+
}
7383
}

sigstore-java/src/main/java/dev/sigstore/tuf/SigstoreTufClient.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,8 @@ public SigstoreTufClient build() throws IOException {
130130
Updater.builder()
131131
.setTrustedRootPath(trustedRoot)
132132
.setLocalStore(FileSystemTufStore.newFileSystemStore(tufCacheLocation))
133-
.setFetcher(HttpMetaFetcher.newFetcher(remoteMirror))
133+
.setMetaFetcher(MetaFetcher.newFetcher(HttpFetcher.newFetcher(remoteMirror)))
134+
.setTargetFetcher(HttpFetcher.newFetcher(remoteMirror))
134135
.build();
135136
return new SigstoreTufClient(tufUpdater, cacheValidity);
136137
}

sigstore-java/src/main/java/dev/sigstore/tuf/Updater.java

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -59,22 +59,25 @@ public class Updater {
5959

6060
private Clock clock;
6161
private Verifiers.Supplier verifiers;
62-
private MetaFetcher fetcher;
62+
private MetaFetcher metaFetcher;
63+
private Fetcher targetFetcher;
6364
private ZonedDateTime updateStartTime;
6465
private RootProvider trustedRootPath;
6566
private MutableTufStore localStore;
6667

6768
Updater(
6869
Clock clock,
6970
Verifiers.Supplier verifiers,
70-
MetaFetcher fetcher,
71+
MetaFetcher metaFetcher,
72+
Fetcher targetFetcher,
7173
RootProvider trustedRootPath,
7274
MutableTufStore localStore) {
7375
this.clock = clock;
7476
this.verifiers = verifiers;
7577
this.trustedRootPath = trustedRootPath;
7678
this.localStore = localStore;
77-
this.fetcher = fetcher;
79+
this.metaFetcher = metaFetcher;
80+
this.targetFetcher = targetFetcher;
7881
}
7982

8083
public static Builder builder() {
@@ -121,7 +124,7 @@ Root updateRoot()
121124
// 5.3.3) download $version+1.root.json from mirror url (eventually obtained from remote.json
122125
// or map.json) up MAX_META_BYTES. If the file is not available, or we have reached
123126
// MAX_UPDATES number of root metadata files go to step 5.3.10
124-
var newRootMaybe = fetcher.getRootAtVersion(nextVersion);
127+
var newRootMaybe = metaFetcher.getRootAtVersion(nextVersion);
125128
if (newRootMaybe.isEmpty()) {
126129
// No newer versions, go to 5.3.10.
127130
break;
@@ -168,7 +171,7 @@ Root updateRoot()
168171

169172
private void throwIfExpired(ZonedDateTime expires) {
170173
if (expires.isBefore(updateStartTime)) {
171-
throw new RoleExpiredException(fetcher.getSource(), updateStartTime, expires);
174+
throw new RoleExpiredException(metaFetcher.getSource(), updateStartTime, expires);
172175
}
173176
}
174177

@@ -267,9 +270,9 @@ Optional<Timestamp> updateTimestamp(Root root)
267270
FileNotFoundException, SignatureVerificationException {
268271
// 1) download the timestamp.json bytes.
269272
var timestamp =
270-
fetcher
273+
metaFetcher
271274
.getMeta(RootRole.TIMESTAMP, Timestamp.class)
272-
.orElseThrow(() -> new FileNotFoundException("timestamp.json", fetcher.getSource()))
275+
.orElseThrow(() -> new FileNotFoundException("timestamp.json", metaFetcher.getSource()))
273276
.getMetaResource();
274277

275278
// 2) verify against threshold of keys as specified in trusted root.json
@@ -303,14 +306,14 @@ Snapshot updateSnapshot(Root root, Timestamp timestamp)
303306
// 1) download the snapshot.json bytes up to timestamp's snapshot length.
304307
int timestampSnapshotVersion = timestamp.getSignedMeta().getSnapshotMeta().getVersion();
305308
var snapshotResult =
306-
fetcher.getMeta(
309+
metaFetcher.getMeta(
307310
RootRole.SNAPSHOT,
308311
timestampSnapshotVersion,
309312
Snapshot.class,
310313
timestamp.getSignedMeta().getSnapshotMeta().getLengthOrDefault());
311314
if (snapshotResult.isEmpty()) {
312315
throw new FileNotFoundException(
313-
timestampSnapshotVersion + ".snapshot.json", fetcher.getSource());
316+
timestampSnapshotVersion + ".snapshot.json", metaFetcher.getSource());
314317
}
315318
// 2) check against timestamp.snapshot.hash, this is optional, the fallback is
316319
// that the version must match, which is handled in (4).
@@ -393,14 +396,14 @@ Targets updateTargets(Root root, Snapshot snapshot)
393396
// 1) download the targets.json up to targets.json length in bytes.
394397
SnapshotMeta.SnapshotTarget targetMeta = snapshot.getSignedMeta().getTargetMeta("targets.json");
395398
var targetsResultMaybe =
396-
fetcher.getMeta(
399+
metaFetcher.getMeta(
397400
RootRole.TARGETS,
398401
targetMeta.getVersion(),
399402
Targets.class,
400403
targetMeta.getLengthOrDefault());
401404
if (targetsResultMaybe.isEmpty()) {
402405
throw new FileNotFoundException(
403-
targetMeta.getVersion() + ".targets.json", fetcher.getSource());
406+
targetMeta.getVersion() + ".targets.json", metaFetcher.getSource());
404407
}
405408
var targetsResult = targetsResultMaybe.get();
406409
// 2) check hash against snapshot.targets.hash, else just make sure versions match, handled
@@ -451,9 +454,9 @@ void downloadTargets(Targets targets)
451454
}
452455

453456
var targetBytes =
454-
fetcher.fetchResource("targets/" + versionedTargetName, targetData.getLength());
457+
targetFetcher.fetchResource("targets/" + versionedTargetName, targetData.getLength());
455458
if (targetBytes == null) {
456-
throw new FileNotFoundException(targetName, fetcher.getSource());
459+
throw new FileNotFoundException(targetName, targetFetcher.getSource());
457460
}
458461
verifyHashes(entry.getKey(), targetBytes, targetData.getHashes());
459462

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

475-
private MetaFetcher fetcher;
478+
private MetaFetcher metaFetcher;
479+
private Fetcher targetFetcher;
476480
private RootProvider trustedRootPath;
477481
private MutableTufStore localStore;
478482

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

499-
public Builder setFetcher(MetaFetcher fetcher) {
500-
this.fetcher = fetcher;
503+
public Builder setMetaFetcher(MetaFetcher metaFetcher) {
504+
this.metaFetcher = metaFetcher;
505+
return this;
506+
}
507+
508+
public Builder setTargetFetcher(Fetcher fetcher) {
509+
this.targetFetcher = fetcher;
501510
return this;
502511
}
503512

504513
public Updater build() {
505-
return new Updater(clock, verifiers, fetcher, trustedRootPath, localStore);
514+
return new Updater(clock, verifiers, metaFetcher, targetFetcher, trustedRootPath, localStore);
506515
}
507516
}
508517
}

0 commit comments

Comments
 (0)