Skip to content

Commit

Permalink
Merge pull request #849 from sigstore/use_tuf_verifiers_in_updater
Browse files Browse the repository at this point in the history
Use tuf verifiers in updater
  • Loading branch information
loosebazooka authored Nov 14, 2024
2 parents 12068fc + 6d82e02 commit ce09d39
Show file tree
Hide file tree
Showing 3 changed files with 23 additions and 139 deletions.
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]

0 comments on commit ce09d39

Please sign in to comment.