Example #1
0
func TestApplyTargetsDelegationCreateDelete(t *testing.T) {
	_, repo, cs := testutils.EmptyRepo()

	newKey, err := cs.Create("targets/level1", data.ED25519Key)
	assert.NoError(t, err)

	// create delegation
	kl := data.KeyList{newKey}
	td := &changelist.TufDelegation{
		NewThreshold: 1,
		AddKeys:      kl,
		AddPaths:     []string{"level1"},
	}

	tdJSON, err := json.Marshal(td)
	assert.NoError(t, err)

	ch := changelist.NewTufChange(
		changelist.ActionCreate,
		"targets/level1",
		changelist.TypeTargetsDelegation,
		"",
		tdJSON,
	)

	err = applyTargetsChange(repo, ch)
	assert.NoError(t, err)

	tgts := repo.Targets[data.CanonicalTargetsRole]
	assert.Len(t, tgts.Signed.Delegations.Roles, 1)
	assert.Len(t, tgts.Signed.Delegations.Keys, 1)

	_, ok := tgts.Signed.Delegations.Keys[newKey.ID()]
	assert.True(t, ok)

	role := tgts.Signed.Delegations.Roles[0]
	assert.Len(t, role.KeyIDs, 1)
	assert.Equal(t, newKey.ID(), role.KeyIDs[0])
	assert.Equal(t, "targets/level1", role.Name)
	assert.Equal(t, "level1", role.Paths[0])

	// delete delegation
	ch = changelist.NewTufChange(
		changelist.ActionDelete,
		"targets/level1",
		changelist.TypeTargetsDelegation,
		"",
		nil,
	)

	err = applyTargetsChange(repo, ch)
	assert.NoError(t, err)

	assert.Len(t, tgts.Signed.Delegations.Roles, 0)
	assert.Len(t, tgts.Signed.Delegations.Keys, 0)
}
Example #2
0
func TestApplyTargetsDelegationAlreadyExistingMergePaths(t *testing.T) {
	repo, cs, err := testutils.EmptyRepo("docker.com/notary")
	assert.NoError(t, err)

	newKey, err := cs.Create("targets/level1", data.ED25519Key)
	assert.NoError(t, err)

	// create delegation
	kl := data.KeyList{newKey}
	td := &changelist.TufDelegation{
		NewThreshold: 1,
		AddKeys:      kl,
		AddPaths:     []string{"level1"},
	}

	tdJSON, err := json.Marshal(td)
	assert.NoError(t, err)

	ch := changelist.NewTufChange(
		changelist.ActionCreate,
		"targets/level1",
		changelist.TypeTargetsDelegation,
		"",
		tdJSON,
	)

	err = applyTargetsChange(repo, ch)
	assert.NoError(t, err)
	// we have sufficient checks elsewhere we don't need to confirm that
	// creating fresh works here via more asserts.

	// Use different path for this changelist
	td.AddPaths = []string{"level2"}

	tdJSON, err = json.Marshal(td)
	assert.NoError(t, err)

	ch = changelist.NewTufChange(
		changelist.ActionCreate,
		"targets/level1",
		changelist.TypeTargetsDelegation,
		"",
		tdJSON,
	)

	// when attempting to create the same role again, check that we
	// merged with previous details
	err = applyTargetsChange(repo, ch)
	assert.NoError(t, err)
	delegation, _, err := repo.GetDelegation("targets/level1")
	assert.NoError(t, err)
	// Assert we have both paths
	assert.Contains(t, delegation.Paths, "level2")
	assert.Contains(t, delegation.Paths, "level1")
}
Example #3
0
// adds a TUF Change template to the given roles
func addChange(cl *changelist.FileChangelist, c changelist.Change, roles ...string) error {

	if len(roles) == 0 {
		roles = []string{data.CanonicalTargetsRole}
	}

	var changes []changelist.Change
	for _, role := range roles {
		role = strings.ToLower(role)

		// Ensure we can only add targets to the CanonicalTargetsRole,
		// or a Delegation role (which is <CanonicalTargetsRole>/something else)
		if role != data.CanonicalTargetsRole && !data.IsDelegation(role) {
			return data.ErrInvalidRole{
				Role:   role,
				Reason: "cannot add targets to this role",
			}
		}

		changes = append(changes, changelist.NewTufChange(
			c.Action(),
			role,
			c.Type(),
			c.Path(),
			c.Content(),
		))
	}

	for _, c := range changes {
		if err := cl.Add(c); err != nil {
			return err
		}
	}
	return nil
}
Example #4
0
// Applying a delegation whose parent doesn't exist fails.
func TestApplyTargetsDelegationParentDoesntExist(t *testing.T) {
	repo, cs, err := testutils.EmptyRepo("docker.com/notary")
	assert.NoError(t, err)

	// make sure a key exists for the previous level, so it's not a missing
	// key error, but we don't care about this key
	_, err = cs.Create("targets/level1", data.ED25519Key)
	assert.NoError(t, err)

	newKey, err := cs.Create("targets/level1/level2", data.ED25519Key)
	assert.NoError(t, err)

	// create delegation
	kl := data.KeyList{newKey}
	td := &changelist.TufDelegation{
		NewThreshold: 1,
		AddKeys:      kl,
	}

	tdJSON, err := json.Marshal(td)
	assert.NoError(t, err)

	ch := changelist.NewTufChange(
		changelist.ActionCreate,
		"targets/level1/level2",
		changelist.TypeTargetsDelegation,
		"",
		tdJSON,
	)

	err = applyTargetsChange(repo, ch)
	assert.Error(t, err)
	assert.IsType(t, data.ErrInvalidRole{}, err)
}
Example #5
0
func TestApplyTargetsDelegationCreateInvalid(t *testing.T) {
	_, repo, cs := testutils.EmptyRepo()

	newKey, err := cs.Create("targets/level1", data.ED25519Key)
	assert.NoError(t, err)

	// create delegation
	kl := data.KeyList{newKey}
	td := &changelist.TufDelegation{
		NewThreshold:        1,
		AddKeys:             kl,
		AddPaths:            []string{"level1"},
		AddPathHashPrefixes: []string{"abc"},
	}

	tdJSON, err := json.Marshal(td)
	assert.NoError(t, err)

	ch := changelist.NewTufChange(
		changelist.ActionCreate,
		"targets/level1",
		changelist.TypeTargetsDelegation,
		"",
		tdJSON,
	)

	err = applyTargetsChange(repo, ch)
	assert.Error(t, err)
}
Example #6
0
// AddDelegation creates a new changelist entry to add a delegation to the repository
// when the changelist gets applied at publish time.  This does not do any validation
// other than checking the name of the delegation to add - all that will happen
// at publish time.
func (r *NotaryRepository) AddDelegation(name string, threshold int,
	delegationKeys []data.PublicKey, paths []string) error {

	if !data.IsDelegation(name) {
		return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"}
	}

	cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
	if err != nil {
		return err
	}
	defer cl.Close()

	logrus.Debugf(`Adding delegation "%s" with threshold %d, and %d keys\n`,
		name, threshold, len(delegationKeys))

	tdJSON, err := json.Marshal(&changelist.TufDelegation{
		NewThreshold: threshold,
		AddKeys:      data.KeyList(delegationKeys),
		AddPaths:     paths,
	})
	if err != nil {
		return err
	}

	template := changelist.NewTufChange(
		changelist.ActionCreate,
		name,
		changelist.TypeTargetsDelegation,
		"", // no path
		tdJSON,
	)

	return addChange(cl, template, name)
}
Example #7
0
func (r *NotaryRepository) rootFileKeyChange(role, action string, key data.PublicKey) error {
	cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
	if err != nil {
		return err
	}
	defer cl.Close()

	kl := make(data.KeyList, 0, 1)
	kl = append(kl, key)
	meta := changelist.TufRootData{
		RoleName: role,
		Keys:     kl,
	}
	metaJSON, err := json.Marshal(meta)
	if err != nil {
		return err
	}

	c := changelist.NewTufChange(
		action,
		changelist.ScopeRoot,
		changelist.TypeRootRole,
		role,
		metaJSON,
	)
	err = cl.Add(c)
	if err != nil {
		return err
	}
	return nil
}
Example #8
0
func TestApplyTargetsDelegationInvalidJSONContent(t *testing.T) {
	repo, cs, err := testutils.EmptyRepo("docker.com/notary")
	assert.NoError(t, err)

	newKey, err := cs.Create("targets/level1", data.ED25519Key)
	assert.NoError(t, err)

	// create delegation
	kl := data.KeyList{newKey}
	td := &changelist.TufDelegation{
		NewThreshold: 1,
		AddKeys:      kl,
		AddPaths:     []string{"level1"},
	}

	tdJSON, err := json.Marshal(td)
	assert.NoError(t, err)

	ch := changelist.NewTufChange(
		changelist.ActionCreate,
		"targets/level1",
		changelist.TypeTargetsDelegation,
		"",
		tdJSON[1:],
	)

	err = applyTargetsChange(repo, ch)
	assert.Error(t, err)
}
Example #9
0
func (r *NotaryRepository) rootFileKeyChange(role, action string, key data.PublicKey) error {
	cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
	if err != nil {
		return err
	}
	defer cl.Close()

	k, ok := key.(*data.TUFKey)
	if !ok {
		return errors.New("Invalid key type found during rotation.")
	}

	meta := changelist.TufRootData{
		RoleName: role,
		Keys:     []data.TUFKey{*k},
	}
	metaJSON, err := json.Marshal(meta)
	if err != nil {
		return err
	}

	c := changelist.NewTufChange(
		action,
		changelist.ScopeRoot,
		changelist.TypeRootRole,
		role,
		metaJSON,
	)
	err = cl.Add(c)
	if err != nil {
		return err
	}
	return nil
}
Example #10
0
// RemoveDelegation creates a new changelist entry to remove a delegation from
// the repository when the changelist gets applied at publish time.
// This does not validate that the delegation exists, since one might exist
// after applying all changes.
func (r *NotaryRepository) RemoveDelegation(name string, keyIDs, paths []string, removeAll bool) error {

	if !data.IsDelegation(name) {
		return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"}
	}

	cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
	if err != nil {
		return err
	}
	defer cl.Close()

	logrus.Debugf(`Removing delegation "%s"\n`, name)
	var template *changelist.TufChange

	// We use the Delete action only for force removal, Update is used for removing individual keys and paths
	if removeAll {
		template = changelist.NewTufChange(
			changelist.ActionDelete,
			name,
			changelist.TypeTargetsDelegation,
			"",  // no path
			nil, // deleting role, no data needed
		)

	} else {
		tdJSON, err := json.Marshal(&changelist.TufDelegation{
			RemoveKeys:  keyIDs,
			RemovePaths: paths,
		})
		if err != nil {
			return err
		}

		template = changelist.NewTufChange(
			changelist.ActionUpdate,
			name,
			changelist.TypeTargetsDelegation,
			"", // no path
			tdJSON,
		)
	}

	return addChange(cl, template, name)
}
Example #11
0
func newDeleteDelegationChange(name string, content []byte) *changelist.TufChange {
	return changelist.NewTufChange(
		changelist.ActionDelete,
		name,
		changelist.TypeTargetsDelegation,
		"", // no path for delegations
		content,
	)
}
Example #12
0
// RemoveTarget creates new changelist entries to remove a target from the given
// roles in the repository when the changelist gets applied at publish time.
// If roles are unspecified, the default role is "target".
func (r *NotaryRepository) RemoveTarget(targetName string, roles ...string) error {

	cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
	if err != nil {
		return err
	}
	logrus.Debugf("Removing target \"%s\"", targetName)
	template := changelist.NewTufChange(changelist.ActionDelete, "",
		changelist.TypeTargetsTarget, targetName, nil)
	return addChange(cl, template, roles...)
}
Example #13
0
// RemoveTarget creates a new changelist entry to remove a target from the repository
// when the changelist gets applied at publish time
func (r *NotaryRepository) RemoveTarget(targetName string) error {
	cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
	if err != nil {
		return err
	}
	logrus.Debugf("Removing target \"%s\"", targetName)
	c := changelist.NewTufChange(changelist.ActionDelete, changelist.ScopeTargets, "target", targetName, nil)
	err = cl.Add(c)
	if err != nil {
		return err
	}
	return nil
}
Example #14
0
func TestApplyTargetsDelegationInvalidAction(t *testing.T) {
	_, repo, _ := testutils.EmptyRepo()

	ch := changelist.NewTufChange(
		"bad action",
		"targets/level1",
		changelist.TypeTargetsDelegation,
		"",
		nil,
	)

	err := applyTargetsChange(repo, ch)
	assert.Error(t, err)
}
Example #15
0
func TestApplyTargetsChangeInvalidType(t *testing.T) {
	_, repo, _ := testutils.EmptyRepo()

	ch := changelist.NewTufChange(
		changelist.ActionCreate,
		"targets/level1",
		"badType",
		"",
		nil,
	)

	err := applyTargetsChange(repo, ch)
	assert.Error(t, err)
}
Example #16
0
// AddTarget creates new changelist entries to add a target to the given roles
// in the repository when the changelist gets appied at publish time.
// If roles are unspecified, the default role is "targets".
func (r *NotaryRepository) AddTarget(target *Target, roles ...string) error {

	cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
	if err != nil {
		return err
	}
	defer cl.Close()
	logrus.Debugf("Adding target \"%s\" with sha256 \"%x\" and size %d bytes.\n", target.Name, target.Hashes["sha256"], target.Length)

	meta := data.FileMeta{Length: target.Length, Hashes: target.Hashes}
	metaJSON, err := json.Marshal(meta)
	if err != nil {
		return err
	}

	template := changelist.NewTufChange(
		changelist.ActionCreate, "", changelist.TypeTargetsTarget,
		target.Name, metaJSON)
	return addChange(cl, template, roles...)
}
Example #17
0
// AddTarget adds a new target to the repository, forcing a timestamps check from TUF
func (r *NotaryRepository) AddTarget(target *Target) error {
	cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
	if err != nil {
		return err
	}
	logrus.Debugf("Adding target \"%s\" with sha256 \"%x\" and size %d bytes.\n", target.Name, target.Hashes["sha256"], target.Length)

	meta := data.FileMeta{Length: target.Length, Hashes: target.Hashes}
	metaJSON, err := json.Marshal(meta)
	if err != nil {
		return err
	}

	c := changelist.NewTufChange(changelist.ActionCreate, "targets", "target", target.Name, metaJSON)
	err = cl.Add(c)
	if err != nil {
		return err
	}
	return cl.Close()
}
Example #18
0
func (r *NotaryRepository) rootFileKeyChange(cl changelist.Changelist, role, action string, key data.PublicKey) error {
	kl := make(data.KeyList, 0, 1)
	kl = append(kl, key)
	meta := changelist.TufRootData{
		RoleName: role,
		Keys:     kl,
	}
	metaJSON, err := json.Marshal(meta)
	if err != nil {
		return err
	}

	c := changelist.NewTufChange(
		action,
		changelist.ScopeRoot,
		changelist.TypeRootRole,
		role,
		metaJSON,
	)
	return cl.Add(c)
}
Example #19
0
// RemoveDelegation creates a new changelist entry to remove a delegation from
// the repository when the changelist gets applied at publish time.
// This does not validate that the delegation exists, since one might exist
// after applying all changes.
func (r *NotaryRepository) RemoveDelegation(name string) error {

	if !data.IsDelegation(name) {
		return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"}
	}

	cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
	if err != nil {
		return err
	}
	defer cl.Close()

	logrus.Debugf(`Removing delegation "%s"\n`, name)

	template := changelist.NewTufChange(
		changelist.ActionDelete,
		name,
		changelist.TypeTargetsDelegation,
		"", // no path
		nil,
	)

	return addChange(cl, template, name)
}
Example #20
0
func TestApplyTargetsDelegationCreateAlreadyExisting(t *testing.T) {
	_, repo, cs := testutils.EmptyRepo()

	newKey, err := cs.Create("targets/level1", data.ED25519Key)
	assert.NoError(t, err)

	// create delegation
	kl := data.KeyList{newKey}
	td := &changelist.TufDelegation{
		NewThreshold: 1,
		AddKeys:      kl,
		AddPaths:     []string{"level1"},
	}

	tdJSON, err := json.Marshal(td)
	assert.NoError(t, err)

	ch := changelist.NewTufChange(
		changelist.ActionCreate,
		"targets/level1",
		changelist.TypeTargetsDelegation,
		"",
		tdJSON,
	)

	err = applyTargetsChange(repo, ch)
	assert.NoError(t, err)
	// we have sufficient checks elsewhere we don't need to confirm that
	// creating fresh works here via more asserts.

	// when attempting to create the same role again, assert we receive
	// an ErrInvalidRole because an existing role can't be "created"
	err = applyTargetsChange(repo, ch)
	assert.Error(t, err)
	assert.IsType(t, data.ErrInvalidRole{}, err)
}
Example #21
0
func TestApplyTargetsDelegationCreate2Deep(t *testing.T) {
	repo, cs, err := testutils.EmptyRepo("docker.com/notary")
	assert.NoError(t, err)

	newKey, err := cs.Create("targets/level1", data.ED25519Key)
	assert.NoError(t, err)

	// create delegation
	kl := data.KeyList{newKey}
	td := &changelist.TufDelegation{
		NewThreshold: 1,
		AddKeys:      kl,
		AddPaths:     []string{"level1"},
	}

	tdJSON, err := json.Marshal(td)
	assert.NoError(t, err)

	ch := changelist.NewTufChange(
		changelist.ActionCreate,
		"targets/level1",
		changelist.TypeTargetsDelegation,
		"",
		tdJSON,
	)

	err = applyTargetsChange(repo, ch)
	assert.NoError(t, err)

	tgts := repo.Targets[data.CanonicalTargetsRole]
	assert.Len(t, tgts.Signed.Delegations.Roles, 1)
	assert.Len(t, tgts.Signed.Delegations.Keys, 1)

	_, ok := tgts.Signed.Delegations.Keys[newKey.ID()]
	assert.True(t, ok)

	role := tgts.Signed.Delegations.Roles[0]
	assert.Len(t, role.KeyIDs, 1)
	assert.Equal(t, newKey.ID(), role.KeyIDs[0])
	assert.Equal(t, "targets/level1", role.Name)
	assert.Equal(t, "level1", role.Paths[0])

	// init delegations targets file. This would be done as part of a publish
	// operation
	repo.InitTargets("targets/level1")

	td = &changelist.TufDelegation{
		NewThreshold: 1,
		AddKeys:      kl,
		AddPaths:     []string{"level1/level2"},
	}

	tdJSON, err = json.Marshal(td)
	assert.NoError(t, err)

	ch = changelist.NewTufChange(
		changelist.ActionCreate,
		"targets/level1/level2",
		changelist.TypeTargetsDelegation,
		"",
		tdJSON,
	)

	err = applyTargetsChange(repo, ch)
	assert.NoError(t, err)

	tgts = repo.Targets["targets/level1"]
	assert.Len(t, tgts.Signed.Delegations.Roles, 1)
	assert.Len(t, tgts.Signed.Delegations.Keys, 1)

	_, ok = tgts.Signed.Delegations.Keys[newKey.ID()]
	assert.True(t, ok)

	role = tgts.Signed.Delegations.Roles[0]
	assert.Len(t, role.KeyIDs, 1)
	assert.Equal(t, newKey.ID(), role.KeyIDs[0])
	assert.Equal(t, "targets/level1/level2", role.Name)
	assert.Equal(t, "level1/level2", role.Paths[0])
}
Example #22
0
func TestApplyTargetsDelegationCreateAlreadyExisting(t *testing.T) {
	repo, cs, err := testutils.EmptyRepo("docker.com/notary")
	assert.NoError(t, err)

	newKey, err := cs.Create("targets/level1", data.ED25519Key)
	assert.NoError(t, err)

	// create delegation
	kl := data.KeyList{newKey}
	td := &changelist.TufDelegation{
		NewThreshold: 1,
		AddKeys:      kl,
		AddPaths:     []string{"level1"},
	}

	tdJSON, err := json.Marshal(td)
	assert.NoError(t, err)

	ch := changelist.NewTufChange(
		changelist.ActionCreate,
		"targets/level1",
		changelist.TypeTargetsDelegation,
		"",
		tdJSON,
	)

	err = applyTargetsChange(repo, ch)
	assert.NoError(t, err)
	// we have sufficient checks elsewhere we don't need to confirm that
	// creating fresh works here via more asserts.

	extraKey, err := cs.Create("targets/level1", data.ED25519Key)
	assert.NoError(t, err)

	// create delegation
	kl = data.KeyList{extraKey}
	td = &changelist.TufDelegation{
		NewThreshold: 1,
		AddKeys:      kl,
		AddPaths:     []string{"level1"},
	}

	tdJSON, err = json.Marshal(td)
	assert.NoError(t, err)

	ch = changelist.NewTufChange(
		changelist.ActionCreate,
		"targets/level1",
		changelist.TypeTargetsDelegation,
		"",
		tdJSON,
	)

	// when attempting to create the same role again, check that we added a key
	err = applyTargetsChange(repo, ch)
	assert.NoError(t, err)
	delegation, keys, err := repo.GetDelegation("targets/level1")
	assert.NoError(t, err)
	assert.Contains(t, delegation.Paths, "level1")
	assert.Equal(t, len(delegation.KeyIDs), 2)
	for _, keyID := range delegation.KeyIDs {
		_, ok := keys[keyID]
		assert.True(t, ok)
	}
}
Example #23
0
func TestApplyTargetsDelegationCreate2SharedKey(t *testing.T) {
	repo, cs, err := testutils.EmptyRepo("docker.com/notary")
	assert.NoError(t, err)

	newKey, err := cs.Create("targets/level1", data.ED25519Key)
	assert.NoError(t, err)

	// create first delegation
	kl := data.KeyList{newKey}
	td := &changelist.TufDelegation{
		NewThreshold: 1,
		AddKeys:      kl,
		AddPaths:     []string{"level1"},
	}

	tdJSON, err := json.Marshal(td)
	assert.NoError(t, err)

	ch := changelist.NewTufChange(
		changelist.ActionCreate,
		"targets/level1",
		changelist.TypeTargetsDelegation,
		"",
		tdJSON,
	)

	err = applyTargetsChange(repo, ch)
	assert.NoError(t, err)

	// create second delegation
	kl = data.KeyList{newKey}
	td = &changelist.TufDelegation{
		NewThreshold: 1,
		AddKeys:      kl,
		AddPaths:     []string{"level2"},
	}

	tdJSON, err = json.Marshal(td)
	assert.NoError(t, err)

	ch = changelist.NewTufChange(
		changelist.ActionCreate,
		"targets/level2",
		changelist.TypeTargetsDelegation,
		"",
		tdJSON,
	)

	err = applyTargetsChange(repo, ch)
	assert.NoError(t, err)

	tgts := repo.Targets[data.CanonicalTargetsRole]
	assert.Len(t, tgts.Signed.Delegations.Roles, 2)
	assert.Len(t, tgts.Signed.Delegations.Keys, 1)

	role1 := tgts.Signed.Delegations.Roles[0]
	assert.Len(t, role1.KeyIDs, 1)
	assert.Equal(t, newKey.ID(), role1.KeyIDs[0])
	assert.Equal(t, "targets/level1", role1.Name)
	assert.Equal(t, "level1", role1.Paths[0])

	role2 := tgts.Signed.Delegations.Roles[1]
	assert.Len(t, role2.KeyIDs, 1)
	assert.Equal(t, newKey.ID(), role2.KeyIDs[0])
	assert.Equal(t, "targets/level2", role2.Name)
	assert.Equal(t, "level2", role2.Paths[0])

	// delete one delegation, ensure shared key remains
	td = &changelist.TufDelegation{
		RemoveKeys: []string{newKey.ID()},
	}
	tdJSON, err = json.Marshal(td)
	assert.NoError(t, err)
	ch = changelist.NewTufChange(
		changelist.ActionDelete,
		"targets/level1",
		changelist.TypeTargetsDelegation,
		"",
		tdJSON,
	)

	err = applyTargetsChange(repo, ch)
	assert.NoError(t, err)

	assert.Len(t, tgts.Signed.Delegations.Roles, 1)
	assert.Len(t, tgts.Signed.Delegations.Keys, 1)

	// delete other delegation, ensure key cleaned up
	ch = changelist.NewTufChange(
		changelist.ActionDelete,
		"targets/level2",
		changelist.TypeTargetsDelegation,
		"",
		tdJSON,
	)

	err = applyTargetsChange(repo, ch)
	assert.NoError(t, err)

	assert.Len(t, tgts.Signed.Delegations.Roles, 0)
	assert.Len(t, tgts.Signed.Delegations.Keys, 0)
}