FileSystemTufStore.java
/*
* 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.common.annotations.VisibleForTesting;
import dev.sigstore.tuf.model.*;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
/** Uses a local file system directory to store the trusted TUF metadata. */
public class FileSystemTufStore implements MetaStore, TargetStore {
private final Path repoBaseDir;
private final Path targetsDir;
@VisibleForTesting
FileSystemTufStore(Path repoBaseDir, Path targetsDir) {
this.repoBaseDir = repoBaseDir;
this.targetsDir = targetsDir;
}
public static FileSystemTufStore newFileSystemStore(Path repoBaseDir) throws IOException {
if (!Files.isDirectory(repoBaseDir)) {
throw new IllegalArgumentException(repoBaseDir + " must be a file system directory.");
}
Path defaultTargetsCache = repoBaseDir.resolve("targets");
if (!Files.exists(defaultTargetsCache)) {
Files.createDirectory(defaultTargetsCache);
}
return newFileSystemStore(repoBaseDir, defaultTargetsCache);
}
public static FileSystemTufStore newFileSystemStore(Path repoBaseDir, Path targetsCache) {
if (!Files.isDirectory(repoBaseDir)) {
throw new IllegalArgumentException(repoBaseDir + " must be a file system directory.");
}
if (!Files.isDirectory(targetsCache)) {
throw new IllegalArgumentException(targetsCache + " must be a file system directory.");
}
return new FileSystemTufStore(repoBaseDir, targetsCache);
}
@Override
public String getIdentifier() {
return "Meta: " + repoBaseDir.toAbsolutePath() + ", Targets:" + targetsDir.toAbsolutePath();
}
@Override
public void writeTarget(String targetName, byte[] targetContents) throws IOException {
var encoded = URLEncoder.encode(targetName, StandardCharsets.UTF_8);
Files.write(targetsDir.resolve(encoded), targetContents);
}
@Override
public byte[] readTarget(String targetName) throws IOException {
var encoded = URLEncoder.encode(targetName, StandardCharsets.UTF_8);
return Files.readAllBytes(targetsDir.resolve(encoded));
}
@Override
public InputStream getTargetInputSteam(String targetName) throws IOException {
var encoded = URLEncoder.encode(targetName, StandardCharsets.UTF_8);
return Files.newInputStream(targetsDir.resolve(encoded));
}
@Override
public boolean hasTarget(String targetName) throws IOException {
var encoded = URLEncoder.encode(targetName, StandardCharsets.UTF_8);
return Files.isRegularFile(targetsDir.resolve(encoded));
}
@Override
public void writeMeta(String roleName, SignedTufMeta<?> meta) throws IOException {
storeRole(roleName, meta);
}
@Override
public <T extends SignedTufMeta<?>> Optional<T> readMeta(String roleName, Class<T> tClass)
throws IOException {
Path roleFile = repoBaseDir.resolve(roleName + ".json");
if (!roleFile.toFile().exists()) {
return Optional.empty();
}
return Optional.of(GSON.get().fromJson(Files.readString(roleFile), tClass));
}
<T extends SignedTufMeta<?>> void storeRole(String roleName, T role) throws IOException {
try (BufferedWriter fileWriter =
Files.newBufferedWriter(repoBaseDir.resolve(roleName + ".json"))) {
GSON.get().toJson(role, fileWriter);
}
}
@Override
public void clearMeta(String role) throws IOException {
Path metaFile = repoBaseDir.resolve(role + ".json");
if (Files.isRegularFile(metaFile)) {
Files.delete(metaFile);
}
}
public Path getRepoBaseDir() {
return repoBaseDir;
}
public Path getTargetsDir() {
return targetsDir;
}
}