diff --git a/metadata/metadata_api_test.go b/metadata/metadata_api_test.go index 15544926..28d5ca93 100644 --- a/metadata/metadata_api_test.go +++ b/metadata/metadata_api_test.go @@ -14,10 +14,15 @@ package metadata import ( "bytes" "crypto" + "crypto/sha256" + "encoding/json" "fmt" "io/fs" "os" + "strconv" + "strings" "testing" + "time" testutils "github.com/rdimitrov/go-tuf-metadata/testutils/testutils" "github.com/sigstore/sigstore/pkg/cryptoutils" @@ -340,7 +345,7 @@ func TestSignVerify(t *testing.T) { // Load sample metadata (targets) and assert ... targets, err := Targets().FromFile(testutils.RepoDir + "/targets.json") assert.NoError(t, err) - sig := getSignatureByKeyID(targets.Signatures, targetsKeyID) + sig, _ := getSignatureByKeyID(targets.Signatures, targetsKeyID) data, err := targets.Signed.MarshalJSON() assert.NoError(t, err) @@ -404,3 +409,546 @@ func TestSignVerify(t *testing.T) { err = targetsVerifier.VerifySignature(bytes.NewReader(timestampSig.Signature), bytes.NewReader(data)) assert.ErrorContains(t, err, "crypto/rsa: verification error") } + +func TestKeyVerifyFailures(t *testing.T) { + root, err := Root().FromFile(testutils.RepoDir + "/root.json") + assert.NoError(t, err) + + // Locate the timestamp public key we need from root + assert.NotEmpty(t, root.Signed.Roles[TIMESTAMP].KeyIDs) + timestampKeyID := root.Signed.Roles[TIMESTAMP].KeyIDs[0] + + // Load sample metadata (timestamp) + timestamp, err := Timestamp().FromFile(testutils.RepoDir + "/timestamp.json") + assert.NoError(t, err) + timestampSig, _ := getSignatureByKeyID(timestamp.Signatures, timestampKeyID) + data, err := timestamp.Signed.MarshalJSON() + assert.NoError(t, err) + + // Test failure on unknown type + // Originally this test should cover unknown scheme, + // but in our case scheme changes do not affect any + // further functionality + timestampKey := root.Signed.Keys[timestampKeyID] + ttype := timestampKey.Type + timestampKey.Type = "foo" + + timestampPublicKey, err := timestampKey.ToPublicKey() + assert.Error(t, err, "unsupported public key type") + timestampHash := crypto.SHA256 + timestampVerifier, err := signature.LoadVerifier(timestampPublicKey, timestampHash) + assert.Error(t, err, "unsupported public key type") + assert.Nil(t, timestampVerifier) + + timestampKey.Type = ttype + timestampPublicKey, err = timestampKey.ToPublicKey() + assert.NoError(t, err) + timestampHash = crypto.SHA256 + timestampVerifier, err = signature.LoadVerifier(timestampPublicKey, timestampHash) + assert.NoError(t, err) + err = timestampVerifier.VerifySignature(bytes.NewReader(timestampSig), bytes.NewReader(data)) + assert.NoError(t, err) + timestampKey.Type = ttype + + // Test failure on broken public key data + public := timestampKey.Value.PublicKey + timestampKey.Value.PublicKey = "ffff" + timestampBrokenPublicKey, err := timestampKey.ToPublicKey() + assert.ErrorContains(t, err, "PEM decoding failed") + timestampHash = crypto.SHA256 + timestampNilVerifier, err := signature.LoadVerifier(timestampBrokenPublicKey, timestampHash) + assert.ErrorContains(t, err, "unsupported public key type") + assert.Nil(t, timestampNilVerifier) + timestampKey.Value.PublicKey = public + + // Test failure with invalid signature + sigData := []byte("foo") + h32 := sha256.Sum256(sigData) + incorrectTimestampSig := h32[:] + err = timestampVerifier.VerifySignature(bytes.NewReader(incorrectTimestampSig), bytes.NewReader(data)) + assert.ErrorContains(t, err, "crypto/rsa: verification error") + + // Test failure with valid but incorrect signature + anotherSig := root.Signatures[0] + h32 = sha256.Sum256([]byte(anotherSig.Signature.String())) + incorrectValidTimestampSig := h32[:] + err = timestampVerifier.VerifySignature(bytes.NewReader(incorrectValidTimestampSig), bytes.NewReader(data)) + assert.ErrorContains(t, err, "crypto/rsa: verification error") +} + +func TestMetadataSignedIsExpired(t *testing.T) { + // Use of Snapshot is arbitrary, we're just testing the base class + // features with real data + snapshot, err := Snapshot().FromFile(testutils.RepoDir + "/snapshot.json") + assert.NoError(t, err) + assert.Equal(t, time.Date(2030, 8, 15, 14, 30, 45, 100, time.UTC), snapshot.Signed.Expires) + + // Test IsExpired with reference time provided + // In the Go implementation IsExpired tests >= rather than only >, + // which results in snapshot.Signed.Expires IsExpired check + // being false by default, so we skip the default assertion + isExpired := snapshot.Signed.IsExpired(snapshot.Signed.Expires.Add(time.Microsecond)) + assert.True(t, isExpired) + isExpired = snapshot.Signed.IsExpired(snapshot.Signed.Expires.Add(-time.Microsecond)) + assert.False(t, isExpired) +} + +func TestMetadataVerifyDelegate(t *testing.T) { + + root, err := Root().FromFile(fmt.Sprintf("%s/root.json", testutils.RepoDir)) + assert.NoError(t, err) + snapshot, err := Snapshot().FromFile(fmt.Sprintf("%s/snapshot.json", testutils.RepoDir)) + assert.NoError(t, err) + targets, err := Targets().FromFile(fmt.Sprintf("%s/targets.json", testutils.RepoDir)) + assert.NoError(t, err) + role1, err := Targets().FromFile(fmt.Sprintf("%s/role1.json", testutils.RepoDir)) + assert.NoError(t, err) + role2, err := Targets().FromFile(fmt.Sprintf("%s/role2.json", testutils.RepoDir)) + assert.NoError(t, err) + + // Test the expected delegation tree + err = root.VerifyDelegate(ROOT, root) + assert.NoError(t, err) + err = root.VerifyDelegate(SNAPSHOT, snapshot) + assert.NoError(t, err) + err = root.VerifyDelegate(TARGETS, targets) + assert.NoError(t, err) + err = targets.VerifyDelegate("role1", role1) + assert.NoError(t, err) + err = role1.VerifyDelegate("role2", role2) + assert.NoError(t, err) + + // Only root and targets can verify delegates + err = snapshot.VerifyDelegate(SNAPSHOT, snapshot) + assert.ErrorIs(t, err, ErrType{"call is valid only on delegator metadata (should be either root or targets)"}) + // Verify fails for roles that are not delegated by delegator + err = root.VerifyDelegate("role1", role1) + assert.ErrorIs(t, err, ErrValue{"no delegation found for role1"}) + err = targets.VerifyDelegate(TARGETS, targets) + assert.ErrorIs(t, err, ErrValue{"no delegation found for targets"}) + // Verify fails when delegator has no delegations + err = role2.VerifyDelegate("role1", role1) + assert.ErrorIs(t, err, ErrValue{"no delegations found"}) + + // Verify fails when delegate content is modified + expires := snapshot.Signed.Expires + snapshot.Signed.Expires = snapshot.Signed.Expires.Add(time.Hour * 24) + err = root.VerifyDelegate(SNAPSHOT, snapshot) + assert.ErrorIs(t, err, ErrUnsignedMetadata{"Verifying snapshot failed, not enough signatures, got 0, want 1"}) + snapshot.Signed.Expires = expires + + // Verify fails with verification error + // (in this case signature is malformed) + keyID := root.Signed.Roles[SNAPSHOT].KeyIDs[0] + goodSig, idx := getSignatureByKeyID(snapshot.Signatures, keyID) + assert.NotEqual(t, -1, idx) + snapshot.Signatures[idx].Signature = []byte("foo") + err = root.VerifyDelegate(SNAPSHOT, snapshot) + assert.ErrorIs(t, err, ErrUnsignedMetadata{"Verifying snapshot failed, not enough signatures, got 0, want 1"}) + snapshot.Signatures[idx].Signature = goodSig + + // Verify fails if roles keys do not sign the metadata + err = root.VerifyDelegate(TIMESTAMP, snapshot) + assert.ErrorIs(t, err, ErrUnsignedMetadata{"Verifying timestamp failed, not enough signatures, got 0, want 1"}) + + // Add a key to snapshot role, make sure the new sig fails to verify + tsKeyID := root.Signed.Roles[TIMESTAMP].KeyIDs[0] + root.Signed.AddKey(root.Signed.Keys[tsKeyID], SNAPSHOT) + newSig := Signature{ + KeyID: tsKeyID, + Signature: []byte(strings.Repeat("ff", 64)), + } + snapshot.Signatures = append(snapshot.Signatures, newSig) + + // Verify succeeds if threshold is reached even if some signatures + // fail to verify + err = root.VerifyDelegate(SNAPSHOT, snapshot) + assert.NoError(t, err) + + // Verify fails if threshold of signatures is not reached + root.Signed.Roles[SNAPSHOT].Threshold = 2 + err = root.VerifyDelegate(SNAPSHOT, snapshot) + assert.ErrorIs(t, err, ErrUnsignedMetadata{"Verifying snapshot failed, not enough signatures, got 1, want 2"}) + + // Verify succeeds when we correct the new signature and reach the + // threshold of 2 keys + signer, err := signature.LoadSignerFromPEMFile(testutils.KeystoreDir+"/timestamp_key", crypto.SHA256, cryptoutils.SkipPassword) + assert.NoError(t, err) + _, err = snapshot.Sign(signer) + assert.NoError(t, err) + err = root.VerifyDelegate(SNAPSHOT, snapshot) + assert.NoError(t, err) +} + +func TestRootAddKeyAndRevokeKey(t *testing.T) { + root, err := Root().FromFile(fmt.Sprintf("%s/root.json", testutils.RepoDir)) + assert.NoError(t, err) + + // Create a new key + signer, err := signature.LoadSignerFromPEMFile(testutils.KeystoreDir+"/root_key2", crypto.SHA256, cryptoutils.SkipPassword) + assert.NoError(t, err) + key, err := signer.PublicKey() + assert.NoError(t, err) + rootKey2, err := KeyFromPublicKey(key) + assert.NoError(t, err) + + // Assert that root does not contain the new key + assert.NotContains(t, root.Signed.Roles[ROOT].KeyIDs, rootKey2.id) + assert.NotContains(t, root.Signed.Keys, rootKey2.id) + + // Add new root key + err = root.Signed.AddKey(rootKey2, ROOT) + assert.NoError(t, err) + + // Assert that key is added + assert.Contains(t, root.Signed.Roles[ROOT].KeyIDs, rootKey2.id) + assert.Contains(t, root.Signed.Keys, rootKey2.id) + + // Confirm that the newly added key does not break + // the object serialization + _, err = root.Signed.MarshalJSON() + assert.NoError(t, err) + + // Try adding the same key again and assert its ignored. + preAddKeyIDs := make([]string, len(root.Signed.Roles[ROOT].KeyIDs)) + copy(preAddKeyIDs, root.Signed.Roles[ROOT].KeyIDs) + err = root.Signed.AddKey(rootKey2, ROOT) + assert.NoError(t, err) + assert.Equal(t, preAddKeyIDs, root.Signed.Roles[ROOT].KeyIDs) + + // Add the same key to targets role as well + err = root.Signed.AddKey(rootKey2, TARGETS) + assert.NoError(t, err) + + // Add the same key to a nonexistent role. + err = root.Signed.AddKey(rootKey2, "nosuchrole") + assert.ErrorIs(t, err, ErrValue{"role nosuchrole doesn't exist"}) + + // Remove the key from root role (targets role still uses it) + root.Signed.RevokeKey(rootKey2.id, ROOT) + assert.NotContains(t, root.Signed.Roles[ROOT].KeyIDs, rootKey2.id) + assert.Contains(t, root.Signed.Keys, rootKey2.id) + + // Remove the key from targets as well + root.Signed.RevokeKey(rootKey2.id, TARGETS) + assert.NotContains(t, root.Signed.Roles[ROOT].KeyIDs, rootKey2.id) + assert.NotContains(t, root.Signed.Keys, rootKey2.id) + + err = root.Signed.RevokeKey("nosuchkey", ROOT) + assert.ErrorIs(t, err, ErrValue{"key with id nosuchkey is not used by root"}) + err = root.Signed.RevokeKey(rootKey2.id, "nosuchrole") + assert.ErrorIs(t, err, ErrValue{"role nosuchrole doesn't exist"}) +} + +func TestTargetsKeyAPI(t *testing.T) { + targets, err := Targets().FromFile(fmt.Sprintf("%s/targets.json", testutils.RepoDir)) + assert.NoError(t, err) + + delegatedRole := DelegatedRole{ + Name: "role2", + Paths: []string{"fn3", "fn4"}, + KeyIDs: []string{}, + Terminating: false, + Threshold: 1, + } + targets.Signed.Delegations.Roles = append(targets.Signed.Delegations.Roles, delegatedRole) + + key := &Key{ + Type: "ed25519", + Value: KeyVal{PublicKey: "edcd0a32a07dce33f7c7873aaffbff36d20ea30787574ead335eefd337e4dacd"}, + Scheme: "ed25519", + } + + // Assert that delegated role "role1" does not contain the new key + assert.Equal(t, "role1", targets.Signed.Delegations.Roles[0].Name) + assert.NotContains(t, targets.Signed.Delegations.Roles[0].KeyIDs, key.id) + err = targets.Signed.AddKey(key, "role1") + assert.NoError(t, err) + + // Assert that the new key is added to the delegated role "role1" + assert.Contains(t, targets.Signed.Delegations.Roles[0].KeyIDs, key.id) + + // Try adding the same key again and assert its ignored. + pastKeyIDs := make([]string, len(targets.Signed.Delegations.Roles[0].KeyIDs)) + copy(pastKeyIDs, targets.Signed.Delegations.Roles[0].KeyIDs) + err = targets.Signed.AddKey(key, "role1") + assert.NoError(t, err) + assert.Equal(t, pastKeyIDs, targets.Signed.Delegations.Roles[0].KeyIDs) + + // Try adding a key to a delegated role that doesn't exists + err = targets.Signed.AddKey(key, "nosuchrole") + assert.ErrorIs(t, err, ErrValue{"delegated role nosuchrole doesn't exist"}) + + // Add the same key to "role2" as well + err = targets.Signed.AddKey(key, "role2") + assert.NoError(t, err) + + // Remove the key from "role1" role ("role2" still uses it) + err = targets.Signed.RevokeKey(key.id, "role1") + assert.NoError(t, err) + + // Assert that delegated role "role1" doesn't contain the key. + assert.Equal(t, "role1", targets.Signed.Delegations.Roles[0].Name) + assert.Equal(t, "role2", targets.Signed.Delegations.Roles[1].Name) + assert.NotContains(t, targets.Signed.Delegations.Roles[0].KeyIDs, key.id) + assert.Contains(t, targets.Signed.Delegations.Roles[1].KeyIDs, key.id) + + // Remove the key from "role2" as well + err = targets.Signed.RevokeKey(key.id, "role2") + assert.NoError(t, err) + assert.NotContains(t, targets.Signed.Delegations.Roles[1].KeyIDs, key.id) + + // Try remove key not used by "role1" + err = targets.Signed.RevokeKey(key.id, "role1") + assert.ErrorIs(t, err, ErrValue{fmt.Sprintf("key with id %s is not used by role1", key.id)}) + + // Try removing a key from delegated role that doesn't exists + err = targets.Signed.RevokeKey(key.id, "nosuchrole") + assert.ErrorIs(t, err, ErrValue{"delegated role nosuchrole doesn't exist"}) + + // Remove delegations as a whole + targets.Signed.Delegations = nil + + //Test that calling add_key and revoke_key throws an error + // and that delegations is still None after each of the api calls + err = targets.Signed.AddKey(key, "role1") + assert.ErrorIs(t, err, ErrValue{"delegated role role1 doesn't exist"}) + err = targets.Signed.RevokeKey(key.id, "role1") + assert.ErrorIs(t, err, ErrValue{"delegated role role1 doesn't exist"}) + assert.Nil(t, targets.Signed.Delegations) +} + +func TestTargetsKeyAPIWithSuccinctRoles(t *testing.T) { + targets, err := Targets().FromFile(testutils.RepoDir + "/targets.json") + assert.NoError(t, err) + + // Remove delegated roles + assert.NotNil(t, targets.Signed.Delegations) + assert.NotNil(t, targets.Signed.Delegations.Roles) + targets.Signed.Delegations.Roles = nil + targets.Signed.Delegations.Keys = map[string]*Key{} + + // Add succinct roles information + targets.Signed.Delegations.SuccinctRoles = &SuccinctRoles{ + KeyIDs: []string{}, + Threshold: 1, + BitLength: 8, + NamePrefix: "foo", + } + assert.Equal(t, 0, len(targets.Signed.Delegations.Keys)) + assert.Equal(t, 0, len(targets.Signed.Delegations.SuccinctRoles.KeyIDs)) + + // Add a key to succinct_roles and verify it's saved. + key := &Key{ + Type: "ed25519", + Value: KeyVal{PublicKey: "edcd0a32a07dce33f7c7873aaffbff36d20ea30787574ead335eefd337e4dacd"}, + Scheme: "ed25519", + } + err = targets.Signed.AddKey(key, "foo") + assert.NoError(t, err) + assert.Contains(t, targets.Signed.Delegations.Keys, key.id) + assert.Contains(t, targets.Signed.Delegations.SuccinctRoles.KeyIDs, key.id) + assert.Equal(t, 1, len(targets.Signed.Delegations.Keys)) + + // Try adding the same key again and verify that noting is added. + err = targets.Signed.AddKey(key, "foo") + assert.NoError(t, err) + assert.Equal(t, 1, len(targets.Signed.Delegations.Keys)) + + // Remove the key and verify it's not stored anymore. + err = targets.Signed.RevokeKey(key.id, "foo") + assert.NoError(t, err) + assert.NotContains(t, targets.Signed.Delegations.Keys, key.id) + assert.NotContains(t, targets.Signed.Delegations.SuccinctRoles.KeyIDs, key.id) + assert.Equal(t, 0, len(targets.Signed.Delegations.Keys)) + + // Try removing it again. + err = targets.Signed.RevokeKey(key.id, "foo") + assert.ErrorIs(t, err, ErrValue{fmt.Sprintf("key with id %s is not used by SuccinctRoles", key.id)}) +} + +func TestLengthAndHashValidation(t *testing.T) { + // Test metadata files' hash and length verification. + // Use timestamp to get a MetaFile object and snapshot + // for untrusted metadata file to verify. + + timestamp, err := Timestamp().FromFile(testutils.RepoDir + "/timestamp.json") + assert.NoError(t, err) + + snapshotMetafile := timestamp.Signed.Meta["snapshot.json"] + assert.NotNil(t, snapshotMetafile) + + data, err := os.ReadFile(testutils.RepoDir + "/snapshot.json") + assert.NoError(t, err) + err = snapshotMetafile.VerifyLengthHashes(data) + assert.NoError(t, err) + + // test exceptions + originalLength := snapshotMetafile.Length + snapshotMetafile.Length = 2345 + err = snapshotMetafile.VerifyLengthHashes(data) + assert.ErrorIs(t, err, ErrLengthOrHashMismatch{fmt.Sprintf("length verification failed - expected %d, got %d", 2345, originalLength)}) + + snapshotMetafile.Length = originalLength + originalHashSHA256 := snapshotMetafile.Hashes["sha256"] + snapshotMetafile.Hashes["sha256"] = []byte("incorrecthash") + err = snapshotMetafile.VerifyLengthHashes(data) + assert.ErrorIs(t, err, ErrLengthOrHashMismatch{"hash verification failed - mismatch for algorithm sha256"}) + + snapshotMetafile.Hashes["sha256"] = originalHashSHA256 + snapshotMetafile.Hashes["unsupported-alg"] = []byte("72c5cabeb3e8079545a5f4d2b067f8e35f18a0de3c2b00d3cb8d05919c19c72d") + err = snapshotMetafile.VerifyLengthHashes(data) + assert.ErrorIs(t, err, ErrLengthOrHashMismatch{"hash verification failed - unknown hashing algorithm - unsupported-alg"}) + + // test optional length and hashes + snapshotMetafile.Length = 0 + snapshotMetafile.Hashes = nil + err = snapshotMetafile.VerifyLengthHashes(data) + assert.NoError(t, err) + + // Test target files' hash and length verification + targets, err := Targets().FromFile(testutils.RepoDir + "/targets.json") + assert.NoError(t, err) + targetFile := targets.Signed.Targets["file1.txt"] + targetFileData, err := os.ReadFile(testutils.TargetsDir + "/" + targetFile.Path) + assert.NoError(t, err) + + // test exceptions + originalLength = targetFile.Length + targetFile.Length = 2345 + err = targetFile.VerifyLengthHashes(targetFileData) + assert.ErrorIs(t, err, ErrLengthOrHashMismatch{fmt.Sprintf("length verification failed - expected %d, got %d", 2345, originalLength)}) + + targetFile.Length = originalLength + targetFile.Hashes["sha256"] = []byte("incorrecthash") + err = targetFile.VerifyLengthHashes(targetFileData) + assert.ErrorIs(t, err, ErrLengthOrHashMismatch{"hash verification failed - mismatch for algorithm sha256"}) +} + +func TestTargetFileFromFile(t *testing.T) { + // Test with an existing file and valid hash algorithm + targetFileFromFile, err := TargetFile().FromFile(testutils.TargetsDir+"/file1.txt", "sha256") + assert.NoError(t, err) + targetFileData, err := os.ReadFile(testutils.TargetsDir + "/file1.txt") + assert.NoError(t, err) + err = targetFileFromFile.VerifyLengthHashes(targetFileData) + assert.NoError(t, err) + + // Test with mismatching target file data + mismatchingTargetFileData, err := os.ReadFile(testutils.TargetsDir + "/file2.txt") + assert.NoError(t, err) + err = targetFileFromFile.VerifyLengthHashes(mismatchingTargetFileData) + assert.ErrorIs(t, err, ErrLengthOrHashMismatch{"hash verification failed - mismatch for algorithm sha256"}) + + // Test with an unsupported algorithm + _, err = TargetFile().FromFile(testutils.TargetsDir+"/file1.txt", "123") + assert.ErrorIs(t, err, ErrValue{"failed generating TargetFile - unsupported hashing algorithm - 123"}) +} + +func TestTargetFileCustom(t *testing.T) { + // Test creating TargetFile and accessing custom. + targetFile := TargetFile() + customJSON := json.RawMessage([]byte(`{"foo":"bar"}`)) + targetFile.Custom = &customJSON + custom, err := targetFile.Custom.MarshalJSON() + assert.NoError(t, err) + assert.Equal(t, "{\"foo\":\"bar\"}", string(custom)) +} + +func TestTargetFileFromBytes(t *testing.T) { + data := []byte("Inline test content") + + // Test with a valid hash algorithm + targetFileFromData, err := TargetFile().FromBytes(testutils.TargetsDir+"/file1.txt", data, "sha256") + assert.NoError(t, err) + err = targetFileFromData.VerifyLengthHashes(data) + assert.NoError(t, err) + + // Test with no algorithms specified + targetFileFromDataWithNoAlg, err := TargetFile().FromBytes(testutils.TargetsDir+"/file1.txt", data) + assert.NoError(t, err) + err = targetFileFromDataWithNoAlg.VerifyLengthHashes(data) + assert.NoError(t, err) +} + +func TestIsDelegatedRole(t *testing.T) { + // Test path matches + role := &DelegatedRole{ + Name: "", + KeyIDs: []string{}, + Threshold: 1, + Terminating: false, + Paths: []string{"a/path", "otherpath", "a/path", "*/?ath"}, + } + nonMatching, err := role.IsDelegatedPath("a/non-matching-path") + assert.NoError(t, err) + assert.False(t, nonMatching) + matching, err := role.IsDelegatedPath("a/path") + assert.NoError(t, err) + assert.True(t, matching) + + // Test path hash prefix matches: sha256 sum of "a/path" is 927b0ecf9... + role = &DelegatedRole{ + Name: "", + KeyIDs: []string{}, + Threshold: 1, + Terminating: false, + PathHashPrefixes: []string{"knsOz5xYT", "other prefix", "knsOz5xYT", "knsOz", "kn"}, + } + nonMatching, err = role.IsDelegatedPath("a/non-matching-path") + assert.NoError(t, err) + assert.False(t, nonMatching) + matching, err = role.IsDelegatedPath("a/path") + assert.NoError(t, err) + assert.True(t, matching) +} + +func TestIsDelegatedRoleInSuccinctRoles(t *testing.T) { + succinctRoles := &SuccinctRoles{ + KeyIDs: []string{}, + Threshold: 1, + BitLength: 5, + NamePrefix: "bin", + } + + falseRoleNmaeExamples := []string{ + "foo", + "bin-", + "bin-s", + "bin-0t", + "bin-20", + "bin-100", + } + for _, roleName := range falseRoleNmaeExamples { + res := succinctRoles.IsDelegatedRole(roleName) + assert.False(t, res) + } + + // Delegated role name suffixes are in hex format. + trueNameExamples := []string{"bin-00", "bin-0f", "bin-1f"} + for _, roleName := range trueNameExamples { + res := succinctRoles.IsDelegatedRole(roleName) + assert.True(t, res) + } +} + +func TestGetRolesInSuccinctRoles(t *testing.T) { + succinctRoles := &SuccinctRoles{ + KeyIDs: []string{}, + Threshold: 1, + BitLength: 16, + NamePrefix: "bin", + } + // bin names are in hex format and 4 hex digits are enough to represent + // all bins between 0 and 2^16 - 1 meaning suffix_len must be 4 + expectedSuffixLength := 4 + assert.Equal(t, expectedSuffixLength, succinctRoles.GetSuffixLen()) + + allRoles := succinctRoles.GetRoles() + for binNumer, roleName := range allRoles { + // This adds zero-padding if the bin_numer is represented by a hex + // number with a length less than expected_suffix_length. + expectedBinSuffix := fmt.Sprintf("%0"+strconv.Itoa(expectedSuffixLength)+"x", binNumer) + assert.Equal(t, fmt.Sprintf("bin-%s", expectedBinSuffix), roleName) + } +} diff --git a/metadata/metadata_test.go b/metadata/metadata_test.go index a92ac611..a184d2fe 100644 --- a/metadata/metadata_test.go +++ b/metadata/metadata_test.go @@ -29,13 +29,13 @@ const TEST_REPOSITORY_DATA = "../testutils/repository_data/repository/metadata" var fixedExpire = time.Date(2030, 8, 15, 14, 30, 45, 100, time.UTC) -func getSignatureByKeyID(signatures []Signature, keyID string) HexBytes { - for _, sig := range signatures { +func getSignatureByKeyID(signatures []Signature, keyID string) (HexBytes, int) { + for i, sig := range signatures { if sig.KeyID == keyID { - return sig.Signature + return sig.Signature, i } } - return []byte{} + return []byte{}, -1 } func TestDefaultValuesRoot(t *testing.T) {