package tuf

import (
	"bytes"
	"crypto"
	"crypto/elliptic"
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"os"
	"path"
	"path/filepath"
	"reflect"
	"sort"
	"strings"
	"testing"
	"time"

	"github.com/secure-systems-lab/go-securesystemslib/cjson"
	"github.com/secure-systems-lab/go-securesystemslib/encrypted"
	"github.com/theupdateframework/go-tuf/data"
	"github.com/theupdateframework/go-tuf/internal/sets"
	"github.com/theupdateframework/go-tuf/pkg/keys"
	"github.com/theupdateframework/go-tuf/pkg/targets"
	"github.com/theupdateframework/go-tuf/util"
	"github.com/theupdateframework/go-tuf/verify"
	"golang.org/x/crypto/ed25519"
	. "gopkg.in/check.v1"
)

// Hook up gocheck into the "go test" runner.
func Test(t *testing.T) { TestingT(t) }

type RepoSuite struct{}

var _ = Suite(&RepoSuite{})

func (RepoSuite) TestNewRepo(c *C) {
	testNewRepo(c, NewRepo)
}

func (RepoSuite) TestNewRepoIndent(c *C) {
	testNewRepo(c, func(local LocalStore, hashAlgorithms ...string) (*Repo, error) {
		return NewRepoIndent(local, "", "\t")
	})
}

// UniqueKeys returns the unique keys for each associated role.
// We might have multiple key IDs that correspond to the same key.
func UniqueKeys(r *data.Root) map[string][]*data.PublicKey {
	keysByRole := make(map[string][]*data.PublicKey)
	for name, role := range r.Roles {
		seen := make(map[string]struct{})
		roleKeys := []*data.PublicKey{}
		for _, id := range role.KeyIDs {
			// Double-check that there is actually a key with that ID.
			if key, ok := r.Keys[id]; ok {
				verifier, err := keys.GetVerifier(key)
				if err != nil {
					continue
				}
				val := verifier.Public()
				if _, ok := seen[val]; ok {
					continue
				}
				seen[val] = struct{}{}
				roleKeys = append(roleKeys, key)
			}
		}
		keysByRole[name] = roleKeys
	}
	return keysByRole
}

// AssertNumUniqueKeys verifies that the number of unique root keys for a given role is as expected.
func (*RepoSuite) assertNumUniqueKeys(c *C, root *data.Root, role string, num int) {
	c.Assert(UniqueKeys(root)[role], HasLen, num)
}

func testNewRepo(c *C, newRepo func(local LocalStore, hashAlgorithms ...string) (*Repo, error)) {
	meta := map[string]json.RawMessage{
		"root.json": []byte(`{
		  "signed": {
		    "_type": "root",
		    "version": 1,
		    "expires": "2015-12-26T03:26:55.821520874Z",
		    "keys": {},
		    "roles": {}
		  },
		  "signatures": []
		}`),
		"targets.json": []byte(`{
		  "signed": {
		    "_type": "targets",
		    "version": 1,
		    "expires": "2015-03-26T03:26:55.82155686Z",
		    "targets": {}
		  },
		  "signatures": []
		}`),
		"snapshot.json": []byte(`{
		  "signed": {
		    "_type": "snapshot",
		    "version": 1,
		    "expires": "2015-01-02T03:26:55.821585981Z",
		    "meta": {}
		  },
		  "signatures": []
		}`),
		"timestamp.json": []byte(`{
		  "signed": {
		    "_type": "timestamp",
		    "version": 1,
		    "expires": "2014-12-27T03:26:55.821599702Z",
		    "meta": {}
		  },
		  "signatures": []
		}`),
	}
	local := MemoryStore(meta, nil)
	r, err := newRepo(local)
	c.Assert(err, IsNil)

	root, err := r.root()
	c.Assert(err, IsNil)
	c.Assert(root.Type, Equals, "root")
	c.Assert(root.Version, Equals, int64(1))
	c.Assert(root.Keys, NotNil)
	c.Assert(root.Keys, HasLen, 0)

	targets, err := r.topLevelTargets()
	c.Assert(err, IsNil)
	c.Assert(targets.Type, Equals, "targets")
	c.Assert(targets.Version, Equals, int64(1))
	c.Assert(targets.Targets, NotNil)
	c.Assert(targets.Targets, HasLen, 0)

	snapshot, err := r.snapshot()
	c.Assert(err, IsNil)
	c.Assert(snapshot.Type, Equals, "snapshot")
	c.Assert(snapshot.Version, Equals, int64(1))
	c.Assert(snapshot.Meta, NotNil)
	c.Assert(snapshot.Meta, HasLen, 0)

	timestamp, err := r.timestamp()
	c.Assert(err, IsNil)
	c.Assert(timestamp.Type, Equals, "timestamp")
	c.Assert(timestamp.Version, Equals, int64(1))
	c.Assert(timestamp.Meta, NotNil)
	c.Assert(timestamp.Meta, HasLen, 0)
}

func (rs *RepoSuite) TestInit(c *C) {
	local := MemoryStore(
		make(map[string]json.RawMessage),
		map[string][]byte{"foo.txt": []byte("foo")},
	)
	r, err := NewRepo(local)
	c.Assert(err, IsNil)

	// Init() sets root.ConsistentSnapshot
	for _, v := range []bool{true, false} {
		c.Assert(r.Init(v), IsNil)
		root, err := r.root()
		c.Assert(err, IsNil)
		c.Assert(root.ConsistentSnapshot, Equals, v)
	}

	// Add a target.
	generateAndAddPrivateKey(c, r, "targets")
	c.Assert(r.AddTarget("foo.txt", nil), IsNil)

	// Init() fails if targets have been added
	c.Assert(r.Init(true), Equals, ErrInitNotAllowed)
}

func genKey(c *C, r *Repo, role string) []string {
	keyids, err := r.GenKey(role)
	c.Assert(err, IsNil)
	c.Assert(len(keyids) > 0, Equals, true)
	return keyids
}

func (rs *RepoSuite) TestGenKey(c *C) {
	local := MemoryStore(make(map[string]json.RawMessage), nil)
	r, err := NewRepo(local)
	c.Assert(err, IsNil)

	// generate a key for an unknown role
	_, err = r.GenKey("foo")
	c.Assert(err, Equals, ErrInvalidRole{"foo", "only support adding keys for top-level roles"})

	// generate a root key
	ids := genKey(c, r, "root")

	// check root metadata is correct
	root, err := r.root()
	c.Assert(err, IsNil)
	c.Assert(root.Roles, NotNil)
	c.Assert(root.Roles, HasLen, 1)
	rs.assertNumUniqueKeys(c, root, "root", 1)
	rootRole, ok := root.Roles["root"]
	if !ok {
		c.Fatal("missing root role")
	}
	c.Assert(rootRole.KeyIDs, HasLen, 1)
	c.Assert(rootRole.KeyIDs, DeepEquals, ids)
	for _, keyID := range ids {
		k, ok := root.Keys[keyID]
		if !ok {
			c.Fatal("missing key")
		}
		c.Assert(k.IDs(), DeepEquals, ids)
		pk, err := keys.GetVerifier(k)
		c.Assert(err, IsNil)
		c.Assert(pk.Public(), HasLen, ed25519.PublicKeySize)
	}

	// check root key + role are in db
	db, err := r.topLevelKeysDB()
	c.Assert(err, IsNil)
	for _, keyID := range ids {
		rootKey, err := db.GetVerifier(keyID)
		c.Assert(err, IsNil)
		c.Assert(rootKey.MarshalPublicKey().IDs(), DeepEquals, ids)
		role := db.GetRole("root")
		c.Assert(role.KeyIDs, DeepEquals, sets.StringSliceToSet(ids))

		// check the key was saved correctly
		localKeys, err := local.GetSigners("root")
		c.Assert(err, IsNil)
		c.Assert(localKeys, HasLen, 1)
		c.Assert(localKeys[0].PublicData().IDs(), DeepEquals, ids)

		// check RootKeys() is correct
		rootKeys, err := r.RootKeys()
		c.Assert(err, IsNil)
		c.Assert(rootKeys, HasLen, 1)
		c.Assert(rootKeys[0].IDs(), DeepEquals, rootKey.MarshalPublicKey().IDs())
		pk, err := keys.GetVerifier(rootKeys[0])
		c.Assert(err, IsNil)
		c.Assert(pk.Public(), DeepEquals, rootKey.Public())
	}

	rootKey, err := db.GetVerifier(ids[0])
	c.Assert(err, IsNil)

	// generate two targets keys
	genKey(c, r, "targets")
	genKey(c, r, "targets")

	// check root metadata is correct
	root, err = r.root()
	c.Assert(err, IsNil)
	c.Assert(root.Roles, HasLen, 2)
	rs.assertNumUniqueKeys(c, root, "root", 1)
	rs.assertNumUniqueKeys(c, root, "targets", 2)
	targetsRole, ok := root.Roles["targets"]
	if !ok {
		c.Fatal("missing targets role")
	}
	c.Assert(targetsRole.KeyIDs, HasLen, 2)
	targetKeyIDs := make(map[string]struct{}, 2)
	db, err = r.topLevelKeysDB()
	c.Assert(err, IsNil)
	for _, id := range targetsRole.KeyIDs {
		targetKeyIDs[id] = struct{}{}
		_, ok = root.Keys[id]
		if !ok {
			c.Fatal("missing key")
		}
		verifier, err := db.GetVerifier(id)
		c.Assert(err, IsNil)
		c.Assert(verifier.MarshalPublicKey().ContainsID(id), Equals, true)
	}
	role := db.GetRole("targets")
	c.Assert(role.KeyIDs, DeepEquals, targetKeyIDs)

	// check RootKeys() is unchanged
	rootKeys, err := r.RootKeys()
	c.Assert(err, IsNil)
	c.Assert(rootKeys, HasLen, 1)
	c.Assert(rootKeys[0].IDs(), DeepEquals, rootKey.MarshalPublicKey().IDs())

	// check the keys were saved correctly
	localKeys, err := local.GetSigners("targets")
	c.Assert(err, IsNil)
	c.Assert(localKeys, HasLen, 2)
	for _, key := range localKeys {
		found := false
		for _, id := range targetsRole.KeyIDs {
			if key.PublicData().ContainsID(id) {
				found = true
				break
			}
		}
		if !found {
			c.Fatal("missing key")
		}
	}

	// check root.json got staged
	meta, err := local.GetMeta()
	c.Assert(err, IsNil)
	rootJSON, ok := meta["root.json"]
	if !ok {
		c.Fatal("missing root metadata")
	}
	s := &data.Signed{}
	c.Assert(json.Unmarshal(rootJSON, s), IsNil)
	stagedRoot := &data.Root{}
	c.Assert(json.Unmarshal(s.Signed, stagedRoot), IsNil)
	c.Assert(stagedRoot.Type, Equals, root.Type)
	c.Assert(stagedRoot.Version, Equals, root.Version)
	c.Assert(stagedRoot.Expires.UnixNano(), Equals, root.Expires.UnixNano())

	// make sure both root and stagedRoot have evaluated IDs(), otherwise
	// DeepEquals will fail because those values might not have been
	// computed yet.
	for _, key := range root.Keys {
		key.IDs()
	}
	for _, key := range stagedRoot.Keys {
		key.IDs()
	}
	c.Assert(stagedRoot.Keys, DeepEquals, root.Keys)
	c.Assert(stagedRoot.Roles, DeepEquals, root.Roles)
}

func addPrivateKey(c *C, r *Repo, role string, key keys.Signer) []string {
	err := r.AddPrivateKey(role, key)
	c.Assert(err, IsNil)
	keyids := key.PublicData().IDs()
	c.Assert(len(keyids) > 0, Equals, true)
	return keyids
}

func generateAndAddPrivateKey(c *C, r *Repo, role string) []string {
	signer, err := keys.GenerateEd25519Key()
	c.Assert(err, IsNil)
	return addPrivateKey(c, r, role, signer)
}

func (rs *RepoSuite) TestAddPrivateKey(c *C) {
	local := MemoryStore(make(map[string]json.RawMessage), nil)
	r, err := NewRepo(local)
	c.Assert(err, IsNil)

	// generate a key for an unknown role
	signer, err := keys.GenerateEd25519Key()
	c.Assert(err, IsNil)
	err = r.AddPrivateKey("foo", signer)
	c.Assert(err, Equals, ErrInvalidRole{"foo", "only support adding keys for top-level roles"})

	// add a root key
	ids := addPrivateKey(c, r, "root", signer)

	// check root metadata is correct
	root, err := r.root()
	c.Assert(err, IsNil)
	c.Assert(root.Version, Equals, int64(1))
	c.Assert(root.Roles, NotNil)
	c.Assert(root.Roles, HasLen, 1)
	rs.assertNumUniqueKeys(c, root, "root", 1)
	rootRole, ok := root.Roles["root"]
	if !ok {
		c.Fatal("missing root role")
	}
	c.Assert(rootRole.KeyIDs, HasLen, 1)
	c.Assert(rootRole.KeyIDs, DeepEquals, ids)
	for _, keyID := range ids {
		k, ok := root.Keys[keyID]
		if !ok {
			c.Fatalf("missing key %s", keyID)
		}
		c.Assert(k.IDs(), DeepEquals, ids)
		pk, err := keys.GetVerifier(k)
		c.Assert(err, IsNil)
		c.Assert(pk.Public(), HasLen, ed25519.PublicKeySize)
	}

	// check root key + role are in db
	db, err := r.topLevelKeysDB()
	c.Assert(err, IsNil)
	for _, keyID := range ids {
		rootKey, err := db.GetVerifier(keyID)
		c.Assert(err, IsNil)
		c.Assert(rootKey.MarshalPublicKey().IDs(), DeepEquals, ids)
		role := db.GetRole("root")
		c.Assert(role.KeyIDs, DeepEquals, sets.StringSliceToSet(ids))

		// check the key was saved correctly
		localKeys, err := local.GetSigners("root")
		c.Assert(err, IsNil)
		c.Assert(localKeys, HasLen, 1)
		c.Assert(localKeys[0].PublicData().IDs(), DeepEquals, ids)

		// check RootKeys() is correct
		rootKeys, err := r.RootKeys()
		c.Assert(err, IsNil)
		c.Assert(rootKeys, HasLen, 1)
		c.Assert(rootKeys[0].IDs(), DeepEquals, rootKey.MarshalPublicKey().IDs())
		pk, err := keys.GetVerifier(rootKeys[0])
		c.Assert(err, IsNil)
		c.Assert(pk.Public(), DeepEquals, rootKey.Public())
	}

	rootKey, err := db.GetVerifier(ids[0])
	c.Assert(err, IsNil)

	// generate two targets keys
	generateAndAddPrivateKey(c, r, "targets")
	generateAndAddPrivateKey(c, r, "targets")

	// check root metadata is correct
	root, err = r.root()
	c.Assert(err, IsNil)
	c.Assert(root.Roles, HasLen, 2)
	rs.assertNumUniqueKeys(c, root, "root", 1)
	rs.assertNumUniqueKeys(c, root, "targets", 2)
	targetsRole, ok := root.Roles["targets"]
	if !ok {
		c.Fatal("missing targets role")
	}
	c.Assert(targetsRole.KeyIDs, HasLen, 2)
	targetKeyIDs := make(map[string]struct{}, 2)
	db, err = r.topLevelKeysDB()
	c.Assert(err, IsNil)
	for _, id := range targetsRole.KeyIDs {
		targetKeyIDs[id] = struct{}{}
		_, ok = root.Keys[id]
		if !ok {
			c.Fatal("missing key")
		}
		verifier, err := db.GetVerifier(id)
		c.Assert(err, IsNil)
		c.Assert(verifier.MarshalPublicKey().ContainsID(id), Equals, true)
	}
	role := db.GetRole("targets")
	c.Assert(role.KeyIDs, DeepEquals, targetKeyIDs)

	// check RootKeys() is unchanged
	rootKeys, err := r.RootKeys()
	c.Assert(err, IsNil)
	c.Assert(rootKeys, HasLen, 1)
	c.Assert(rootKeys[0].IDs(), DeepEquals, rootKey.MarshalPublicKey().IDs())

	// check the keys were saved correctly
	localKeys, err := local.GetSigners("targets")
	c.Assert(err, IsNil)
	c.Assert(localKeys, HasLen, 2)
	for _, key := range localKeys {
		found := false
		for _, id := range targetsRole.KeyIDs {
			if key.PublicData().ContainsID(id) {
				found = true
				break
			}
		}
		if !found {
			c.Fatal("missing key")
		}
	}

	// check root.json got staged
	meta, err := local.GetMeta()
	c.Assert(err, IsNil)
	rootJSON, ok := meta["root.json"]
	if !ok {
		c.Fatal("missing root metadata")
	}
	s := &data.Signed{}
	c.Assert(json.Unmarshal(rootJSON, s), IsNil)
	stagedRoot := &data.Root{}
	c.Assert(json.Unmarshal(s.Signed, stagedRoot), IsNil)
	c.Assert(stagedRoot.Type, Equals, root.Type)
	c.Assert(stagedRoot.Version, Equals, root.Version)
	c.Assert(stagedRoot.Expires.UnixNano(), Equals, root.Expires.UnixNano())

	// make sure both root and stagedRoot have evaluated IDs(), otherwise
	// DeepEquals will fail because those values might not have been
	// computed yet.
	for _, key := range root.Keys {
		key.IDs()
	}
	for _, key := range stagedRoot.Keys {
		key.IDs()
	}
	c.Assert(stagedRoot.Keys, DeepEquals, root.Keys)
	c.Assert(stagedRoot.Roles, DeepEquals, root.Roles)

	// commit to make sure we don't modify metadata after committing metadata.
	generateAndAddPrivateKey(c, r, "snapshot")
	generateAndAddPrivateKey(c, r, "timestamp")
	c.Assert(r.AddTargets([]string{}, nil), IsNil)
	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)

	// add the same root key to make sure the metadata is unmodified.
	oldRoot, err := r.root()
	c.Assert(err, IsNil)
	addPrivateKey(c, r, "root", signer)
	newRoot, err := r.root()
	c.Assert(err, IsNil)
	c.Assert(oldRoot, DeepEquals, newRoot)
	if r.local.FileIsStaged("root.json") {
		c.Fatal("root should not be marked dirty")
	}
}

func (rs *RepoSuite) TestRevokeKey(c *C) {
	local := MemoryStore(make(map[string]json.RawMessage), nil)
	r, err := NewRepo(local)
	c.Assert(err, IsNil)

	// revoking a key for an unknown role returns ErrInvalidRole
	c.Assert(r.RevokeKey("foo", ""), DeepEquals, ErrInvalidRole{"foo", "only revocations for top-level roles supported"})

	// revoking a key which doesn't exist returns ErrKeyNotFound
	c.Assert(r.RevokeKey("root", "nonexistent"), DeepEquals, ErrKeyNotFound{"root", "nonexistent"})

	// generate keys
	genKey(c, r, "root")
	target1IDs := genKey(c, r, "targets")
	target2IDs := genKey(c, r, "targets")
	genKey(c, r, "snapshot")
	genKey(c, r, "timestamp")
	root, err := r.root()
	c.Assert(err, IsNil)
	c.Assert(root.Roles, NotNil)
	c.Assert(root.Roles, HasLen, 4)
	c.Assert(root.Keys, NotNil)
	rs.assertNumUniqueKeys(c, root, "root", 1)
	rs.assertNumUniqueKeys(c, root, "targets", 2)
	rs.assertNumUniqueKeys(c, root, "snapshot", 1)
	rs.assertNumUniqueKeys(c, root, "timestamp", 1)

	// revoke a key
	targetsRole, ok := root.Roles["targets"]
	if !ok {
		c.Fatal("missing targets role")
	}
	c.Assert(targetsRole.KeyIDs, HasLen, len(target1IDs)+len(target2IDs))
	id := targetsRole.KeyIDs[0]
	c.Assert(r.RevokeKey("targets", id), IsNil)

	// make sure all the other key ids were also revoked
	for _, id := range target1IDs {
		c.Assert(r.RevokeKey("targets", id), DeepEquals, ErrKeyNotFound{"targets", id})
	}

	// check root was updated
	root, err = r.root()
	c.Assert(err, IsNil)
	c.Assert(root.Roles, NotNil)
	c.Assert(root.Roles, HasLen, 4)
	c.Assert(root.Keys, NotNil)
	rs.assertNumUniqueKeys(c, root, "root", 1)
	rs.assertNumUniqueKeys(c, root, "targets", 1)
	rs.assertNumUniqueKeys(c, root, "snapshot", 1)
	rs.assertNumUniqueKeys(c, root, "timestamp", 1)
	targetsRole, ok = root.Roles["targets"]
	if !ok {
		c.Fatal("missing targets role")
	}
	c.Assert(targetsRole.KeyIDs, HasLen, 1)
	c.Assert(targetsRole.KeyIDs, DeepEquals, target2IDs)
}

func (rs *RepoSuite) TestRevokeKeyInMultipleRoles(c *C) {
	local := MemoryStore(make(map[string]json.RawMessage), nil)
	r, err := NewRepo(local)
	c.Assert(err, IsNil)

	// generate keys. add a root key that is shared with the targets role
	rootSigner, err := keys.GenerateEd25519Key()
	c.Assert(err, IsNil)
	c.Assert(r.AddVerificationKey("root", rootSigner.PublicData()), IsNil)
	sharedSigner, err := keys.GenerateEd25519Key()
	c.Assert(err, IsNil)
	sharedIDs := sharedSigner.PublicData().IDs()
	c.Assert(r.AddVerificationKey("root", sharedSigner.PublicData()), IsNil)
	c.Assert(r.AddVerificationKey("targets", sharedSigner.PublicData()), IsNil)
	targetIDs := genKey(c, r, "targets")
	genKey(c, r, "snapshot")
	genKey(c, r, "timestamp")
	root, err := r.root()
	c.Assert(err, IsNil)
	c.Assert(root.Roles, NotNil)
	c.Assert(root.Roles, HasLen, 4)
	c.Assert(root.Keys, NotNil)
	rs.assertNumUniqueKeys(c, root, "root", 2)
	rs.assertNumUniqueKeys(c, root, "targets", 2)
	rs.assertNumUniqueKeys(c, root, "snapshot", 1)
	rs.assertNumUniqueKeys(c, root, "timestamp", 1)

	// revoke a key
	targetsRole, ok := root.Roles["targets"]
	if !ok {
		c.Fatal("missing targets role")
	}
	c.Assert(targetsRole.KeyIDs, HasLen, len(targetIDs)+len(sharedIDs))
	id := targetsRole.KeyIDs[0]
	c.Assert(r.RevokeKey("targets", id), IsNil)

	// make sure all the other key ids were also revoked
	for _, id := range sharedIDs {
		c.Assert(r.RevokeKey("targets", id), DeepEquals, ErrKeyNotFound{"targets", id})
	}

	// check root was updated
	root, err = r.root()
	c.Assert(err, IsNil)
	c.Assert(root.Roles, NotNil)
	c.Assert(root.Roles, HasLen, 4)
	c.Assert(root.Keys, NotNil)
	// the shared root/targets signer should still be present in root keys
	c.Assert(UniqueKeys(root)["root"], DeepEquals,
		[]*data.PublicKey{rootSigner.PublicData(), sharedSigner.PublicData()})
	rs.assertNumUniqueKeys(c, root, "root", 2)
	rs.assertNumUniqueKeys(c, root, "targets", 1)
	rs.assertNumUniqueKeys(c, root, "snapshot", 1)
	rs.assertNumUniqueKeys(c, root, "timestamp", 1)
	targetsRole, ok = root.Roles["targets"]
	if !ok {
		c.Fatal("missing targets role")
	}
	c.Assert(targetsRole.KeyIDs, HasLen, 1)
	c.Assert(targetsRole.KeyIDs, DeepEquals, targetIDs)
}

func (rs *RepoSuite) TestSign(c *C) {
	meta := map[string]json.RawMessage{"root.json": []byte(`{"signed":{},"signatures":[]}`)}
	local := MemoryStore(meta, nil)
	r, err := NewRepo(local)
	c.Assert(err, IsNil)

	c.Assert(r.Sign("foo.json"), Equals, ErrMissingMetadata{"foo.json"})

	// signing with no keys returns ErrNoKeys
	c.Assert(r.Sign("root.json"), Equals, ErrNoKeys{"root.json"})

	checkSigIDs := func(keyIDs ...string) {
		meta, err := local.GetMeta()
		c.Assert(err, IsNil)
		rootJSON, ok := meta["root.json"]
		if !ok {
			c.Fatal("missing root.json")
		}
		s := &data.Signed{}
		c.Assert(json.Unmarshal(rootJSON, s), IsNil)
		c.Assert(s.Signatures, HasLen, len(keyIDs))

		// Signatures may be in any order, so must sort key IDs before comparison.
		wantKeyIDs := append([]string{}, keyIDs...)
		sort.Strings(wantKeyIDs)

		gotKeyIDs := []string{}
		for _, sig := range s.Signatures {
			gotKeyIDs = append(gotKeyIDs, sig.KeyID)
		}
		sort.Strings(gotKeyIDs)

		c.Assert(wantKeyIDs, DeepEquals, gotKeyIDs)
	}

	// signing with an available key generates a signature
	signer, err := keys.GenerateEd25519Key()
	c.Assert(err, IsNil)
	c.Assert(local.SaveSigner("root", signer), IsNil)
	c.Assert(r.Sign("root.json"), IsNil)
	checkSigIDs(signer.PublicData().IDs()...)

	// signing again does not generate a duplicate signature
	c.Assert(r.Sign("root.json"), IsNil)
	checkSigIDs(signer.PublicData().IDs()...)

	// signing with a new available key generates another signature
	newKey, err := keys.GenerateEd25519Key()
	c.Assert(err, IsNil)
	c.Assert(local.SaveSigner("root", newKey), IsNil)
	c.Assert(r.Sign("root.json"), IsNil)
	checkSigIDs(append(signer.PublicData().IDs(), newKey.PublicData().IDs()...)...)

	// attempt to sign missing metadata
	c.Assert(r.Sign("targets.json"), Equals, ErrMissingMetadata{"targets.json"})
}

func (rs *RepoSuite) TestStatus(c *C) {
	files := map[string][]byte{"foo.txt": []byte("foo")}
	local := MemoryStore(make(map[string]json.RawMessage), files)
	r, err := NewRepo(local)
	c.Assert(err, IsNil)

	genKey(c, r, "root")
	genKey(c, r, "targets")
	genKey(c, r, "snapshot")
	genKey(c, r, "timestamp")

	c.Assert(r.AddTarget("foo.txt", nil), IsNil)
	c.Assert(r.SnapshotWithExpires(time.Now().Add(24*time.Hour)), IsNil)
	c.Assert(r.TimestampWithExpires(time.Now().Add(1*time.Hour)), IsNil)
	c.Assert(r.Commit(), IsNil)

	expires := time.Now().Add(2 * time.Hour)
	c.Assert(r.CheckRoleUnexpired("timestamp", expires), ErrorMatches, "role expired on.*")
	c.Assert(r.CheckRoleUnexpired("snapshot", expires), IsNil)
	c.Assert(r.CheckRoleUnexpired("targets", expires), IsNil)
	c.Assert(r.CheckRoleUnexpired("root", expires), IsNil)
}

func (rs *RepoSuite) TestCommit(c *C) {
	files := map[string][]byte{"foo.txt": []byte("foo"), "bar.txt": []byte("bar")}
	local := MemoryStore(make(map[string]json.RawMessage), files)
	r, err := NewRepo(local)
	c.Assert(err, IsNil)

	// commit without root.json
	c.Assert(r.Commit(), DeepEquals, ErrMissingMetadata{"root.json"})

	// Init should create targets.json, but not signed yet
	r.Init(false)
	c.Assert(r.Commit(), DeepEquals, ErrMissingMetadata{"snapshot.json"})

	genKey(c, r, "root")

	// commit without snapshot.json
	genKey(c, r, "targets")
	c.Assert(r.Sign("targets.json"), IsNil)
	c.Assert(r.Commit(), DeepEquals, ErrMissingMetadata{"snapshot.json"})

	// commit without timestamp.json
	genKey(c, r, "snapshot")
	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Commit(), DeepEquals, ErrMissingMetadata{"timestamp.json"})

	// commit with timestamp.json but no timestamp key
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), DeepEquals, ErrInsufficientSignatures{"timestamp.json", verify.ErrNoSignatures})

	// commit success
	genKey(c, r, "timestamp")
	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)

	// commit with an invalid root hash in snapshot.json due to new key creation
	genKey(c, r, "targets")
	c.Assert(r.Sign("targets.json"), IsNil)
	c.Assert(r.Commit(), DeepEquals, errors.New("tuf: invalid targets.json in snapshot.json: wrong length, expected 338 got 552"))

	// commit with an invalid targets hash in snapshot.json
	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.AddTarget("bar.txt", nil), IsNil)
	c.Assert(r.Commit(), DeepEquals, errors.New("tuf: invalid targets.json in snapshot.json: wrong length, expected 552 got 725"))

	// commit with an invalid timestamp
	c.Assert(r.Snapshot(), IsNil)
	err = r.Commit()
	c.Assert(err, NotNil)
	c.Assert(err.Error()[0:44], Equals, "tuf: invalid snapshot.json in timestamp.json")

	// commit with a role's threshold greater than number of keys
	root, err := r.root()
	c.Assert(err, IsNil)
	role, ok := root.Roles["timestamp"]
	if !ok {
		c.Fatal("missing timestamp role")
	}
	c.Assert(role.KeyIDs, HasLen, 1)
	c.Assert(role.Threshold, Equals, 1)
	c.Assert(r.RevokeKey("timestamp", role.KeyIDs[0]), IsNil)
	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), DeepEquals, ErrNotEnoughKeys{"timestamp", 0, 1})
}

func (rs *RepoSuite) TestCommitVersions(c *C) {
	files := map[string][]byte{"foo.txt": []byte("foo")}
	local := MemoryStore(make(map[string]json.RawMessage), files)
	r, err := NewRepo(local)
	c.Assert(err, IsNil)

	genKey(c, r, "root")
	genKey(c, r, "targets")
	genKey(c, r, "snapshot")
	genKey(c, r, "timestamp")

	c.Assert(r.AddTarget("foo.txt", nil), IsNil)
	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)

	// on initial commit everything should be at version 1.
	rootVersion, err := r.RootVersion()
	c.Assert(err, IsNil)
	c.Assert(rootVersion, Equals, int64(1))

	targetsVersion, err := r.TargetsVersion()
	c.Assert(err, IsNil)
	c.Assert(targetsVersion, Equals, int64(1))

	snapshotVersion, err := r.SnapshotVersion()
	c.Assert(err, IsNil)
	c.Assert(snapshotVersion, Equals, int64(1))

	timestampVersion, err := r.SnapshotVersion()
	c.Assert(err, IsNil)
	c.Assert(timestampVersion, Equals, int64(1))

	// taking a snapshot should only increment snapshot and timestamp.
	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)

	rootVersion, err = r.RootVersion()
	c.Assert(err, IsNil)
	c.Assert(rootVersion, Equals, int64(1))

	targetsVersion, err = r.TargetsVersion()
	c.Assert(err, IsNil)
	c.Assert(targetsVersion, Equals, int64(1))

	snapshotVersion, err = r.SnapshotVersion()
	c.Assert(err, IsNil)
	c.Assert(snapshotVersion, Equals, int64(2))

	timestampVersion, err = r.SnapshotVersion()
	c.Assert(err, IsNil)
	c.Assert(timestampVersion, Equals, int64(2))

	// rotating multiple keys should increment the root once.
	genKey(c, r, "targets")
	genKey(c, r, "snapshot")
	genKey(c, r, "timestamp")
	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)

	rootVersion, err = r.RootVersion()
	c.Assert(err, IsNil)
	c.Assert(rootVersion, Equals, int64(2))

	targetsVersion, err = r.TargetsVersion()
	c.Assert(err, IsNil)
	c.Assert(targetsVersion, Equals, int64(1))

	snapshotVersion, err = r.SnapshotVersion()
	c.Assert(err, IsNil)
	c.Assert(snapshotVersion, Equals, int64(3))

	timestampVersion, err = r.TimestampVersion()
	c.Assert(err, IsNil)
	c.Assert(timestampVersion, Equals, int64(3))
}

type tmpDir struct {
	path string
	c    *C
}

func newTmpDir(c *C) *tmpDir {
	return &tmpDir{path: c.MkDir(), c: c}
}

func (t *tmpDir) assertExists(path string) {
	if _, err := os.Stat(filepath.Join(t.path, path)); os.IsNotExist(err) {
		t.c.Fatalf("expected path to exist but it doesn't: %s", path)
	}
}

func (t *tmpDir) assertNotExist(path string) {
	if _, err := os.Stat(filepath.Join(t.path, path)); !os.IsNotExist(err) {
		t.c.Fatalf("expected path to not exist but it does: %s", path)
	}
}

func (t *tmpDir) assertHashedFilesExist(path string, hashes data.Hashes) {
	t.c.Assert(len(hashes) > 0, Equals, true)
	for _, path := range util.HashedPaths(path, hashes) {
		t.assertExists(path)
	}
}

func (t *tmpDir) assertHashedFilesNotExist(path string, hashes data.Hashes) {
	for _, path := range util.HashedPaths(path, hashes) {
		t.assertNotExist(path)
	}
}

func (t *tmpDir) assertVersionedFileExist(path string, version int64) {
	t.assertExists(util.VersionedPath(path, version))
}

func (t *tmpDir) assertVersionedFileNotExist(path string, version int64) {
	t.assertNotExist(util.VersionedPath(path, version))
}

func (t *tmpDir) assertEmpty(dir string) {
	path := filepath.Join(t.path, dir)
	f, err := os.Stat(path)
	if os.IsNotExist(err) {
		t.c.Fatalf("expected dir to exist but it doesn't: %s", dir)
	}
	t.c.Assert(err, IsNil)
	t.c.Assert(f.IsDir(), Equals, true)

	entries, err := os.ReadDir(path)
	t.c.Assert(err, IsNil)
	// check that all (if any) entries are also empty
	for _, e := range entries {
		t.assertEmpty(filepath.Join(dir, e.Name()))
	}
}

func (t *tmpDir) assertFileContent(path, content string) {
	actual := t.readFile(path)
	t.c.Assert(string(actual), Equals, content)
}

func (t *tmpDir) stagedTargetPath(path string) string {
	return filepath.Join(t.path, "staged", "targets", path)
}

func (t *tmpDir) writeStagedTarget(path, data string) {
	path = t.stagedTargetPath(path)
	t.c.Assert(os.MkdirAll(filepath.Dir(path), 0755), IsNil)
	t.c.Assert(os.WriteFile(path, []byte(data), 0644), IsNil)
}

func (t *tmpDir) readFile(path string) []byte {
	t.assertExists(path)
	data, err := os.ReadFile(filepath.Join(t.path, path))
	t.c.Assert(err, IsNil)
	return data
}

func (rs *RepoSuite) TestCommitFileSystem(c *C) {
	tmp := newTmpDir(c)
	local := FileSystemStore(tmp.path, nil)
	r, err := NewRepo(local)
	c.Assert(err, IsNil)

	// don't use consistent snapshots to make the checks simpler
	c.Assert(r.Init(false), IsNil)

	// cleaning with nothing staged or committed should fail
	c.Assert(r.Clean(), Equals, ErrNewRepository)

	// generating keys should stage root.json and create repo dirs
	genKey(c, r, "root")
	genKey(c, r, "targets")
	genKey(c, r, "snapshot")
	genKey(c, r, "timestamp")
	tmp.assertExists("staged/root.json")
	tmp.assertEmpty("repository")
	tmp.assertEmpty("staged/targets")

	// cleaning with nothing committed should fail
	c.Assert(r.Clean(), Equals, ErrNewRepository)

	// adding a non-existent file fails
	c.Assert(r.AddTarget("foo.txt", nil), Equals, ErrFileNotFound{tmp.stagedTargetPath("foo.txt")})
	tmp.assertEmpty("repository")

	// adding a file stages targets.json
	tmp.writeStagedTarget("foo.txt", "foo")
	c.Assert(r.AddTarget("foo.txt", nil), IsNil)
	tmp.assertExists("staged/targets.json")
	tmp.assertEmpty("repository")
	t, err := r.topLevelTargets()
	c.Assert(err, IsNil)
	c.Assert(t.Targets, HasLen, 1)
	if _, ok := t.Targets["foo.txt"]; !ok {
		c.Fatal("missing target file: foo.txt")
	}

	// Snapshot() stages snapshot.json
	c.Assert(r.Snapshot(), IsNil)
	tmp.assertExists("staged/snapshot.json")
	tmp.assertEmpty("repository")

	// Timestamp() stages timestamp.json
	c.Assert(r.Timestamp(), IsNil)
	tmp.assertExists("staged/timestamp.json")
	tmp.assertEmpty("repository")

	// committing moves files from staged -> repository
	c.Assert(r.Commit(), IsNil)
	tmp.assertExists("repository/root.json")
	tmp.assertExists("repository/targets.json")
	tmp.assertExists("repository/snapshot.json")
	tmp.assertExists("repository/timestamp.json")
	tmp.assertFileContent("repository/targets/foo.txt", "foo")
	tmp.assertEmpty("staged/targets")
	tmp.assertEmpty("staged")

	// adding and committing another file moves it into repository/targets
	tmp.writeStagedTarget("path/to/bar.txt", "bar")
	c.Assert(r.AddTarget("path/to/bar.txt", nil), IsNil)
	tmp.assertExists("staged/targets.json")
	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)
	tmp.assertFileContent("repository/targets/foo.txt", "foo")
	tmp.assertFileContent("repository/targets/path/to/bar.txt", "bar")
	tmp.assertEmpty("staged/targets")
	tmp.assertEmpty("staged")

	// removing and committing a file removes it from repository/targets
	c.Assert(r.RemoveTarget("foo.txt"), IsNil)
	tmp.assertExists("staged/targets.json")
	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)
	tmp.assertNotExist("repository/targets/foo.txt")
	tmp.assertFileContent("repository/targets/path/to/bar.txt", "bar")
	tmp.assertEmpty("staged/targets")
	tmp.assertEmpty("staged")
}

func (rs *RepoSuite) TestCommitFileSystemWithNewRepositories(c *C) {
	tmp := newTmpDir(c)

	newRepo := func() *Repo {
		local := FileSystemStore(tmp.path, nil)
		r, err := NewRepo(local)
		c.Assert(err, IsNil)
		return r
	}

	genKey(c, newRepo(), "root")
	genKey(c, newRepo(), "targets")
	genKey(c, newRepo(), "snapshot")
	genKey(c, newRepo(), "timestamp")

	tmp.writeStagedTarget("foo.txt", "foo")
	c.Assert(newRepo().AddTarget("foo.txt", nil), IsNil)
	c.Assert(newRepo().Snapshot(), IsNil)
	c.Assert(newRepo().Timestamp(), IsNil)
	c.Assert(newRepo().Commit(), IsNil)
}

func (rs *RepoSuite) TestConsistentSnapshot(c *C) {
	tmp := newTmpDir(c)
	local := FileSystemStore(tmp.path, nil)
	r, err := NewRepo(local, "sha512", "sha256")
	c.Assert(err, IsNil)

	genKey(c, r, "root")
	genKey(c, r, "targets")
	genKey(c, r, "snapshot")
	genKey(c, r, "timestamp")
	tmp.writeStagedTarget("foo.txt", "foo")
	c.Assert(r.AddTarget("foo.txt", nil), IsNil)
	tmp.writeStagedTarget("dir/bar.txt", "bar")
	c.Assert(r.AddTarget("dir/bar.txt", nil), IsNil)
	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)

	versions, err := r.fileVersions()
	c.Assert(err, IsNil)
	c.Assert(versions["root.json"], Equals, int64(1))
	c.Assert(versions["targets.json"], Equals, int64(1))
	c.Assert(versions["snapshot.json"], Equals, int64(1))

	hashes, err := r.fileHashes()
	c.Assert(err, IsNil)

	// root.json, targets.json and snapshot.json should exist at both versioned and unversioned paths
	for _, meta := range []string{"root.json", "targets.json", "snapshot.json"} {
		repoPath := path.Join("repository", meta)
		if meta != "root.json" {
			c.Assert(len(hashes[meta]) > 0, Equals, true)
		}
		tmp.assertHashedFilesNotExist(repoPath, hashes[meta])
		tmp.assertVersionedFileExist(repoPath, versions[meta])
		tmp.assertExists(repoPath)
	}

	// target files should exist at hashed but not unhashed paths
	for _, target := range []string{"targets/foo.txt", "targets/dir/bar.txt"} {
		repoPath := path.Join("repository", target)
		tmp.assertHashedFilesExist(repoPath, hashes[target])
		tmp.assertNotExist(repoPath)
	}

	// timestamp.json should exist at an unversioned and unhashed path (it doesn't have a hash)
	c.Assert(hashes["repository/timestamp.json"], IsNil)
	tmp.assertVersionedFileNotExist("repository/timestamp.json", versions["repository/timestamp.json"])
	tmp.assertExists("repository/timestamp.json")

	// removing a file should remove the hashed files
	c.Assert(r.RemoveTarget("foo.txt"), IsNil)
	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)

	versions, err = r.fileVersions()
	c.Assert(err, IsNil)
	c.Assert(versions["root.json"], Equals, int64(1))
	c.Assert(versions["targets.json"], Equals, int64(2))
	c.Assert(versions["snapshot.json"], Equals, int64(2))

	// Save the old hashes for foo.txt to make sure we can assert it doesn't exist later.
	fooHashes := hashes["targets/foo.txt"]
	hashes, err = r.fileHashes()
	c.Assert(err, IsNil)

	// root.json, targets.json and snapshot.json should exist at both versioned and unversioned paths
	for _, meta := range []string{"root.json", "targets.json", "snapshot.json"} {
		repoPath := path.Join("repository", meta)
		if meta != "root.json" {
			c.Assert(len(hashes[meta]) > 0, Equals, true)
		}
		tmp.assertHashedFilesNotExist(repoPath, hashes[meta])
		tmp.assertVersionedFileExist(repoPath, versions[meta])
		tmp.assertExists(repoPath)
	}

	tmp.assertHashedFilesNotExist("repository/targets/foo.txt", fooHashes)
	tmp.assertNotExist("repository/targets/foo.txt")

	// targets should be returned by new repo
	newRepo, err := NewRepo(local, "sha512", "sha256")
	c.Assert(err, IsNil)
	t, err := newRepo.topLevelTargets()
	c.Assert(err, IsNil)
	c.Assert(t.Targets, HasLen, 1)
	if _, ok := t.Targets["dir/bar.txt"]; !ok {
		c.Fatal("missing targets file: dir/bar.txt")
	}
}

func (rs *RepoSuite) TestExpiresAndVersion(c *C) {
	files := map[string][]byte{"foo.txt": []byte("foo")}
	local := MemoryStore(make(map[string]json.RawMessage), files)
	r, err := NewRepo(local)
	c.Assert(err, IsNil)

	past := time.Now().Add(-1 * time.Second)
	_, genKeyErr := r.GenKeyWithExpires("root", past)
	for _, err := range []error{
		genKeyErr,
		r.AddTargetWithExpires("foo.txt", nil, past),
		r.RemoveTargetWithExpires("foo.txt", past),
		r.SnapshotWithExpires(past),
		r.TimestampWithExpires(past),
	} {
		c.Assert(err, Equals, ErrInvalidExpires{past})
	}

	genKey(c, r, "root")
	genKey(c, r, "targets")
	genKey(c, r, "snapshot")
	genKey(c, r, "timestamp")

	c.Assert(r.AddTargets([]string{}, nil), IsNil)
	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)

	root, err := r.root()
	c.Assert(err, IsNil)
	c.Assert(root.Version, Equals, int64(1))

	expires := time.Now().Add(24 * time.Hour)
	_, err = r.GenKeyWithExpires("root", expires)
	c.Assert(err, IsNil)
	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)
	root, err = r.root()
	c.Assert(err, IsNil)
	c.Assert(root.Expires.Unix(), DeepEquals, expires.Round(time.Second).Unix())
	c.Assert(root.Version, Equals, int64(2))

	expires = time.Now().Add(12 * time.Hour)
	role, ok := root.Roles["root"]
	if !ok {
		c.Fatal("missing root role")
	}
	c.Assert(role.KeyIDs, HasLen, 2)
	c.Assert(r.RevokeKeyWithExpires("root", role.KeyIDs[0], expires), IsNil)
	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)
	root, err = r.root()
	c.Assert(err, IsNil)
	c.Assert(root.Expires.Unix(), DeepEquals, expires.Round(time.Second).Unix())
	c.Assert(root.Version, Equals, int64(3))

	expires = time.Now().Add(6 * time.Hour)
	c.Assert(r.AddTargetWithExpires("foo.txt", nil, expires), IsNil)
	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)
	targets, err := r.topLevelTargets()
	c.Assert(err, IsNil)
	c.Assert(targets.Expires.Unix(), Equals, expires.Round(time.Second).Unix())
	c.Assert(targets.Version, Equals, int64(2))

	expires = time.Now().Add(2 * time.Hour)
	c.Assert(r.RemoveTargetWithExpires("foo.txt", expires), IsNil)
	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)
	targets, err = r.topLevelTargets()
	c.Assert(err, IsNil)
	c.Assert(targets.Expires.Unix(), Equals, expires.Round(time.Second).Unix())
	c.Assert(targets.Version, Equals, int64(3))

	expires = time.Now().Add(time.Hour)
	c.Assert(r.SnapshotWithExpires(expires), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)
	snapshot, err := r.snapshot()
	c.Assert(err, IsNil)
	c.Assert(snapshot.Expires.Unix(), Equals, expires.Round(time.Second).Unix())
	c.Assert(snapshot.Version, Equals, int64(6))

	_, snapshotHasRoot := snapshot.Meta["root.json"]
	c.Assert(snapshotHasRoot, Equals, false)
	c.Assert(snapshot.Meta["targets.json"].Version, Equals, targets.Version)

	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)
	snapshot, err = r.snapshot()
	c.Assert(err, IsNil)
	c.Assert(snapshot.Version, Equals, int64(7))

	expires = time.Now().Add(10 * time.Minute)
	c.Assert(r.TimestampWithExpires(expires), IsNil)
	c.Assert(r.Commit(), IsNil)
	timestamp, err := r.timestamp()
	c.Assert(err, IsNil)
	c.Assert(timestamp.Expires.Unix(), Equals, expires.Round(time.Second).Unix())
	c.Assert(timestamp.Version, Equals, int64(8))

	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)
	timestamp, err = r.timestamp()
	c.Assert(err, IsNil)
	c.Assert(timestamp.Version, Equals, int64(9))
	c.Assert(timestamp.Meta["snapshot.json"].Version, Equals, snapshot.Version)
}

func (rs *RepoSuite) TestHashAlgorithm(c *C) {
	files := map[string][]byte{"foo.txt": []byte("foo")}
	local := MemoryStore(make(map[string]json.RawMessage), files)
	type hashTest struct {
		args     []string
		expected []string
	}
	for _, test := range []hashTest{
		{args: []string{}, expected: []string{"sha512"}},
		{args: []string{"sha256"}},
		{args: []string{"sha512", "sha256"}},
	} {
		// generate metadata with specific hash functions
		r, err := NewRepo(local, test.args...)
		c.Assert(err, IsNil)
		genKey(c, r, "root")
		genKey(c, r, "targets")
		genKey(c, r, "snapshot")
		c.Assert(r.AddTarget("foo.txt", nil), IsNil)
		c.Assert(r.Snapshot(), IsNil)
		c.Assert(r.Timestamp(), IsNil)

		// check metadata has correct hash functions
		if test.expected == nil {
			test.expected = test.args
		}
		targets, err := r.topLevelTargets()
		c.Assert(err, IsNil)
		snapshot, err := r.snapshot()
		c.Assert(err, IsNil)
		timestamp, err := r.timestamp()
		c.Assert(err, IsNil)
		for name, file := range map[string]data.FileMeta{
			"foo.txt":       targets.Targets["foo.txt"].FileMeta,
			"targets.json":  {Length: snapshot.Meta["targets.json"].Length, Hashes: snapshot.Meta["targets.json"].Hashes},
			"snapshot.json": {Length: timestamp.Meta["snapshot.json"].Length, Hashes: timestamp.Meta["snapshot.json"].Hashes},
		} {
			for _, hashAlgorithm := range test.expected {
				if _, ok := file.Hashes[hashAlgorithm]; !ok {
					c.Fatalf("expected %s hash to contain hash func %s, got %s", name, hashAlgorithm, file.Hashes.HashAlgorithms())
				}
			}
		}
	}
}

func (rs *RepoSuite) TestKeyPersistence(c *C) {
	tmp := newTmpDir(c)
	oldPassphrase := []byte("old_s3cr3t")
	newPassphrase := []byte("new_s3cr3t")
	// returnNewPassphrase is used to force the passphrase function to return the new passphrase when called by the SaveSigner() method
	returnNewPassphrase := false
	// passphrase mock function
	testPassphraseFunc := func(a string, b, change bool) ([]byte, error) {
		if change || returnNewPassphrase {
			return newPassphrase, nil
		}
		return oldPassphrase, nil
	}
	store := FileSystemStore(tmp.path, testPassphraseFunc)

	assertKeys := func(role string, enc bool, expected []*data.PrivateKey) {
		keysJSON := tmp.readFile("keys/" + role + ".json")
		pk := &persistedKeys{}
		c.Assert(json.Unmarshal(keysJSON, pk), IsNil)

		// check the persisted keys are correct
		var actual []*data.PrivateKey
		pass := oldPassphrase
		if enc {
			c.Assert(pk.Encrypted, Equals, true)
			if returnNewPassphrase {
				pass = newPassphrase
			}
			decrypted, err := encrypted.Decrypt(pk.Data, pass)
			c.Assert(err, IsNil)
			c.Assert(json.Unmarshal(decrypted, &actual), IsNil)
		} else {
			c.Assert(pk.Encrypted, Equals, false)
			c.Assert(json.Unmarshal(pk.Data, &actual), IsNil)
		}

		// Compare slices of unique elements disregarding order.
		c.Assert(actual, HasLen, len(expected))
		for _, gotKey := range actual {
			expectedNumMatches := 0
			for _, x := range actual {
				if reflect.DeepEqual(gotKey, x) {
					expectedNumMatches++
				}
			}

			numMatches := 0
			for _, wantKey := range expected {
				wantCanon, err := cjson.EncodeCanonical(wantKey)
				c.Assert(err, IsNil)

				gotCanon, err := cjson.EncodeCanonical(gotKey)
				c.Assert(err, IsNil)

				if string(wantCanon) == string(gotCanon) {
					numMatches++
				}
			}

			c.Assert(numMatches, Equals, expectedNumMatches, Commentf("actual: %+v, expected: %+v", actual, expected))
		}

		// check GetKeys is correct
		signers, err := store.GetSigners(role)
		c.Assert(err, IsNil)

		// Compare slices of unique elements disregarding order.
		c.Assert(signers, HasLen, len(expected))
		for _, s := range signers {
			expectedNumMatches := 0
			for _, x := range signers {
				if reflect.DeepEqual(s, x) {
					expectedNumMatches++
				}
			}

			numMatches := 0
			for _, e := range expected {
				v, err := keys.GetSigner(e)
				c.Assert(err, IsNil)

				if reflect.DeepEqual(s.PublicData().IDs(), v.PublicData().IDs()) {
					numMatches++
				}
			}

			c.Assert(numMatches, Equals, expectedNumMatches, Commentf("signers: %+v, expected: %+v", signers, expected))
		}
	}

	// save a key and check it gets encrypted
	signer, err := keys.GenerateEd25519Key()
	c.Assert(err, IsNil)
	privateKey, err := signer.MarshalPrivateKey()
	c.Assert(err, IsNil)
	c.Assert(store.SaveSigner("root", signer), IsNil)
	assertKeys("root", true, []*data.PrivateKey{privateKey})

	// save another key and check it gets added to the existing keys
	newKey, err := keys.GenerateEd25519Key()
	c.Assert(err, IsNil)
	newPrivateKey, err := newKey.MarshalPrivateKey()
	c.Assert(err, IsNil)
	c.Assert(store.SaveSigner("root", newKey), IsNil)
	assertKeys("root", true, []*data.PrivateKey{privateKey, newPrivateKey})

	// check saving a key to an encrypted file without a passphrase fails
	insecureStore := FileSystemStore(tmp.path, nil)
	signer, err = keys.GenerateEd25519Key()
	c.Assert(err, IsNil)
	c.Assert(insecureStore.SaveSigner("root", signer), Equals, ErrPassphraseRequired{"root"})

	// save a key to an insecure store and check it is not encrypted
	signer, err = keys.GenerateEd25519Key()
	c.Assert(err, IsNil)
	privateKey, err = signer.MarshalPrivateKey()
	c.Assert(err, IsNil)
	c.Assert(insecureStore.SaveSigner("targets", signer), IsNil)
	assertKeys("targets", false, []*data.PrivateKey{privateKey})

	c.Assert(insecureStore.SaveSigner("foo", signer), IsNil)
	assertKeys("foo", false, []*data.PrivateKey{privateKey})

	// Test changing the passphrase
	// 1. Create a secure store with a passphrase (create new object and temp folder so we discard any previous state)
	tmp = newTmpDir(c)
	var logBytes bytes.Buffer
	storeOpts := StoreOpts{
		Logger:   log.New(&logBytes, "", 0),
		PassFunc: testPassphraseFunc,
	}
	store = FileSystemStoreWithOpts(tmp.path, storeOpts)

	// 1.5. Changing passphrase works for top-level and delegated roles.
	r, err := NewRepo(store)
	c.Assert(err, IsNil)

	c.Assert(r.ChangePassphrase("targets"), NotNil)
	c.Assert(r.ChangePassphrase("foo"), NotNil)

	// 2. Test changing the passphrase when the keys file does not exist - should FAIL
	c.Assert(store.(PassphraseChanger).ChangePassphrase("root"), NotNil)
	c.Assert(strings.Contains(logBytes.String(), "Missing keys file"), Equals, true)

	// 3. Generate a new key
	signer, err = keys.GenerateEd25519Key()
	c.Assert(err, IsNil)
	privateKey, err = signer.MarshalPrivateKey()
	c.Assert(err, IsNil)
	c.Assert(store.SaveSigner("root", signer), IsNil)

	// 4. Verify the key file can be decrypted using the original passphrase - should SUCCEED
	assertKeys("root", true, []*data.PrivateKey{privateKey})

	// 5. Change the passphrase (our mock passphrase function is called with change=true thus returning the newPassphrase value)
	c.Assert(store.(PassphraseChanger).ChangePassphrase("root"), IsNil)

	// 6. Try to add a key and implicitly decrypt the keys file using the OLD passphrase - should FAIL
	newKey, err = keys.GenerateEd25519Key()
	c.Assert(err, IsNil)
	_, err = newKey.MarshalPrivateKey()
	c.Assert(err, IsNil)
	c.Assert(store.SaveSigner("root", newKey), NotNil)

	// 7. Try to add a key and implicitly decrypt the keys using the NEW passphrase - should SUCCEED
	returnNewPassphrase = true
	newKey, err = keys.GenerateEd25519Key()
	c.Assert(err, IsNil)
	newPrivateKey, err = newKey.MarshalPrivateKey()
	c.Assert(err, IsNil)
	c.Assert(store.SaveSigner("root", newKey), IsNil)

	// 8. Verify again that the key entries are what we expect after decrypting them using the NEW passphrase
	assertKeys("root", true, []*data.PrivateKey{privateKey, newPrivateKey})
}

func (rs *RepoSuite) TestManageMultipleTargets(c *C) {
	tmp := newTmpDir(c)
	local := FileSystemStore(tmp.path, nil)
	r, err := NewRepo(local)
	c.Assert(err, IsNil)
	// don't use consistent snapshots to make the checks simpler
	c.Assert(r.Init(false), IsNil)
	genKey(c, r, "root")
	genKey(c, r, "targets")
	genKey(c, r, "snapshot")
	genKey(c, r, "timestamp")

	assertRepoTargets := func(paths ...string) {
		t, err := r.topLevelTargets()
		c.Assert(err, IsNil)
		for _, path := range paths {
			if _, ok := t.Targets[path]; !ok {
				c.Fatalf("missing target file: %s", path)
			}
		}
	}

	// adding and committing multiple files moves correct targets from staged -> repository
	tmp.writeStagedTarget("foo.txt", "foo")
	tmp.writeStagedTarget("bar.txt", "bar")
	c.Assert(r.AddTargets([]string{"foo.txt", "bar.txt"}, nil), IsNil)
	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)
	assertRepoTargets("foo.txt", "bar.txt")
	tmp.assertExists("repository/targets/foo.txt")
	tmp.assertExists("repository/targets/bar.txt")

	// adding all targets moves them all from staged -> repository
	count := 10
	files := make([]string, count)
	for i := 0; i < count; i++ {
		files[i] = fmt.Sprintf("file%d.txt", i)
		tmp.writeStagedTarget(files[i], "data")
	}
	c.Assert(r.AddTargets(nil, nil), IsNil)
	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)
	tmp.assertExists("repository/targets/foo.txt")
	tmp.assertExists("repository/targets/bar.txt")
	assertRepoTargets(files...)
	for _, file := range files {
		tmp.assertExists("repository/targets/" + file)
	}
	tmp.assertEmpty("staged/targets")
	tmp.assertEmpty("staged")

	// removing all targets removes them from the repository and targets.json
	c.Assert(r.RemoveTargets(nil), IsNil)
	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)
	tmp.assertNotExist("repository/targets")
	t, err := r.topLevelTargets()
	c.Assert(err, IsNil)
	c.Assert(t.Targets, HasLen, 0)
}

func (rs *RepoSuite) TestCustomTargetMetadata(c *C) {
	files := map[string][]byte{
		"foo.txt": []byte("foo"),
		"bar.txt": []byte("bar"),
		"baz.txt": []byte("baz"),
	}
	local := MemoryStore(make(map[string]json.RawMessage), files)
	r, err := NewRepo(local)
	c.Assert(err, IsNil)

	generateAndAddPrivateKey(c, r, "targets")

	custom := json.RawMessage(`{"foo":"bar"}`)
	assertCustomMeta := func(file string, custom *json.RawMessage) {
		t, err := r.topLevelTargets()
		c.Assert(err, IsNil)
		target, ok := t.Targets[file]
		if !ok {
			c.Fatalf("missing target file: %s", file)
		}
		c.Assert(target.Custom, DeepEquals, custom)
	}

	// check custom metadata gets added to the target
	c.Assert(r.AddTarget("foo.txt", custom), IsNil)
	assertCustomMeta("foo.txt", &custom)

	// check adding bar.txt with no metadata doesn't affect foo.txt
	c.Assert(r.AddTarget("bar.txt", nil), IsNil)
	assertCustomMeta("bar.txt", nil)
	assertCustomMeta("foo.txt", &custom)

	// check adding all files with no metadata doesn't reset existing metadata
	c.Assert(r.AddTargets(nil, nil), IsNil)
	assertCustomMeta("baz.txt", nil)
	assertCustomMeta("bar.txt", nil)
	assertCustomMeta("foo.txt", &custom)
}

func (rs *RepoSuite) TestUnknownKeyIDs(c *C) {
	// generate a repo
	local := MemoryStore(make(map[string]json.RawMessage), nil)
	r, err := NewRepo(local)
	c.Assert(err, IsNil)

	genKey(c, r, "root")
	genKey(c, r, "targets")
	genKey(c, r, "snapshot")
	genKey(c, r, "timestamp")

	// add a new key to the root metadata with an unknown key id.
	signer, err := keys.GenerateEd25519Key()
	c.Assert(err, IsNil)

	root, err := r.root()
	c.Assert(err, IsNil)
	c.Assert(root.Version, Equals, int64(1))

	root.Keys["unknown-key-id"] = signer.PublicData()
	r.setMeta("root.json", root)

	// commit the metadata to the store.
	c.Assert(r.AddTargets([]string{}, nil), IsNil)
	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)

	// validate that the unknown key id wasn't stripped when written to the
	// store.
	meta, err := local.GetMeta()
	c.Assert(err, IsNil)

	rootJSON, ok := meta["root.json"]
	c.Assert(ok, Equals, true)

	var signedRoot struct {
		Signed     data.Root        `json:"signed"`
		Signatures []data.Signature `json:"signatures"`
	}
	c.Assert(json.Unmarshal(rootJSON, &signedRoot), IsNil)
	c.Assert(signedRoot.Signed.Version, Equals, int64(1))

	unknownKey, ok := signedRoot.Signed.Keys["unknown-key-id"]
	c.Assert(ok, Equals, true)
	c.Assert(unknownKey, DeepEquals, signer.PublicData())

	// a new root should preserve the unknown key id.
	root, err = r.root()
	c.Assert(root, NotNil)
	c.Assert(err, IsNil)

	genKey(c, r, "timestamp")
	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)

	meta, err = local.GetMeta()
	c.Assert(err, IsNil)

	rootJSON, ok = meta["root.json"]
	c.Assert(ok, Equals, true)

	c.Assert(json.Unmarshal(rootJSON, &signedRoot), IsNil)
	c.Assert(signedRoot.Signed.Version, Equals, int64(2))

	unknownKey, ok = signedRoot.Signed.Keys["unknown-key-id"]
	c.Assert(ok, Equals, true)
	c.Assert(unknownKey, DeepEquals, signer.PublicData())
}

func (rs *RepoSuite) TestThreshold(c *C) {
	local := MemoryStore(make(map[string]json.RawMessage), nil)
	r, err := NewRepo(local)
	c.Assert(err, IsNil)

	_, err = r.GetThreshold("root")
	c.Assert(err, DeepEquals, ErrInvalidRole{"root", "role missing from root metadata"})
	err = r.SetThreshold("root", 2)
	c.Assert(err, DeepEquals, ErrInvalidRole{"root", "role missing from root metadata"})

	// Add one key to each role
	genKey(c, r, "root")
	genKey(c, r, "targets")
	genKey(c, r, "snapshot")
	genKey(c, r, "timestamp")
	t, err := r.GetThreshold("root")
	c.Assert(err, IsNil)
	c.Assert(t, Equals, 1)

	_, err = r.GetThreshold("foo")
	c.Assert(err, DeepEquals, ErrInvalidRole{"foo", "only thresholds for top-level roles supported"})
	err = r.SetThreshold("foo", 2)
	c.Assert(err, DeepEquals, ErrInvalidRole{"foo", "only thresholds for top-level roles supported"})

	// commit the metadata to the store.
	c.Assert(r.AddTargets([]string{}, nil), IsNil)
	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)

	// Set a new threshold. Commit without threshold keys
	c.Assert(r.SetThreshold("root", 2), IsNil)
	t, err = r.GetThreshold("root")
	c.Assert(err, IsNil)
	c.Assert(t, Equals, 2)
	c.Assert(r.Commit(), DeepEquals, ErrNotEnoughKeys{"root", 1, 2})

	// Add a second root key and try again
	genKey(c, r, "root")
	c.Assert(r.Sign("root.json"), IsNil)
	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)

	// Check versions updated
	rootVersion, err := r.RootVersion()
	c.Assert(err, IsNil)
	c.Assert(rootVersion, Equals, int64(2))

	targetsVersion, err := r.TargetsVersion()
	c.Assert(err, IsNil)
	c.Assert(targetsVersion, Equals, int64(1))

	snapshotVersion, err := r.SnapshotVersion()
	c.Assert(err, IsNil)
	c.Assert(snapshotVersion, Equals, int64(2))

	timestampVersion, err := r.TimestampVersion()
	c.Assert(err, IsNil)
	c.Assert(timestampVersion, Equals, int64(2))
}

func (rs *RepoSuite) TestAddOrUpdateSignatures(c *C) {
	files := map[string][]byte{"foo.txt": []byte("foo")}
	local := MemoryStore(make(map[string]json.RawMessage), files)
	r, err := NewRepo(local)
	c.Assert(err, IsNil)

	// don't use consistent snapshots to make the checks simpler
	c.Assert(r.Init(false), IsNil)

	// generate root key offline and add as a verification key
	rootKey, err := keys.GenerateEd25519Key()
	c.Assert(err, IsNil)
	c.Assert(r.AddVerificationKey("root", rootKey.PublicData()), IsNil)
	targetsKey, err := keys.GenerateEd25519Key()
	c.Assert(err, IsNil)
	c.Assert(r.AddVerificationKey("targets", targetsKey.PublicData()), IsNil)
	snapshotKey, err := keys.GenerateEd25519Key()
	c.Assert(err, IsNil)
	c.Assert(r.AddVerificationKey("snapshot", snapshotKey.PublicData()), IsNil)
	timestampKey, err := keys.GenerateEd25519Key()
	c.Assert(err, IsNil)
	c.Assert(r.AddVerificationKey("timestamp", timestampKey.PublicData()), IsNil)

	// generate signatures externally and append
	rootMeta, err := r.SignedMeta("root.json")
	c.Assert(err, IsNil)
	rootCanonical, err := cjson.EncodeCanonical(rootMeta.Signed)
	c.Assert(err, IsNil)
	rootSig, err := rootKey.SignMessage(rootCanonical)
	c.Assert(err, IsNil)
	for _, id := range rootKey.PublicData().IDs() {
		c.Assert(r.AddOrUpdateSignature("root.json", data.Signature{
			KeyID:     id,
			Signature: rootSig}), IsNil)
	}

	// add targets and sign
	c.Assert(r.AddTarget("foo.txt", nil), IsNil)
	targetsMeta, err := r.SignedMeta("targets.json")
	c.Assert(err, IsNil)
	targetsCanonical, err := cjson.EncodeCanonical(targetsMeta.Signed)
	c.Assert(err, IsNil)
	targetsSig, err := targetsKey.SignMessage(targetsCanonical)
	c.Assert(err, IsNil)
	for _, id := range targetsKey.PublicData().IDs() {
		r.AddOrUpdateSignature("targets.json", data.Signature{
			KeyID:     id,
			Signature: targetsSig})
	}

	// snapshot and timestamp
	c.Assert(r.Snapshot(), IsNil)
	snapshotMeta, err := r.SignedMeta("snapshot.json")
	c.Assert(err, IsNil)
	snapshotCanonical, err := cjson.EncodeCanonical(snapshotMeta.Signed)
	c.Assert(err, IsNil)
	snapshotSig, err := snapshotKey.SignMessage(snapshotCanonical)
	c.Assert(err, IsNil)
	for _, id := range snapshotKey.PublicData().IDs() {
		r.AddOrUpdateSignature("snapshot.json", data.Signature{
			KeyID:     id,
			Signature: snapshotSig})
	}

	c.Assert(r.Timestamp(), IsNil)
	timestampMeta, err := r.SignedMeta("timestamp.json")
	c.Assert(err, IsNil)
	timestampCanonical, err := cjson.EncodeCanonical(timestampMeta.Signed)
	c.Assert(err, IsNil)
	timestampSig, err := timestampKey.SignMessage(timestampCanonical)
	c.Assert(err, IsNil)
	for _, id := range timestampKey.PublicData().IDs() {
		r.AddOrUpdateSignature("timestamp.json", data.Signature{
			KeyID:     id,
			Signature: timestampSig})
	}

	// commit successfully!
	c.Assert(r.Commit(), IsNil)
}

func (rs *RepoSuite) TestBadAddOrUpdateSignatures(c *C) {
	files := map[string][]byte{"foo.txt": []byte("foo")}
	local := MemoryStore(make(map[string]json.RawMessage), files)
	r, err := NewRepo(local)
	c.Assert(err, IsNil)

	// don't use consistent snapshots to make the checks simpler
	c.Assert(r.Init(false), IsNil)

	c.Assert(r.AddOrUpdateSignature("targets.json", data.Signature{
		KeyID:     "foo",
		Signature: nil}), Equals, ErrInvalidRole{"targets", "role is not in verifier DB"})

	// generate root key offline and add as a verification key
	rootKey, err := keys.GenerateEd25519Key()
	c.Assert(err, IsNil)
	c.Assert(r.AddVerificationKey("root", rootKey.PublicData()), IsNil)
	targetsKey, err := keys.GenerateEd25519Key()
	c.Assert(err, IsNil)
	c.Assert(r.AddVerificationKey("targets", targetsKey.PublicData()), IsNil)
	snapshotKey, err := keys.GenerateEd25519Key()
	c.Assert(err, IsNil)
	c.Assert(r.AddVerificationKey("snapshot", snapshotKey.PublicData()), IsNil)
	timestampKey, err := keys.GenerateEd25519Key()
	c.Assert(err, IsNil)
	c.Assert(r.AddVerificationKey("timestamp", timestampKey.PublicData()), IsNil)

	// attempt to sign `root`, rather than `root.json`
	for _, id := range rootKey.PublicData().IDs() {
		c.Assert(r.AddOrUpdateSignature("root", data.Signature{
			KeyID:     id,
			Signature: nil}), Equals, ErrMissingMetadata{"root"})
	}

	// add a signature with a bad role
	rootMeta, err := r.SignedMeta("root.json")
	c.Assert(err, IsNil)
	rootCanonical, err := cjson.EncodeCanonical(rootMeta.Signed)
	c.Assert(err, IsNil)
	rootSig, err := rootKey.Sign(rand.Reader, rootCanonical, crypto.Hash(0))
	c.Assert(err, IsNil)
	for _, id := range rootKey.PublicData().IDs() {
		c.Assert(r.AddOrUpdateSignature("invalid_root.json", data.Signature{
			KeyID:     id,
			Signature: rootSig}), Equals, ErrInvalidRole{"invalid_root", "no trusted keys for role"})
	}

	// add a root signature with an key ID that is for the targets role
	for _, id := range targetsKey.PublicData().IDs() {
		c.Assert(r.AddOrUpdateSignature("root.json", data.Signature{
			KeyID:     id,
			Signature: rootSig}), Equals, verify.ErrInvalidKey)
	}

	// attempt to add a bad signature to root
	badSig, err := rootKey.Sign(rand.Reader, []byte(""), crypto.Hash(0))
	c.Assert(err, IsNil)
	for _, id := range rootKey.PublicData().IDs() {
		c.Assert(r.AddOrUpdateSignature("root.json", data.Signature{
			KeyID:     id,
			Signature: badSig}), Equals, verify.ErrInvalid)
	}

	// add the correct root signature
	for _, id := range rootKey.PublicData().IDs() {
		c.Assert(r.AddOrUpdateSignature("root.json", data.Signature{
			KeyID:     id,
			Signature: rootSig}), IsNil)
	}
	checkSigIDs := func(role string) {
		s, err := r.SignedMeta(role)
		c.Assert(err, IsNil)
		db, err := r.topLevelKeysDB()
		c.Assert(err, IsNil)
		// keys is a map of key IDs.
		keys := db.GetRole(strings.TrimSuffix(role, ".json")).KeyIDs
		c.Assert(s.Signatures, HasLen, len(keys))
		// If the lengths are equal, and each signature key ID appears
		// in the role keys, they Sig IDs are equal to keyIDs.
		for _, sig := range s.Signatures {
			if _, ok := keys[sig.KeyID]; !ok {
				c.Fatal("missing key ID in signatures")
			}
		}
	}
	checkSigIDs("root.json")

	// re-adding should not duplicate. this is checked by verifying
	// signature key IDs match with the map of role key IDs.
	for _, id := range rootKey.PublicData().IDs() {
		c.Assert(r.AddOrUpdateSignature("root.json", data.Signature{
			KeyID:     id,
			Signature: rootSig}), IsNil)
	}
	checkSigIDs("root.json")
}

func (rs *RepoSuite) TestSignDigest(c *C) {
	files := map[string][]byte{"foo.txt": []byte("foo")}
	local := MemoryStore(make(map[string]json.RawMessage), files)
	r, err := NewRepo(local)
	c.Assert(err, IsNil)

	genKey(c, r, "root")
	genKey(c, r, "targets")
	genKey(c, r, "snapshot")
	genKey(c, r, "timestamp")

	digest := "sha256:bc11b176a293bb341a0f2d0d226f52e7fcebd186a7c4dfca5fc64f305f06b94c"
	hash := "bc11b176a293bb341a0f2d0d226f52e7fcebd186a7c4dfca5fc64f305f06b94c"
	size := int64(42)

	c.Assert(r.AddTargetsWithDigest(hash, "sha256", size, digest, nil), IsNil)
	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)

	digest_bytes, err := hex.DecodeString("bc11b176a293bb341a0f2d0d226f52e7fcebd186a7c4dfca5fc64f305f06b94c")
	hex_digest_bytes := data.HexBytes(digest_bytes)
	c.Assert(err, IsNil)

	targets, err := r.topLevelTargets()
	c.Assert(err, IsNil)
	c.Assert(targets.Targets["sha256:bc11b176a293bb341a0f2d0d226f52e7fcebd186a7c4dfca5fc64f305f06b94c"].FileMeta.Length, Equals, size)
	c.Assert(targets.Targets["sha256:bc11b176a293bb341a0f2d0d226f52e7fcebd186a7c4dfca5fc64f305f06b94c"].FileMeta.Hashes["sha256"], DeepEquals, hex_digest_bytes)
}

func concat(ss ...[]string) []string {
	ret := []string{}
	for _, s := range ss {
		ret = append(ret, s...)
	}
	return ret
}

func checkSigKeyIDs(c *C, local LocalStore, fileToKeyIDs map[string][]string) {
	metas, err := local.GetMeta()
	c.Assert(err, IsNil)

	for f, keyIDs := range fileToKeyIDs {
		meta, ok := metas[f]
		c.Assert(ok, Equals, true, Commentf("meta file: %v", f))

		s := &data.Signed{}
		err = json.Unmarshal(meta, s)
		c.Assert(err, IsNil)

		gotKeyIDs := []string{}
		for _, sig := range s.Signatures {
			gotKeyIDs = append(gotKeyIDs, sig.KeyID)
		}
		gotKeyIDs = sets.DeduplicateStrings(gotKeyIDs)
		sort.Strings(gotKeyIDs)

		sort.Strings(keyIDs)
		c.Assert(gotKeyIDs, DeepEquals, keyIDs)
	}
}

func (rs *RepoSuite) TestDelegations(c *C) {
	tmp := newTmpDir(c)
	local := FileSystemStore(tmp.path, nil)
	r, err := NewRepo(local)
	c.Assert(err, IsNil)

	// Add one key to each role
	genKey(c, r, "root")
	targetsKeyIDs := genKey(c, r, "targets")
	genKey(c, r, "snapshot")
	genKey(c, r, "timestamp")

	// commit the metadata to the store.
	c.Assert(r.AddTargets([]string{}, nil), IsNil)
	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)

	snapshot, err := r.snapshot()
	c.Assert(err, IsNil)
	c.Assert(snapshot.Meta, HasLen, 1)
	c.Assert(snapshot.Meta["targets.json"].Version, Equals, int64(1))

	checkSigKeyIDs(c, local, map[string][]string{
		"1.targets.json": targetsKeyIDs,
	})

	saveNewKey := func(role string) keys.Signer {
		key, err := keys.GenerateEd25519Key()
		c.Assert(err, IsNil)

		err = local.SaveSigner(role, key)
		c.Assert(err, IsNil)

		return key
	}

	// Delegate from targets -> role1 for A/*, B/* with one key, threshold 1.
	role1ABKey := saveNewKey("role1")
	role1AB := data.DelegatedRole{
		Name:      "role1",
		KeyIDs:    role1ABKey.PublicData().IDs(),
		Paths:     []string{"A/*", "B/*"},
		Threshold: 1,
	}
	err = r.AddDelegatedRole("targets", role1AB, []*data.PublicKey{
		role1ABKey.PublicData(),
	})
	c.Assert(err, IsNil)

	// Adding duplicate delegation should return an error.
	err = r.AddDelegatedRole("targets", role1AB, []*data.PublicKey{
		role1ABKey.PublicData(),
	})
	c.Assert(err, NotNil)

	// Delegate from targets -> role2 for C/*, D/* with three key, threshold 2.
	role2CDKey1 := saveNewKey("role2")
	role2CDKey2 := saveNewKey("role2")
	role2CDKey3 := saveNewKey("role2")
	role2CD := data.DelegatedRole{
		Name: "role2",
		KeyIDs: concat(
			role2CDKey1.PublicData().IDs(),
			role2CDKey2.PublicData().IDs(),
			role2CDKey3.PublicData().IDs(),
		),
		Paths:     []string{"C/*", "D/*"},
		Threshold: 2,
	}
	err = r.AddDelegatedRole("targets", role2CD, []*data.PublicKey{
		role2CDKey1.PublicData(),
		role2CDKey2.PublicData(),
		role2CDKey3.PublicData(),
	})
	c.Assert(err, IsNil)

	// Delegate from role1 -> role2 for A/allium.txt with one key, threshold 1.
	role1To2Key := saveNewKey("role2")
	role1To2 := data.DelegatedRole{
		Name:        "role2",
		KeyIDs:      role1To2Key.PublicData().IDs(),
		Paths:       []string{"A/allium.txt"},
		Threshold:   1,
		Terminating: true,
	}
	err = r.AddDelegatedRole("role1", role1To2, []*data.PublicKey{
		role1To2Key.PublicData(),
	})
	c.Assert(err, IsNil)

	checkDelegations := func(delegator string, delegatedRoles ...data.DelegatedRole) {
		t, err := r.targets(delegator)
		c.Assert(err, IsNil)

		// Check if there are no delegations.
		if t.Delegations == nil {
			if delegatedRoles != nil {
				c.Fatal("expected delegated roles on delegator")
			}
			return
		}

		// Check that delegated roles are copied verbatim.
		c.Assert(t.Delegations.Roles, DeepEquals, delegatedRoles)

		// Check that public keys match key IDs in roles.
		expectedKeyIDs := []string{}
		for _, dr := range delegatedRoles {
			expectedKeyIDs = append(expectedKeyIDs, dr.KeyIDs...)
		}
		expectedKeyIDs = sets.DeduplicateStrings(expectedKeyIDs)
		sort.Strings(expectedKeyIDs)

		gotKeyIDs := []string{}
		for _, k := range t.Delegations.Keys {
			gotKeyIDs = append(gotKeyIDs, k.IDs()...)
		}
		gotKeyIDs = sets.DeduplicateStrings(gotKeyIDs)
		sort.Strings(gotKeyIDs)

		c.Assert(gotKeyIDs, DeepEquals, expectedKeyIDs)
	}

	checkDelegations("targets", role1AB, role2CD)
	checkDelegations("role1", role1To2)

	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)

	snapshot, err = r.snapshot()
	c.Assert(err, IsNil)
	c.Assert(snapshot.Meta, HasLen, 3)
	c.Assert(snapshot.Meta["targets.json"].Version, Equals, int64(2))
	c.Assert(snapshot.Meta["role1.json"].Version, Equals, int64(1))
	c.Assert(snapshot.Meta["role2.json"].Version, Equals, int64(1))

	checkSigKeyIDs(c, local, map[string][]string{
		"2.targets.json": targetsKeyIDs,
		"1.role1.json":   role1ABKey.PublicData().IDs(),
		"1.role2.json": concat(
			role2CDKey1.PublicData().IDs(),
			role2CDKey2.PublicData().IDs(),
			role2CDKey3.PublicData().IDs(),
			role1To2Key.PublicData().IDs(),
		),
	})

	// Add a variety of targets.
	files := map[string]string{
		// targets.json
		"potato.txt": "potatoes can be starchy or waxy",
		// role1.json
		"A/apple.txt":  "apples are sometimes red",
		"B/banana.txt": "bananas are yellow and sometimes brown",
		// role2.json
		"C/clementine.txt": "clementines are a citrus fruit",
		"D/durian.txt":     "durians are spiky",
		"A/allium.txt":     "alliums include garlic and leeks",
	}
	for name, content := range files {
		tmp.writeStagedTarget(name, content)
		c.Assert(r.AddTarget(name, nil), IsNil)
	}

	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)

	snapshot, err = r.snapshot()
	c.Assert(err, IsNil)
	c.Assert(snapshot.Meta, HasLen, 3)
	// All roles should have new targets.
	c.Assert(snapshot.Meta["targets.json"].Version, Equals, int64(3))
	c.Assert(snapshot.Meta["role1.json"].Version, Equals, int64(2))
	c.Assert(snapshot.Meta["role2.json"].Version, Equals, int64(2))

	checkSigKeyIDs(c, local, map[string][]string{
		"3.targets.json": targetsKeyIDs,
		"2.role1.json":   role1ABKey.PublicData().IDs(),
		"2.role2.json": concat(
			role2CDKey1.PublicData().IDs(),
			role2CDKey2.PublicData().IDs(),
			role2CDKey3.PublicData().IDs(),
			role1To2Key.PublicData().IDs(),
		),
	})

	// Check that the given targets role has signed for the given filenames, with
	// the correct file metadata.
	checkTargets := func(role string, filenames ...string) {
		t, err := r.targets(role)
		c.Assert(err, IsNil)
		c.Assert(t.Targets, HasLen, len(filenames))

		for _, fn := range filenames {
			content := files[fn]

			fm, err := util.GenerateTargetFileMeta(strings.NewReader(content))
			c.Assert(err, IsNil)

			c.Assert(util.TargetFileMetaEqual(t.Targets[fn], fm), IsNil)
		}
	}

	checkTargets("targets", "potato.txt")
	checkTargets("role1", "A/apple.txt", "B/banana.txt")
	checkTargets("role2", "C/clementine.txt", "D/durian.txt", "A/allium.txt")

	// Test AddTargetToPreferredRole.
	// role2 is the default signer for A/allium.txt, but role1 is also eligible
	// for A/*.txt according to the delegation from the top-level targets role.
	c.Assert(r.RemoveTarget("A/allium.txt"), IsNil)
	tmp.writeStagedTarget("A/allium.txt", files["A/allium.txt"])
	c.Assert(r.AddTargetToPreferredRole("A/allium.txt", nil, "role1"), IsNil)

	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)

	snapshot, err = r.snapshot()
	c.Assert(err, IsNil)
	c.Assert(snapshot.Meta, HasLen, 3)
	// Only role1 and role2 should have bumped versions.
	c.Assert(snapshot.Meta["targets.json"].Version, Equals, int64(3))
	c.Assert(snapshot.Meta["role1.json"].Version, Equals, int64(3))
	c.Assert(snapshot.Meta["role2.json"].Version, Equals, int64(3))

	checkSigKeyIDs(c, local, map[string][]string{
		"3.targets.json": targetsKeyIDs,
		"3.role1.json":   role1ABKey.PublicData().IDs(),
		"3.role2.json": concat(
			role2CDKey1.PublicData().IDs(),
			role2CDKey2.PublicData().IDs(),
			role2CDKey3.PublicData().IDs(),
			role1To2Key.PublicData().IDs(),
		),
	})

	// role1 now signs A/allium.txt.
	checkTargets("targets", "potato.txt")
	checkTargets("role1", "A/apple.txt", "B/banana.txt", "A/allium.txt")
	checkTargets("role2", "C/clementine.txt", "D/durian.txt")

	// Remove the delegation from role1 to role2.
	c.Assert(r.ResetTargetsDelegations("role1"), IsNil)
	checkDelegations("targets", role1AB, role2CD)
	checkDelegations("role1")

	// Try to sign A/allium.txt with role2.
	// It should fail since we removed the role1 -> role2 delegation.
	c.Assert(r.RemoveTarget("A/allium.txt"), IsNil)
	tmp.writeStagedTarget("A/allium.txt", files["A/allium.txt"])
	c.Assert(r.AddTargetToPreferredRole("A/allium.txt", nil, "role2"), Equals, ErrNoDelegatedTarget{Path: "A/allium.txt"})

	// Try to sign A/allium.txt with the default role (role1).
	c.Assert(r.AddTarget("A/allium.txt", nil), IsNil)

	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)

	snapshot, err = r.snapshot()
	c.Assert(err, IsNil)
	c.Assert(snapshot.Meta, HasLen, 3)
	// Only role1 should have a bumped version.
	c.Assert(snapshot.Meta["targets.json"].Version, Equals, int64(3))
	c.Assert(snapshot.Meta["role1.json"].Version, Equals, int64(4))
	c.Assert(snapshot.Meta["role2.json"].Version, Equals, int64(3))

	checkSigKeyIDs(c, local, map[string][]string{
		"3.targets.json": targetsKeyIDs,
		"4.role1.json":   role1ABKey.PublicData().IDs(),
		"3.role2.json": concat(
			// Metadata (and therefore signers) for role2.json shouldn't have
			// changed, even though we revoked role1To2Key. Clients verify the
			// signature using keys specified by 4.role1.json, so role1To2Key
			// shouldn't contribute to the threshold.
			role2CDKey1.PublicData().IDs(),
			role2CDKey2.PublicData().IDs(),
			role2CDKey3.PublicData().IDs(),
			role1To2Key.PublicData().IDs(),
		),
	})

	// Re-sign target signed by role2 to test that role1To2Key is not used going
	// forward.
	c.Assert(r.RemoveTarget("C/clementine.txt"), IsNil)
	tmp.writeStagedTarget("C/clementine.txt", files["C/clementine.txt"])
	c.Assert(r.AddTarget("C/clementine.txt", nil), IsNil)

	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)

	snapshot, err = r.snapshot()
	c.Assert(err, IsNil)
	c.Assert(snapshot.Meta, HasLen, 3)
	// Only role2 should have a bumped version.
	c.Assert(snapshot.Meta["targets.json"].Version, Equals, int64(3))
	c.Assert(snapshot.Meta["role1.json"].Version, Equals, int64(4))
	c.Assert(snapshot.Meta["role2.json"].Version, Equals, int64(4))

	checkSigKeyIDs(c, local, map[string][]string{
		"3.targets.json": targetsKeyIDs,
		"4.role1.json":   role1ABKey.PublicData().IDs(),
		"4.role2.json": concat(
			role2CDKey1.PublicData().IDs(),
			role2CDKey2.PublicData().IDs(),
			role2CDKey3.PublicData().IDs(),
			// Note that role1To2Key no longer signs since the role1 -> role2
			// delegation was removed.
		),
	})

	// Targets should still be signed by the same roles.
	checkTargets("targets", "potato.txt")
	checkTargets("role1", "A/apple.txt", "B/banana.txt", "A/allium.txt")
	checkTargets("role2", "C/clementine.txt", "D/durian.txt")

	// Add back the role1 -> role2 delegation, and verify that it doesn't change
	// existing targets in role2.json.
	err = r.AddDelegatedRole("role1", role1To2, []*data.PublicKey{
		role1To2Key.PublicData(),
	})
	c.Assert(err, IsNil)
	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)

	snapshot, err = r.snapshot()
	c.Assert(err, IsNil)
	c.Assert(snapshot.Meta, HasLen, 3)
	// Both role1 and role2 should have a bumped version.
	// role1 is bumped because the delegations changed.
	// role2 is only bumped because its expiration is bumped.
	c.Assert(snapshot.Meta["targets.json"].Version, Equals, int64(3))
	c.Assert(snapshot.Meta["role1.json"].Version, Equals, int64(5))
	c.Assert(snapshot.Meta["role2.json"].Version, Equals, int64(5))

	checkTargets("targets", "potato.txt")
	checkTargets("role1", "A/apple.txt", "B/banana.txt", "A/allium.txt")
	checkTargets("role2", "C/clementine.txt", "D/durian.txt")
}

func (rs *RepoSuite) TestHashBinDelegations(c *C) {
	tmp := newTmpDir(c)
	local := FileSystemStore(tmp.path, nil)
	r, err := NewRepo(local)
	c.Assert(err, IsNil)

	// Add one key to each role
	genKey(c, r, "root")
	targetsKeyIDs := genKey(c, r, "targets")
	genKey(c, r, "snapshot")
	genKey(c, r, "timestamp")

	hb, err := targets.NewHashBins("bins_", 3)
	if err != nil {
		c.Assert(err, IsNil)
	}

	// Generate key for the intermediate bins role.
	binsKey, err := keys.GenerateEd25519Key()
	c.Assert(err, IsNil)
	err = local.SaveSigner("bins", binsKey)
	c.Assert(err, IsNil)

	// Generate key for the leaf bins role.
	leafKey, err := keys.GenerateEd25519Key()
	c.Assert(err, IsNil)
	for i := uint64(0); i < hb.NumBins(); i++ {
		b := hb.GetBin(i)
		err = local.SaveSigner(b.RoleName(), leafKey)
		if err != nil {
			c.Assert(err, IsNil)
		}
	}

	err = r.AddDelegatedRole("targets", data.DelegatedRole{
		Name:      "bins",
		KeyIDs:    binsKey.PublicData().IDs(),
		Paths:     []string{"*.txt"},
		Threshold: 1,
	}, []*data.PublicKey{
		binsKey.PublicData(),
	})
	c.Assert(err, IsNil)

	err = r.AddDelegatedRolesForPathHashBins("bins", hb, []*data.PublicKey{leafKey.PublicData()}, 1)
	c.Assert(err, IsNil)
	targets, err := r.targets("bins")
	c.Assert(err, IsNil)
	c.Assert(targets.Delegations.Roles, HasLen, 8)

	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)

	tmp.writeStagedTarget("foo.txt", "foo")
	err = r.AddTarget("foo.txt", nil)
	c.Assert(err, IsNil)

	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)

	snapshot, err := r.snapshot()
	c.Assert(err, IsNil)
	// 1 targets.json, 1 bins.json, 8 bins_*.json.
	c.Assert(snapshot.Meta, HasLen, 10)
	c.Assert(snapshot.Meta["targets.json"].Version, Equals, int64(1))
	c.Assert(snapshot.Meta["bins.json"].Version, Equals, int64(1))
	c.Assert(snapshot.Meta["bins_0-1.json"].Version, Equals, int64(1))
	c.Assert(snapshot.Meta["bins_2-3.json"].Version, Equals, int64(1))
	c.Assert(snapshot.Meta["bins_4-5.json"].Version, Equals, int64(1))
	c.Assert(snapshot.Meta["bins_6-7.json"].Version, Equals, int64(1))
	c.Assert(snapshot.Meta["bins_8-9.json"].Version, Equals, int64(1))
	c.Assert(snapshot.Meta["bins_a-b.json"].Version, Equals, int64(1))
	c.Assert(snapshot.Meta["bins_c-d.json"].Version, Equals, int64(2))
	c.Assert(snapshot.Meta["bins_e-f.json"].Version, Equals, int64(1))

	targets, err = r.targets("bins_c-d")
	c.Assert(err, IsNil)
	c.Assert(targets.Targets, HasLen, 1)

	checkSigKeyIDs(c, local, map[string][]string{
		"targets.json":    targetsKeyIDs,
		"1.bins.json":     binsKey.PublicData().IDs(),
		"1.bins_0-1.json": leafKey.PublicData().IDs(),
		"1.bins_2-3.json": leafKey.PublicData().IDs(),
		"1.bins_4-5.json": leafKey.PublicData().IDs(),
		"1.bins_6-7.json": leafKey.PublicData().IDs(),
		"1.bins_8-9.json": leafKey.PublicData().IDs(),
		"1.bins_a-b.json": leafKey.PublicData().IDs(),
		"1.bins_c-d.json": leafKey.PublicData().IDs(),
		"2.bins_c-d.json": leafKey.PublicData().IDs(),
		"1.bins_e-f.json": leafKey.PublicData().IDs(),
	})
}

func (rs *RepoSuite) TestResetTargetsDelegationsWithExpires(c *C) {
	tmp := newTmpDir(c)
	local := FileSystemStore(tmp.path, nil)
	r, err := NewRepo(local)
	c.Assert(err, IsNil)

	// Add one key to each role
	genKey(c, r, "root")
	targetsKeyIDs := genKey(c, r, "targets")
	genKey(c, r, "snapshot")
	genKey(c, r, "timestamp")

	// commit the metadata to the store.
	c.Assert(r.AddTargets([]string{}, nil), IsNil)
	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)

	snapshot, err := r.snapshot()
	c.Assert(err, IsNil)
	c.Assert(snapshot.Meta, HasLen, 1)
	c.Assert(snapshot.Meta["targets.json"].Version, Equals, int64(1))

	checkSigKeyIDs(c, local, map[string][]string{
		"1.targets.json": targetsKeyIDs,
	})

	role1Key, err := keys.GenerateEd25519Key()
	c.Assert(err, IsNil)

	err = local.SaveSigner("role1", role1Key)
	c.Assert(err, IsNil)

	// Delegate from targets -> role1 for A/*, B/* with one key, threshold 1.
	role1 := data.DelegatedRole{
		Name:      "role1",
		KeyIDs:    role1Key.PublicData().IDs(),
		Paths:     []string{"A/*", "B/*"},
		Threshold: 1,
	}
	err = r.AddDelegatedRole("targets", role1, []*data.PublicKey{
		role1Key.PublicData(),
	})
	c.Assert(err, IsNil)

	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)

	snapshot, err = r.snapshot()
	c.Assert(err, IsNil)
	c.Assert(snapshot.Meta, HasLen, 2)
	c.Assert(snapshot.Meta["targets.json"].Version, Equals, int64(2))
	c.Assert(snapshot.Meta["role1.json"].Version, Equals, int64(1))

	checkSigKeyIDs(c, local, map[string][]string{
		"1.targets.json": targetsKeyIDs,
		"targets.json":   targetsKeyIDs,
		"1.role1.json":   role1Key.PublicData().IDs(),
		"role1.json":     role1Key.PublicData().IDs(),
	})

	c.Assert(r.ResetTargetsDelegations("targets"), IsNil)
	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)

	snapshot, err = r.snapshot()
	c.Assert(err, IsNil)
	c.Assert(snapshot.Meta, HasLen, 2)
	c.Assert(snapshot.Meta["targets.json"].Version, Equals, int64(3))
	c.Assert(snapshot.Meta["role1.json"].Version, Equals, int64(1))

	checkSigKeyIDs(c, local, map[string][]string{
		"2.targets.json": targetsKeyIDs,
		"targets.json":   targetsKeyIDs,
		"1.role1.json":   role1Key.PublicData().IDs(),
		"role1.json":     role1Key.PublicData().IDs(),
	})
}

func (rs *RepoSuite) TestSignWithDelegations(c *C) {
	tmp := newTmpDir(c)
	local := FileSystemStore(tmp.path, nil)
	r, err := NewRepo(local)
	c.Assert(err, IsNil)

	// Add one key to each role
	genKey(c, r, "root")
	genKey(c, r, "targets")
	genKey(c, r, "snapshot")
	genKey(c, r, "timestamp")

	role1Key, err := keys.GenerateEd25519Key()
	c.Assert(err, IsNil)

	role1 := data.DelegatedRole{
		Name:      "role1",
		KeyIDs:    role1Key.PublicData().IDs(),
		Paths:     []string{"A/*", "B/*"},
		Threshold: 1,
	}
	err = r.AddDelegatedRole("targets", role1, []*data.PublicKey{
		role1Key.PublicData(),
	})
	c.Assert(err, IsNil)

	// targets.json should be signed, but role1.json is not signed because there
	// is no key in the local store.
	m, err := local.GetMeta()
	c.Assert(err, IsNil)
	targetsMeta := &data.Signed{}
	c.Assert(json.Unmarshal(m["targets.json"], targetsMeta), IsNil)
	c.Assert(len(targetsMeta.Signatures), Equals, 1)
	role1Meta := &data.Signed{}
	c.Assert(json.Unmarshal(m["role1.json"], role1Meta), IsNil)
	c.Assert(len(role1Meta.Signatures), Equals, 0)

	c.Assert(r.Snapshot(), DeepEquals, ErrInsufficientSignatures{"role1.json", verify.ErrNoSignatures})

	// Sign role1.json.
	c.Assert(local.SaveSigner("role1", role1Key), IsNil)
	c.Assert(r.Sign("role1.json"), IsNil)

	m, err = local.GetMeta()
	c.Assert(err, IsNil)
	targetsMeta = &data.Signed{}
	c.Assert(json.Unmarshal(m["targets.json"], targetsMeta), IsNil)
	c.Assert(len(targetsMeta.Signatures), Equals, 1)
	role1Meta = &data.Signed{}
	c.Assert(json.Unmarshal(m["role1.json"], role1Meta), IsNil)
	c.Assert(len(role1Meta.Signatures), Equals, 1)

	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)
}

func (rs *RepoSuite) TestAddOrUpdateSignatureWithDelegations(c *C) {
	tmp := newTmpDir(c)
	local := FileSystemStore(tmp.path, nil)
	r, err := NewRepo(local)
	c.Assert(err, IsNil)

	// Add one key to each role
	genKey(c, r, "root")
	genKey(c, r, "targets")
	genKey(c, r, "snapshot")
	genKey(c, r, "timestamp")

	role1Key, err := keys.GenerateEd25519Key()
	c.Assert(err, IsNil)

	role1 := data.DelegatedRole{
		Name:      "role1",
		KeyIDs:    role1Key.PublicData().IDs(),
		Paths:     []string{"A/*", "B/*"},
		Threshold: 1,
	}
	err = r.AddDelegatedRole("targets", role1, []*data.PublicKey{
		role1Key.PublicData(),
	})
	c.Assert(err, IsNil)

	// targets.json should be signed, but role1.json is not signed because there
	// is no key in the local store.
	m, err := local.GetMeta()
	c.Assert(err, IsNil)
	targetsMeta := &data.Signed{}
	c.Assert(json.Unmarshal(m["targets.json"], targetsMeta), IsNil)
	c.Assert(len(targetsMeta.Signatures), Equals, 1)
	role1Meta := &data.Signed{}
	c.Assert(json.Unmarshal(m["role1.json"], role1Meta), IsNil)
	c.Assert(len(role1Meta.Signatures), Equals, 0)

	c.Assert(r.Snapshot(), DeepEquals, ErrInsufficientSignatures{"role1.json", verify.ErrNoSignatures})

	// Sign role1.json.
	canonical, err := cjson.EncodeCanonical(role1Meta.Signed)
	c.Assert(err, IsNil)
	sig, err := role1Key.SignMessage(canonical)
	c.Assert(err, IsNil)
	err = r.AddOrUpdateSignature("role1.json", data.Signature{
		KeyID:     role1Key.PublicData().IDs()[0],
		Signature: sig,
	})
	c.Assert(err, IsNil)

	m, err = local.GetMeta()
	c.Assert(err, IsNil)
	targetsMeta = &data.Signed{}
	c.Assert(json.Unmarshal(m["targets.json"], targetsMeta), IsNil)
	c.Assert(len(targetsMeta.Signatures), Equals, 1)
	role1Meta = &data.Signed{}
	c.Assert(json.Unmarshal(m["role1.json"], role1Meta), IsNil)
	c.Assert(len(role1Meta.Signatures), Equals, 1)

	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)
}

// Test the offline signature flow: Payload -> SignPayload -> AddSignature
func (rs *RepoSuite) TestOfflineFlow(c *C) {
	// Set up repo.
	meta := make(map[string]json.RawMessage)
	local := MemoryStore(meta, nil)
	r, err := NewRepo(local)
	c.Assert(err, IsNil)
	c.Assert(r.Init(false), IsNil)
	// Use ECDSA because it has a newline which is a difference between JSON and cJSON.
	_, err = r.GenKeyWithSchemeAndExpires("root", data.DefaultExpires("root"), data.KeySchemeECDSA_SHA2_P256)
	c.Assert(err, IsNil)

	// Get the payload to sign
	_, err = r.Payload("badrole.json")
	c.Assert(err, Equals, ErrMissingMetadata{"badrole.json"})
	_, err = r.Payload("root")
	c.Assert(err, Equals, ErrMissingMetadata{"root"})
	payload, err := r.Payload("root.json")
	c.Assert(err, IsNil)

	root, err := r.SignedMeta("root.json")
	c.Assert(err, IsNil)
	rootCanonical, err := cjson.EncodeCanonical(root.Signed)
	c.Assert(err, IsNil)
	if !bytes.Equal(payload, rootCanonical) {
		c.Fatalf("Payload(): not canonical.\n%s\n%s", string(payload), string(rootCanonical))
	}

	// Sign the payload
	_, err = r.SignRaw("targets", payload)
	c.Assert(err, Equals, ErrNoKeys{"targets"})
	signatures, err := r.SignRaw("root", payload)
	c.Assert(err, IsNil)
	c.Assert(len(signatures), Equals, 1)

	// Add the payload signatures back
	for _, sig := range signatures {
		// This method checks that the signature verifies!
		err = r.AddOrUpdateSignature("root.json", sig)
		c.Assert(err, IsNil)
	}
}

// Regression test: Snapshotting an invalid root should fail.
func (rs *RepoSuite) TestSnapshotWithInvalidRoot(c *C) {
	files := map[string][]byte{"foo.txt": []byte("foo")}
	local := MemoryStore(make(map[string]json.RawMessage), files)
	r, err := NewRepo(local)
	c.Assert(err, IsNil)

	// Init should create targets.json, but not signed yet
	r.Init(false)

	genKey(c, r, "root")
	genKey(c, r, "targets")
	genKey(c, r, "snapshot")
	genKey(c, r, "timestamp")
	c.Assert(r.AddTarget("foo.txt", nil), IsNil)

	// Clear the root signature so that signature verification fails.
	s, err := r.SignedMeta("root.json")
	c.Assert(err, IsNil)
	c.Assert(s.Signatures, HasLen, 1)
	s.Signatures[0].Signature = data.HexBytes{}
	b, err := r.jsonMarshal(s)
	c.Assert(err, IsNil)
	r.meta["root.json"] = b
	local.SetMeta("root.json", b)

	// Snapshotting should fail.
	c.Assert(r.Snapshot(), Equals, ErrInsufficientSignatures{
		"root.json", verify.ErrRoleThreshold{Expected: 1, Actual: 0}})

	// Correctly sign root
	c.Assert(r.Sign("root.json"), IsNil)
	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)
}

// Regression test: Do not omit length in target metadata files.
func (rs *RepoSuite) TestTargetMetadataLength(c *C) {
	files := map[string][]byte{"foo.txt": []byte("")}
	local := MemoryStore(make(map[string]json.RawMessage), files)
	r, err := NewRepo(local)
	c.Assert(err, IsNil)

	// Init should create targets.json, but not signed yet
	r.Init(false)

	genKey(c, r, "root")
	genKey(c, r, "targets")
	genKey(c, r, "snapshot")
	genKey(c, r, "timestamp")
	c.Assert(r.AddTarget("foo.txt", nil), IsNil)
	c.Assert(r.Snapshot(), IsNil)
	c.Assert(r.Timestamp(), IsNil)
	c.Assert(r.Commit(), IsNil)

	// Check length field of foo.txt exists.
	meta, err := local.GetMeta()
	c.Assert(err, IsNil)
	targetsJSON, ok := meta["targets.json"]
	if !ok {
		c.Fatal("missing targets metadata")
	}
	s := &data.Signed{}
	c.Assert(json.Unmarshal(targetsJSON, s), IsNil)
	fmt.Fprint(os.Stderr, s.Signed)
	var objMap map[string]json.RawMessage
	c.Assert(json.Unmarshal(s.Signed, &objMap), IsNil)
	targetsMap, ok := objMap["targets"]
	if !ok {
		c.Fatal("missing targets field in targets metadata")
	}
	c.Assert(json.Unmarshal(targetsMap, &objMap), IsNil)
	targetsMap, ok = objMap["foo.txt"]
	if !ok {
		c.Fatal("missing foo.txt in targets")
	}
	c.Assert(json.Unmarshal(targetsMap, &objMap), IsNil)
	lengthMsg, ok := objMap["length"]
	if !ok {
		c.Fatal("missing length field in foo.txt file meta")
	}
	var length int64
	c.Assert(json.Unmarshal(lengthMsg, &length), IsNil)
	c.Assert(length, Equals, int64(0))
}

func (rs *RepoSuite) TestDeprecatedHexEncodedKeysFails(c *C) {
	files := map[string][]byte{"foo.txt": []byte("foo")}
	local := MemoryStore(make(map[string]json.RawMessage), files)
	r, err := NewRepo(local)
	c.Assert(err, IsNil)

	r.Init(false)
	// Add a root key with hex-encoded ecdsa format
	signer, err := keys.GenerateEcdsaKey()
	c.Assert(err, IsNil)
	type deprecatedP256Verifier struct {
		PublicKey data.HexBytes `json:"public"`
	}
	pub := signer.PublicKey
	keyValBytes, err := json.Marshal(&deprecatedP256Verifier{PublicKey: elliptic.Marshal(pub.Curve, pub.X, pub.Y)})
	c.Assert(err, IsNil)
	publicData := &data.PublicKey{
		Type:       data.KeyTypeECDSA_SHA2_P256,
		Scheme:     data.KeySchemeECDSA_SHA2_P256,
		Algorithms: data.HashAlgorithms,
		Value:      keyValBytes,
	}
	err = r.AddVerificationKey("root", publicData)
	c.Assert(err, IsNil)
	// TODO: AddVerificationKey does no validation, so perform a sign operation.
	c.Assert(r.Sign("root.json"), ErrorMatches, "tuf: error unmarshalling key: invalid PEM value")
}
