Skip to content

Commit

Permalink
Try adding tuf conformance
Browse files Browse the repository at this point in the history
Signed-off-by: Appu Goundan <[email protected]>
  • Loading branch information
loosebazooka committed Oct 31, 2024
1 parent 8e91247 commit 3b57066
Show file tree
Hide file tree
Showing 9 changed files with 349 additions and 29 deletions.
40 changes: 40 additions & 0 deletions .github/workflows/tuf-conformance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Conformance Tests

on:
push:
branches:
- '**'
pull_request:
workflow_dispatch:
# TODO: add cron

jobs:
conformance:
strategy:
max-parallel: 1
matrix:
java-version: [11, 17]
fail-fast: false

runs-on: ubuntu-latest
steps:
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1

- name: Set up JDK ${{ matrix.java-version }}
uses: actions/setup-java@b36c23c0d998641eff861008f374ee103c25ac73 # v4.4.0
with:
java-version: ${{ matrix.java-version }}
distribution: 'temurin'

- name: Setup Gradle
uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0

- name: Build tuf cli
run: ./gradlew :tuf-cli:build

- name: Unpack tuf distribution
run: tar -xvf ${{ github.workspace }}/tuf-cli/build/distributions/tuf-cli-*.tar --strip-components 1

- uses: theupdateframework/tuf-conformance@v2
with:
entrypoint: ${{ github.workspace }}/bin/tuf-cli
4 changes: 3 additions & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ include("sigstore-java")
include("sigstore-gradle:sigstore-gradle-sign-base-plugin")
include("sigstore-gradle:sigstore-gradle-sign-plugin")
include("sigstore-testkit")
include("sigstore-cli")
include("sigstore-maven-plugin")

include("sigstore-cli")
include("tuf-cli")

include("fuzzing")
79 changes: 51 additions & 28 deletions sigstore-java/src/main/java/dev/sigstore/tuf/Updater.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import dev.sigstore.encryption.Keys;
import dev.sigstore.encryption.signers.Verifiers;
import dev.sigstore.tuf.model.*;
import dev.sigstore.tuf.model.TargetMeta.TargetData;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
Expand Down Expand Up @@ -69,6 +70,7 @@ public class Updater {

// Mutable State
private ZonedDateTime updateStartTime;
private boolean metaUpdated;

Updater(
Clock clock,
Expand Down Expand Up @@ -97,19 +99,36 @@ public void update()
downloadTargets(trustedMetaStore.getTargets());
}

void updateMeta() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
updateRoot();
var oldTimestamp = trustedMetaStore.findTimestamp();
updateTimestamp();
if (Objects.equals(oldTimestamp.orElse(null), trustedMetaStore.getTimestamp())
&& trustedMetaStore.findSnapshot().isPresent()
&& trustedMetaStore.findTargets().isPresent()) {
return;
}
// if we need to update or we can't find targets/timestamps locally then grab new snapshot and
// targets from remote
updateSnapshot();
updateTargets();
public void updateMeta() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
try {
updateRoot();
var oldTimestamp = trustedMetaStore.findTimestamp();
updateTimestamp();
if (Objects.equals(oldTimestamp.orElse(null), trustedMetaStore.getTimestamp())
&& trustedMetaStore.findSnapshot().isPresent()
&& trustedMetaStore.findTargets().isPresent()) {
return;
}
// if we need to update or we can't find targets/timestamps locally then grab new snapshot and
// targets from remote
updateSnapshot();
updateTargets();
} finally {
metaUpdated = true;
}
}

public void downloadTarget(String targetName)
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
// update should run at least once before we try to grab targets
if (!metaUpdated) {
updateMeta();
}
var targetData = trustedMetaStore.getTargets().getSignedMeta().getTargets().get(targetName);
if (targetData == null) {
throw new TargetMetadataMissingException(targetName);
}
downloadTarget(targetName, targetData);
}

// https://theupdateframework.github.io/specification/latest/#detailed-client-workflow
Expand Down Expand Up @@ -459,24 +478,28 @@ void downloadTargets(Targets targets)
throw new TargetMetadataMissingException(targetName);
}
TargetMeta.TargetData targetData = entry.getValue();
// 9) Download target up to length specified in bytes. verify against hash.
String versionedTargetName;
if (targetData.getHashes().getSha512() != null) {
versionedTargetName = targetData.getHashes().getSha512() + "." + targetName;
} else {
versionedTargetName = targetData.getHashes().getSha256() + "." + targetName;
}
downloadTarget(targetName, targetData);
}
}

var targetBytes = targetFetcher.fetchResource(versionedTargetName, targetData.getLength());
if (targetBytes == null) {
throw new FileNotFoundException(targetName, targetFetcher.getSource());
}
verifyHashes(entry.getKey(), targetBytes, targetData.getHashes());
void downloadTarget(String targetName, TargetData targetData) throws IOException {
// 9) Download target up to length specified in bytes. verify against hash.
String versionedTargetName;
if (targetData.getHashes().getSha512() != null) {
versionedTargetName = targetData.getHashes().getSha512() + "." + targetName;
} else {
versionedTargetName = targetData.getHashes().getSha256() + "." + targetName;
}

// when persisting targets use the targetname without sha512 prefix
// https://theupdateframework.github.io/specification/latest/index.html#fetch-target
targetStore.writeTarget(targetName, targetBytes);
var targetBytes = targetFetcher.fetchResource(versionedTargetName, targetData.getLength());
if (targetBytes == null) {
throw new FileNotFoundException(targetName, targetFetcher.getSource());
}
verifyHashes(targetName, targetBytes, targetData.getHashes());

// when persisting targets use the targetname without sha512 prefix
// https://theupdateframework.github.io/specification/latest/index.html#fetch-target
targetStore.writeTarget(targetName, targetBytes);
}

@VisibleForTesting
Expand Down
11 changes: 11 additions & 0 deletions tuf-cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Sigstore-Java Tuf CLI

Used for conformance testing and internal processes. This is not meant for public consumption, we will not support
any usecase that uses this.

## Usage

### Help
```
./gradlew tuf-cli:run
```
30 changes: 30 additions & 0 deletions tuf-cli/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
plugins {
id("build-logic.java")
id("application")
}

repositories {
mavenCentral()
}

dependencies {
implementation(project(":sigstore-java"))
implementation("info.picocli:picocli:4.7.6")
implementation("com.google.guava:guava:33.3.1-jre")

implementation(platform("com.google.oauth-client:google-oauth-client-bom:1.36.0"))
implementation("com.google.oauth-client:google-oauth-client")

annotationProcessor("info.picocli:picocli-codegen:4.7.6")
}

tasks.compileJava {
options.compilerArgs.add("-Aproject=${project.group}/${project.name}")
}

application {
mainClass.set("dev.sigstore.tuf.cli.Tuf")
}
tasks.run.configure {
workingDir = rootProject.projectDir
}
50 changes: 50 additions & 0 deletions tuf-cli/src/main/java/dev/sigstore/tuf/cli/Download.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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.cli;

import dev.sigstore.tuf.FileSystemTufStore;
import dev.sigstore.tuf.HttpFetcher;
import dev.sigstore.tuf.MetaFetcher;
import dev.sigstore.tuf.PassthroughCacheMetaStore;
import dev.sigstore.tuf.RootProvider;
import dev.sigstore.tuf.TrustedMetaStore;
import dev.sigstore.tuf.Updater;
import java.util.concurrent.Callable;
import picocli.CommandLine.Command;
import picocli.CommandLine.ParentCommand;

@Command(name = "download", description = "download targets from a remote location")
public class Download implements Callable<Integer> {

@ParentCommand private Tuf tufCommand;

@Override
public Integer call() throws Exception {
var fsStore = FileSystemTufStore.newFileSystemStore(tufCommand.metadataDir);
var tuf =
Updater.builder()
.setTrustedMetaStore(
TrustedMetaStore.newTrustedMetaStore(
PassthroughCacheMetaStore.newPassthroughMetaCache(fsStore)))
.setTrustedRootPath(RootProvider.fromFile(tufCommand.metadataDir.resolve("root.json")))
.setMetaFetcher(MetaFetcher.newFetcher(HttpFetcher.newFetcher(tufCommand.metadataUrl)))
.setTargetFetcher(HttpFetcher.newFetcher(tufCommand.targetBaseUrl))
.setTargetStore(fsStore)
.build();
tuf.downloadTarget(tufCommand.targetName);
return 0;
}
}
49 changes: 49 additions & 0 deletions tuf-cli/src/main/java/dev/sigstore/tuf/cli/Init.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* 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.cli;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.Callable;
import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;
import picocli.CommandLine.ParentCommand;

@Command(name = "init", description = "initialize a local tuf repo")
public class Init implements Callable<Integer> {

@Parameters(arity = "1", paramLabel = "<TRUSTED_ROOT>")
Path trustedRoot;

@ParentCommand private Tuf tufCommand;

@Override
public Integer call() throws Exception {
if (!Files.isRegularFile(trustedRoot)) {
throw new IllegalArgumentException(trustedRoot + " is not a regular file");
}
if (Files.exists(tufCommand.metadataDir)) {
if (!Files.isDirectory(tufCommand.metadataDir)) {
throw new IllegalArgumentException(tufCommand.metadataDir + " is not a directory");
}
} else {
Files.createDirectories(tufCommand.metadataDir);
}

Files.copy(trustedRoot, tufCommand.metadataDir.resolve("root.json"));
return 0;
}
}
48 changes: 48 additions & 0 deletions tuf-cli/src/main/java/dev/sigstore/tuf/cli/Refresh.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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.cli;

import dev.sigstore.tuf.FileSystemTufStore;
import dev.sigstore.tuf.HttpFetcher;
import dev.sigstore.tuf.MetaFetcher;
import dev.sigstore.tuf.PassthroughCacheMetaStore;
import dev.sigstore.tuf.RootProvider;
import dev.sigstore.tuf.TrustedMetaStore;
import dev.sigstore.tuf.Updater;
import java.util.concurrent.Callable;
import picocli.CommandLine.Command;
import picocli.CommandLine.ParentCommand;

@Command(name = "refresh", description = "update local tuf metadata from the repository")
public class Refresh implements Callable<Integer> {

@ParentCommand private Tuf tufCommand;

@Override
public Integer call() throws Exception {
var fsStore = FileSystemTufStore.newFileSystemStore(tufCommand.metadataDir);
var tuf =
Updater.builder()
.setTrustedMetaStore(
TrustedMetaStore.newTrustedMetaStore(
PassthroughCacheMetaStore.newPassthroughMetaCache(fsStore)))
.setTrustedRootPath(RootProvider.fromFile(tufCommand.metadataDir.resolve("root.json")))
.setMetaFetcher(MetaFetcher.newFetcher(HttpFetcher.newFetcher(tufCommand.metadataUrl)))
.build();
tuf.update();
return 0;
}
}
Loading

0 comments on commit 3b57066

Please sign in to comment.