From d379f9918c9f45f37dc0b6ae584a5c8ff624d904 Mon Sep 17 00:00:00 2001 From: Riyaz Faizullabhoy Date: Wed, 10 Feb 2016 15:13:32 -0800 Subject: [PATCH] add GetBaseRole and GetDelegationRole with path validation Signed-off-by: Riyaz Faizullabhoy --- tuf/data/roles.go | 20 ++--- tuf/tuf.go | 132 ++++++++++++++++++++++++++++++ tuf/tuf_test.go | 200 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 342 insertions(+), 10 deletions(-) diff --git a/tuf/data/roles.go b/tuf/data/roles.go index 432ed82fae..274f75b90c 100644 --- a/tuf/data/roles.go +++ b/tuf/data/roles.go @@ -120,12 +120,12 @@ type BaseRole struct { Threshold int `json:"threshold"` } -// Returns true for BaseRole +// IsBaseRole returns true for BaseRole func (b BaseRole) IsBaseRole() bool { return true } -// Returns false for BaseRole +// IsDelegationRole returns false for BaseRole func (b BaseRole) IsDelegationRole() bool { return false } @@ -142,7 +142,7 @@ func (b BaseRole) GetThreshold() int { // ListKeys retrieves the public keys valid for this role func (b BaseRole) ListKeys() KeyList { - keys := make(KeyList) + keys := KeyList{} for _, key := range b.Keys { keys = append(keys, key) } @@ -151,7 +151,7 @@ func (b BaseRole) ListKeys() KeyList { // ListKeyIDs retrieves the list of key IDs valid for this role func (b BaseRole) ListKeyIDs() []string { - keyIDs := make([]string) + keyIDs := []string{} for id := range b.Keys { keyIDs = append(keyIDs, id) } @@ -163,7 +163,7 @@ func (b BaseRole) ListPaths() ([]string, error) { return nil, fmt.Errorf("%s is not a delegation role", b.Name) } -// ListPaths returns an error for non-delegations +// ListPathHashPrefixes returns an error for non-delegations func (b BaseRole) ListPathHashPrefixes() ([]string, error) { return nil, fmt.Errorf("%s is not a delegation role", b.Name) } @@ -175,12 +175,12 @@ type DelegationRole struct { PathHashPrefixes []string `json:"path_hash_prefixes,omitempty"` } -// Returns false for DelegationRole +// IsBaseRole returns false for DelegationRole func (d DelegationRole) IsBaseRole() bool { return false } -// Returns true for DelegationRole +// IsDelegationRole returns true for DelegationRole func (d DelegationRole) IsDelegationRole() bool { return true } @@ -197,7 +197,7 @@ func (d DelegationRole) GetThreshold() int { // ListKeys retrieves the public keys valid for this role func (d DelegationRole) ListKeys() KeyList { - keys := make(KeyList) + keys := KeyList{} for _, key := range d.Keys { keys = append(keys, key) } @@ -206,7 +206,7 @@ func (d DelegationRole) ListKeys() KeyList { // ListKeyIDs retrieves the list of key IDs valid for this role func (d DelegationRole) ListKeyIDs() []string { - keyIDs := make([]string) + keyIDs := []string{} for id := range d.Keys { keyIDs = append(keyIDs, id) } @@ -218,7 +218,7 @@ func (d DelegationRole) ListPaths() ([]string, error) { return d.Paths, nil } -// ListPaths lists the paths of this rol +// ListPathHashPrefixes lists the paths of this role func (d DelegationRole) ListPathHashPrefixes() ([]string, error) { return d.PathHashPrefixes, nil } diff --git a/tuf/tuf.go b/tuf/tuf.go index 644c68b4e5..6e0c0c2f65 100644 --- a/tuf/tuf.go +++ b/tuf/tuf.go @@ -173,6 +173,138 @@ func (tr *Repo) RemoveBaseKeys(role string, keyIDs ...string) error { return nil } +// GetBaseRole gets a base role from this repo's metadata +func (tr *Repo) GetBaseRole(name string) (data.BaseRole, error) { + if !data.IsBaseRole(name) { + return data.BaseRole{}, data.ErrInvalidRole{Role: name, Reason: "invalid base role name"} + } + if tr.Root == nil { + return data.BaseRole{}, ErrNotLoaded{data.CanonicalRootRole} + } + roleData, ok := tr.Root.Signed.Roles[name] + if !ok { + return data.BaseRole{}, ErrNotLoaded{name} + } + // Get all public keys for the base role from TUF metadata + keyIDs := roleData.KeyIDs + pubKeys := make(map[string]data.PublicKey) + for _, keyID := range keyIDs { + pubKey, ok := tr.Root.Signed.Keys[keyID] + if !ok { + return data.BaseRole{}, fmt.Errorf("key with ID %s, specified as a signing key for role %s, was not found in root metadata", keyID, name) + } + pubKeys[keyID] = pubKey + } + + return data.BaseRole{ + Name: name, + Keys: pubKeys, + Threshold: roleData.Threshold, + }, nil +} + +// GetDelegationRole gets a delegation role from this repo's metadata, walking from the targets role down to the delegation itself +func (tr *Repo) GetDelegationRole(name string) (data.DelegationRole, error) { + if !data.IsDelegation(name) { + return data.DelegationRole{}, data.ErrInvalidRole{Role: name, Reason: "invalid delegation name"} + } + if tr.Root == nil { + return data.DelegationRole{}, ErrNotLoaded{data.CanonicalRootRole} + } + _, ok := tr.Root.Signed.Roles[data.CanonicalTargetsRole] + if !ok { + return data.DelegationRole{}, ErrNotLoaded{data.CanonicalTargetsRole} + } + // Traverse target metadata, down to delegation itself + // Get all public keys for the base role from TUF metadata + signedTargetData, ok := tr.Targets[data.CanonicalTargetsRole] + if !ok { + return data.DelegationRole{}, fmt.Errorf("TUF data for targets does not exist in targets metadata", name) + } + + // Keep track of paths and path hash prefixes to validate as we traverse the delegations tree, targets implicitly has "" + whiteListedPaths := []string{""} + whiteListedPathHashes := []string{""} + + // Start with top level roles in targets + delegationRoles := signedTargetData.Signed.Delegations.Roles + var foundRole *data.Role + for len(delegationRoles) > 0 { + delgRole := delegationRoles[0] + delegationRoles = delegationRoles[1:] + + // If this role is delegated above or is our desired role, validate paths and traverse its child roles + if delgRole.Name == name || strings.HasPrefix(name, delgRole.Name+"/") { + if okPaths := validateDelegationPathPrefixes(whiteListedPaths, delgRole.Paths); !okPaths { + return data.DelegationRole{}, fmt.Errorf("Invalid paths specified for delegation %s", delgRole.Name) + } + whiteListedPaths = delgRole.Paths + if okPathHashPrefixes := validateDelegationPathPrefixes(whiteListedPathHashes, delgRole.PathHashPrefixes); !okPathHashPrefixes { + return data.DelegationRole{}, fmt.Errorf("Invalid path hash prefixes specified for delegation %s", delgRole.Name) + } + + // If we found the role, we can exit the loop + if delgRole.Name == name { + foundRole = delgRole + break + } + + // If this is a parent role, keep traversing + whiteListedPathHashes = delgRole.PathHashPrefixes + delegationRoles = append(delegationRoles, tr.Targets[delgRole.Name].Signed.Delegations.Roles...) + } + } + + if foundRole == nil { + return data.DelegationRole{}, fmt.Errorf("could not find valid delegation for role %s", name) + } + + pubKeys := make(map[string]data.PublicKey) + parentData, ok := tr.Targets[path.Dir(foundRole.Name)] + if !ok { + return data.DelegationRole{}, fmt.Errorf("delegation parent data for %s does not exist in targets metadata", foundRole.Name) + } + for _, keyID := range foundRole.KeyIDs { + pubKey, ok := parentData.Signed.Delegations.Keys[keyID] + if !ok { + return data.DelegationRole{}, fmt.Errorf("delegation %s does not exist in targets metadata", foundRole.Name) + } + pubKeys[keyID] = pubKey + } + + return data.DelegationRole{ + BaseRole: data.BaseRole{ + Name: name, + Keys: pubKeys, + Threshold: foundRole.Threshold, + }, + Paths: foundRole.Paths, + PathHashPrefixes: foundRole.PathHashPrefixes, + }, nil +} + +// Returns true if the delegationPaths are prefixed by parentPaths +func validateDelegationPathPrefixes(parentPaths, delegationPaths []string) bool { + if len(delegationPaths) == 0 { + return true + } + // Validate each individual delegation path + for _, delgPath := range delegationPaths { + isPrefixed := false + for _, parentPath := range parentPaths { + if strings.HasPrefix(delgPath, parentPath) { + isPrefixed = true + break + } + } + // If the delegation path did not match prefix against any parent path, it is not valid + if !isPrefixed { + return false + } + } + return true +} + // GetAllLoadedRoles returns a list of all role entries loaded in this TUF repo, could be empty func (tr *Repo) GetAllLoadedRoles() []*data.Role { return tr.keysDB.GetAllRoles() diff --git a/tuf/tuf_test.go b/tuf/tuf_test.go index d04ca6c110..3f8579a686 100644 --- a/tuf/tuf_test.go +++ b/tuf/tuf_test.go @@ -953,3 +953,203 @@ func TestGetAllRoles(t *testing.T) { roles = repo.GetAllLoadedRoles() assert.Len(t, roles, 0) } + +func TestGetBaseRoles(t *testing.T) { + ed25519 := signed.NewEd25519() + keyDB := keys.NewDB() + repo := initRepo(t, ed25519, keyDB) + + // After we init, we get the base roles + for _, role := range data.BaseRoles { + baseRole, err := repo.GetBaseRole(role) + assert.NoError(t, err) + + assert.Equal(t, role, baseRole.GetName()) + keyIDs := repo.cryptoService.ListKeys(role) + for _, keyID := range keyIDs { + _, ok := baseRole.Keys[keyID] + assert.True(t, ok) + assert.Contains(t, baseRole.ListKeyIDs(), keyID) + } + // initRepo should set all key thresholds to 1 + assert.Equal(t, 1, baseRole.GetThreshold()) + assert.False(t, baseRole.IsDelegationRole()) + assert.True(t, baseRole.IsBaseRole()) + _, err = baseRole.ListPathHashPrefixes() + assert.Error(t, err) + _, err = baseRole.ListPaths() + assert.Error(t, err) + } +} + +func TestGetBaseRolesInvalidName(t *testing.T) { + ed25519 := signed.NewEd25519() + keyDB := keys.NewDB() + repo := initRepo(t, ed25519, keyDB) + + _, err := repo.GetBaseRole("invalid") + assert.Error(t, err) + + _, err = repo.GetBaseRole("targets/delegation") + assert.Error(t, err) +} + +func TestGetDelegationValidRoles(t *testing.T) { + ed25519 := signed.NewEd25519() + keyDB := keys.NewDB() + repo := initRepo(t, ed25519, keyDB) + + testKey1, err := ed25519.Create("targets/test", data.ED25519Key) + assert.NoError(t, err) + role, err := data.NewRole( + "targets/test", 1, []string{testKey1.ID()}, []string{"path", "anotherpath"}, []string{}) + assert.NoError(t, err) + + err = repo.UpdateDelegations(role, data.KeyList{testKey1}) + assert.NoError(t, err) + + delgRole, err := repo.GetDelegationRole("targets/test") + assert.NoError(t, err) + assert.True(t, delgRole.IsDelegationRole()) + assert.False(t, delgRole.IsBaseRole()) + assert.Equal(t, "targets/test", delgRole.GetName()) + assert.Equal(t, 1, delgRole.GetThreshold()) + assert.Equal(t, []string{testKey1.ID()}, delgRole.ListKeyIDs()) + delgPaths, err := delgRole.ListPaths() + assert.NoError(t, err) + delgPathPrefixes, err := delgRole.ListPathHashPrefixes() + assert.NoError(t, err) + assert.Empty(t, delgPathPrefixes) + assert.Equal(t, []string{"path", "anotherpath"}, delgPaths) + assert.Equal(t, testKey1, delgRole.Keys[testKey1.ID()]) + + testKey2, err := ed25519.Create("targets/a", data.ED25519Key) + assert.NoError(t, err) + role, err = data.NewRole( + "targets/a", 1, []string{testKey2.ID()}, []string{""}, []string{}) + assert.NoError(t, err) + + err = repo.UpdateDelegations(role, data.KeyList{testKey2}) + assert.NoError(t, err) + + delgRole, err = repo.GetDelegationRole("targets/a") + assert.NoError(t, err) + assert.True(t, delgRole.IsDelegationRole()) + assert.False(t, delgRole.IsBaseRole()) + assert.Equal(t, "targets/a", delgRole.GetName()) + assert.Equal(t, 1, delgRole.GetThreshold()) + assert.Equal(t, []string{testKey2.ID()}, delgRole.ListKeyIDs()) + delgPaths, err = delgRole.ListPaths() + assert.NoError(t, err) + assert.Equal(t, []string{""}, delgPaths) + delgPathPrefixes, err = delgRole.ListPathHashPrefixes() + assert.NoError(t, err) + assert.Empty(t, delgPathPrefixes) + assert.Equal(t, testKey2, delgRole.Keys[testKey2.ID()]) + + testKey3, err := ed25519.Create("targets/test/b", data.ED25519Key) + assert.NoError(t, err) + role, err = data.NewRole( + "targets/test/b", 1, []string{testKey3.ID()}, []string{"path/subpath", "anotherpath"}, []string{}) + assert.NoError(t, err) + + err = repo.UpdateDelegations(role, data.KeyList{testKey3}) + assert.NoError(t, err) + + delgRole, err = repo.GetDelegationRole("targets/test/b") + assert.NoError(t, err) + assert.True(t, delgRole.IsDelegationRole()) + assert.False(t, delgRole.IsBaseRole()) + assert.Equal(t, "targets/test/b", delgRole.GetName()) + assert.Equal(t, 1, delgRole.GetThreshold()) + assert.Equal(t, []string{testKey3.ID()}, delgRole.ListKeyIDs()) + delgPaths, err = delgRole.ListPaths() + assert.NoError(t, err) + assert.Equal(t, []string{"path/subpath", "anotherpath"}, delgPaths) + delgPathPrefixes, err = delgRole.ListPathHashPrefixes() + assert.NoError(t, err) + assert.Empty(t, delgPathPrefixes) + assert.Equal(t, testKey3, delgRole.Keys[testKey3.ID()]) + + testKey4, err := ed25519.Create("targets/test/c", data.ED25519Key) + assert.NoError(t, err) + // Try adding empty paths, ensure this is valid + role, err = data.NewRole( + "targets/test/c", 1, []string{testKey4.ID()}, []string{}, []string{}) + assert.NoError(t, err) + + err = repo.UpdateDelegations(role, data.KeyList{testKey3}) + assert.NoError(t, err) +} + +func TestGetDelegationRolesInvalidName(t *testing.T) { + ed25519 := signed.NewEd25519() + keyDB := keys.NewDB() + repo := initRepo(t, ed25519, keyDB) + + _, err := repo.GetDelegationRole("invalid") + assert.Error(t, err) + + for _, role := range data.BaseRoles { + _, err = repo.GetDelegationRole(role) + assert.Error(t, err) + } +} + +func TestGetDelegationRolesInvalidPaths(t *testing.T) { + ed25519 := signed.NewEd25519() + keyDB := keys.NewDB() + repo := initRepo(t, ed25519, keyDB) + + testKey1, err := ed25519.Create("targets/test", data.ED25519Key) + assert.NoError(t, err) + role, err := data.NewRole( + "targets/test", 1, []string{testKey1.ID()}, []string{"path", "anotherpath"}, []string{}) + assert.NoError(t, err) + + err = repo.UpdateDelegations(role, data.KeyList{testKey1}) + assert.NoError(t, err) + + testKey2, err := ed25519.Create("targets/test/b", data.ED25519Key) + assert.NoError(t, err) + // Now we add a delegation with a path that is not prefixed by its parent delegation + role, err = data.NewRole( + "targets/test/b", 1, []string{testKey2.ID()}, []string{"invalidpath"}, []string{}) + assert.NoError(t, err) + + err = repo.UpdateDelegations(role, data.KeyList{testKey2}) + assert.NoError(t, err) + + // Getting this delegation should fail path verification + _, err = repo.GetDelegationRole("targets/test/b") + assert.Error(t, err) +} + +func TestGetDelegationRolesInvalidPathHashPrefix(t *testing.T) { + ed25519 := signed.NewEd25519() + keyDB := keys.NewDB() + repo := initRepo(t, ed25519, keyDB) + + testKey1, err := ed25519.Create("targets/test", data.ED25519Key) + assert.NoError(t, err) + role, err := data.NewRole( + "targets/test", 1, []string{testKey1.ID()}, []string{}, []string{"pathhash", "anotherpathhash"}) + assert.NoError(t, err) + + err = repo.UpdateDelegations(role, data.KeyList{testKey1}) + assert.NoError(t, err) + + testKey2, err := ed25519.Create("targets/test/b", data.ED25519Key) + assert.NoError(t, err) + // Now we add a delegation with a path that is not prefixed by its parent delegation + role, err = data.NewRole( + "targets/test/b", 1, []string{testKey2.ID()}, []string{}, []string{"invalidpathhash"}) + assert.NoError(t, err) + + err = repo.UpdateDelegations(role, data.KeyList{testKey2}) + assert.NoError(t, err) + + // Getting this delegation should fail path verification + _, err = repo.GetDelegationRole("targets/test/b") + assert.Error(t, err) +}