add GetBaseRole and GetDelegationRole with path validation

Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
This commit is contained in:
Riyaz Faizullabhoy 2016-02-10 15:13:32 -08:00
parent a754574b85
commit d379f9918c
3 changed files with 342 additions and 10 deletions

View File

@ -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
}

View File

@ -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()

View File

@ -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)
}