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

Use tuf verifiers in updater #849

Merged
merged 1 commit into from
Nov 14, 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
34 changes: 17 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 @@ -19,15 +19,15 @@

import com.google.common.annotations.VisibleForTesting;
import com.google.common.hash.Hashing;
import dev.sigstore.encryption.Keys;
import dev.sigstore.encryption.signers.Verifiers;
import dev.sigstore.tuf.encryption.Verifiers;
import dev.sigstore.tuf.model.*;
import dev.sigstore.tuf.model.TargetMeta.TargetData;
import dev.sigstore.tuf.model.Targets;
import dev.sigstore.tuf.model.Timestamp;
import dev.sigstore.tuf.model.TufMeta;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.time.Clock;
Expand Down Expand Up @@ -247,34 +247,34 @@ void verifyDelegate(
// look for the public key that matches the key ID and use it for verification.
var key = publicKeys.get(signature.getKeyId());
if (key != null) {
String publicKeyContents = key.getKeyVal().get("public");
PublicKey pubKey;
// TUF root version 4 and less is raw hex encoded key while 5+ is PEM.
// TODO([email protected]): remove hex handling code once we upgrade the trusted root
// to v5.
if (publicKeyContents.startsWith("-----BEGIN PUBLIC KEY-----")) {
pubKey = Keys.parsePublicKey(publicKeyContents.getBytes(StandardCharsets.UTF_8));
} else {
pubKey = Keys.constructTufPublicKey(Hex.decode(publicKeyContents), key.getScheme());
}
try {
// while we error on keys that are not readable, we are intentionally more permissive
// about signatures. If for ANY reason (except unparsed keys) we cannot validate a
// signature, we continue as long as we find enough valid signatures within the
// threshold. We still warn the user as this could be an indicator of data issues
byte[] signatureBytes = Hex.decode(signature.getSignature());
if (verifiers.newVerifier(pubKey).verify(verificationMaterial, signatureBytes)) {
if (verifiers.newVerifier(key).verify(verificationMaterial, signatureBytes)) {
goodSigs.add(signature.getKeyId());
} else {
log.log(
Level.FINE,
() ->
String.format(
Locale.ROOT,
"TUF: ignored failed signature verification: '%s' for keyid: '%s'",
signature.getSignature(),
signature.getKeyId()));
}
} catch (SignatureException e) {
log.log(
Level.FINE,
() ->
String.format(
Locale.ROOT,
"TUF: ignored unverifiable signature: '%s' for keyid: '%s'",
"TUF: ignored unverifiable signature: '%s' for keyid: '%s', because '%s'",
signature.getSignature(),
signature.getKeyId()));
signature.getKeyId(),
e.getMessage()));
} catch (DecoderException | NoSuchAlgorithmException | InvalidKeyException e) {
log.log(
Level.WARNING,
Expand Down
110 changes: 6 additions & 104 deletions sigstore-java/src/test/java/dev/sigstore/tuf/UpdaterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
package dev.sigstore.tuf;

import static dev.sigstore.json.GsonSupplier.GSON;
import static dev.sigstore.testkit.tuf.TestResources.UPDATER_REAL_TRUSTED_ROOT;
import static dev.sigstore.testkit.tuf.TestResources.UPDATER_SYNTHETIC_TRUSTED_ROOT;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
Expand All @@ -27,12 +26,11 @@

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.hash.Hashing;
import com.google.common.io.Resources;
import com.google.gson.JsonSyntaxException;
import dev.sigstore.encryption.signers.Verifier;
import dev.sigstore.encryption.signers.Verifiers;
import dev.sigstore.testkit.tuf.TestResources;
import dev.sigstore.tuf.encryption.Verifier;
import dev.sigstore.tuf.encryption.Verifiers;
import dev.sigstore.tuf.model.Hashes;
import dev.sigstore.tuf.model.ImmutableKey;
import dev.sigstore.tuf.model.ImmutableRootRole;
Expand All @@ -41,7 +39,6 @@
import dev.sigstore.tuf.model.Role;
import dev.sigstore.tuf.model.Root;
import dev.sigstore.tuf.model.Signature;
import dev.sigstore.tuf.model.TargetMeta;
import dev.sigstore.tuf.model.Targets;
import io.github.netmikey.logunit.api.LogCapturer;
import java.io.File;
Expand All @@ -52,8 +49,6 @@
import java.nio.file.Path;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.time.Clock;
import java.time.Instant;
Expand Down Expand Up @@ -123,19 +118,6 @@ static void startRemoteResourceServer() throws Exception {
System.out.println("TUF local server listening on: " + remoteUrl);
}

@Test
public void testRootUpdate_fromProdData() throws Exception {
setupMirror(
"real/prod", "1.root.json", "2.root.json", "3.root.json", "4.root.json", "5.root.json");
var updater = createTimeStaticUpdater(localStorePath, UPDATER_REAL_TRUSTED_ROOT);
updater.updateRoot();
assertStoreContains("root.json");
Root oldRoot = TestResources.loadRoot(UPDATER_REAL_TRUSTED_ROOT);
Root newRoot = TestResources.loadRoot(localStorePath.resolve("root.json"));
assertRootVersionIncreased(oldRoot, newRoot);
assertRootNotExpired(newRoot);
}

@Test
public void testRootUpdate_notEnoughSignatures()
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException {
Expand Down Expand Up @@ -603,50 +585,6 @@ public void testTargetsDownload_sha256Only() throws Exception {
assertDoesNotThrow(updater::update);
}

// End to end sanity test on the actual prod sigstore repo.
@Test
public void testUpdate_fromProdData()
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException {
setupMirror(
"real/prod",
"1.root.json",
"2.root.json",
"3.root.json",
"4.root.json",
"5.root.json",
"69.snapshot.json",
"5.targets.json",
"timestamp.json",
"snapshot.json",
"targets.json",
"root.json",
"targets/0ae7705e02db33e814329746a4a0e5603c5bdcd91c96d072158d71011a2695788866565a2fec0fe363eb72cbcaeda39e54c5fe8d416daf9f3101fdba4217ef35.rekor.pub",
"targets/0f99f47dbc26c5f1e3cba0bfd9af4245a26e5cb735d6ef005792ec7e603f66fdb897de985973a6e50940ca7eff5e1849719e967b5ad2dac74a29115a41cf6f21.fulcio_intermediate_v1.crt.pem",
"targets/4b20747d1afe2544238ad38cc0cc3010921b177d60ac743767e0ef675b915489bd01a36606c0ff83c06448622d7160f0d866c83d20f0c0f44653dcc3f9aa0bd4.ctfe.pub",
"targets/308fd1d1d95d7f80aa33b837795251cc3e886792982275e062409e13e4e236ffc34d676682aa96fdc751414de99c864bf132dde71581fa651c6343905e3bf988.artifact.pub",
"targets/0713252a7fd17f7f3ab12f88a64accf2eb14b8ad40ca711d7fe8b4ecba3b24db9e9dffadb997b196d3867b8f9ff217faf930d80e4dab4e235c7fc3f07be69224.fulcio.crt.pem",
"targets/e83fa4f427b24ee7728637fad1b4aa45ebde2ba02751fa860694b1bb16059a490328f9985e51cc70e4d237545315a1bc866dc4fdeef2f6248d99cc7a6077bf85.ctfe_2022.pub",
"targets/f2e33a6dc208cee1f51d33bbea675ab0f0ced269617497985f9a0680689ee7073e4b6f8fef64c91bda590d30c129b3070dddce824c05bc165ac9802f0705cab6.fulcio_v1.crt.pem");
var updater = createTimeStaticUpdater(localStorePath, UPDATER_REAL_TRUSTED_ROOT);
updater.update();

Root oldRoot = TestResources.loadRoot(UPDATER_REAL_TRUSTED_ROOT);
TrustedMetaStore metaStore = updater.getMetaStore();
TargetStore targetStore = updater.getTargetStore();
Root newRoot = metaStore.getRoot(); // should be present
assertRootVersionIncreased(oldRoot, newRoot);
Targets targets = metaStore.getTargets(); // should be present
Map<String, TargetMeta.TargetData> targetsData = targets.getSignedMeta().getTargets();
for (String file : targetsData.keySet()) {
TargetMeta.TargetData fileData = targetsData.get(file);
byte[] fileBytes = targetStore.readTarget(file);
assertNotNull(fileBytes, "each file from targets data should be present");
assertEquals(fileData.getLength(), fileBytes.length, "file length should match metadata");
assertEquals(
fileData.getHashes().getSha512(), Hashing.sha512().hashBytes(fileBytes).toString());
}
}

private static final byte[] TEST_HASH_VERIFYIER_BYTES =
"testdata".getBytes(StandardCharsets.UTF_8);
private static final String GOOD_256_HASH =
Expand Down Expand Up @@ -941,8 +879,8 @@ public void testUpdate_snapshotsAndTimestampHaveNoSizeAndNoHashesInMeta() throws

@Test
public void canCreateMultipleUpdaters() throws IOException {
createTimeStaticUpdater(localStorePath, UPDATER_REAL_TRUSTED_ROOT);
createTimeStaticUpdater(localStorePath, UPDATER_REAL_TRUSTED_ROOT);
createTimeStaticUpdater(localStorePath, UPDATER_SYNTHETIC_TRUSTED_ROOT);
createTimeStaticUpdater(localStorePath, UPDATER_SYNTHETIC_TRUSTED_ROOT);
}

static Key newKey(String keyContents) {
Expand Down Expand Up @@ -1027,43 +965,7 @@ static void shutdownRemoteResourceServer() throws Exception {
}

public static final Verifiers.Supplier ALWAYS_VERIFIES =
publicKey ->
new Verifier() {
@Override
public PublicKey getPublicKey() {
return null;
}

@Override
public boolean verify(byte[] artifact, byte[] signature)
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
return true;
}

@Override
public boolean verifyDigest(byte[] artifactDigest, byte[] signature)
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
return true;
}
};
(key) -> (Verifier) (artifactDigest, signature) -> true;
public static final Verifiers.Supplier ALWAYS_FAILS =
publicKey ->
new Verifier() {
@Override
public PublicKey getPublicKey() {
return null;
}

@Override
public boolean verify(byte[] artifact, byte[] signature)
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
return false;
}

@Override
public boolean verifyDigest(byte[] artifactDigest, byte[] signature)
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
return false;
}
};
(key) -> (Verifier) (artifactDigest, signature) -> false;
}
18 changes: 0 additions & 18 deletions tuf-cli/tuf-cli.xfails
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
test_metadata_bytes_match
test_client_downloads_expected_file_in_sub_dir
test_duplicate_sig_keyids
test_keytype_and_scheme[rsa/rsassa-pss-sha256]
test_keytype_and_scheme[ed25519/ed25519]
test_unusual_role_name[?]
test_unusual_role_name[#]
test_unusual_role_name[/delegatedrole]
Expand All @@ -26,20 +24,4 @@ test_targetfile_search[targetpath matches wildcard]
test_targetfile_search[targetpath with separators x]
test_targetfile_search[targetpath with separators y]
test_targetfile_search[targetpath is not delegated by all roles in the chain]
test_root_rotation[1-of-1-key-rotation]
test_root_rotation[1-of-1-key-rotation-unused-signatures]
test_root_rotation[3-of-5-sign-with-different-keycombos]
test_root_rotation[3-of-5-one-key-rotated]
test_root_rotation[3-of-5-one-key-rotated-with-intermediate-step]
test_root_rotation[3-of-5-all-keys-rotated-with-intermediate-step]
test_root_rotation[1-of-3-threshold-increase-to-2-of-3]
test_root_rotation[2-of-3-threshold-decrease-to-1-of-3]
test_root_rotation[1-of-2-threshold-increase-to-2-of-2]
test_non_root_rotations[1-of-1-key-rotation]
test_non_root_rotations[1-of-1-key-rotation-unused-signatures]
test_non_root_rotations[3-of-5-sign-first-combo]
test_non_root_rotations[3-of-5-sign-second-combo]
test_non_root_rotations[3-of-5-sign-third-combo]
test_non_root_rotations[3-of-5-sign-fourth-combo]
test_non_root_rotations[3-of-5-sign-fifth-combo]
test_snapshot_rollback[with hashes]
Loading