blob: 00c08def92a1837d9c4063fb770c859dcd12a150 [file] [log] [blame]
package tuf
import (
"encoding/json"
"errors"
"testing"
"time"
"github.com/agl/ed25519"
"github.com/flynn/go-tuf/data"
"github.com/flynn/go-tuf/keys"
"github.com/flynn/go-tuf/signed"
. "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) {
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, 1)
c.Assert(root.Keys, NotNil)
c.Assert(root.Keys, HasLen, 0)
targets, err := r.targets()
c.Assert(err, IsNil)
c.Assert(targets.Type, Equals, "targets")
c.Assert(targets.Version, Equals, 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, 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, 1)
c.Assert(timestamp.Meta, NotNil)
c.Assert(timestamp.Meta, HasLen, 0)
}
func genKey(c *C, r *Repo, role string) string {
id, err := r.GenKey(role)
c.Assert(err, IsNil)
return id
}
func (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"})
// generate a root key
id := 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)
c.Assert(root.Keys, NotNil)
c.Assert(root.Keys, HasLen, 1)
rootRole, ok := root.Roles["root"]
if !ok {
c.Fatal("missing root role")
}
c.Assert(rootRole.KeyIDs, HasLen, 1)
keyID := rootRole.KeyIDs[0]
c.Assert(keyID, Equals, id)
k, ok := root.Keys[keyID]
if !ok {
c.Fatal("missing key")
}
c.Assert(k.ID(), Equals, keyID)
c.Assert(k.Value.Public, HasLen, ed25519.PublicKeySize)
c.Assert(k.Value.Private, IsNil)
// check root key + role are in db
db, err := r.db()
c.Assert(err, IsNil)
rootKey := db.GetKey(keyID)
c.Assert(rootKey, NotNil)
c.Assert(rootKey.ID, Equals, keyID)
role := db.GetRole("root")
c.Assert(role.KeyIDs, DeepEquals, map[string]struct{}{keyID: {}})
// check the key was saved correctly
localKeys, err := local.GetKeys("root")
c.Assert(err, IsNil)
c.Assert(localKeys, HasLen, 1)
c.Assert(localKeys[0].ID(), Equals, keyID)
// check RootKeys() is correct
rootKeys, err := r.RootKeys()
c.Assert(err, IsNil)
c.Assert(rootKeys, HasLen, 1)
c.Assert(rootKeys[0].ID(), Equals, rootKey.ID)
c.Assert(rootKeys[0].Value.Public, DeepEquals, rootKey.Serialize().Value.Public)
c.Assert(rootKeys[0].Value.Private, 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)
c.Assert(root.Keys, HasLen, 3)
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.db()
c.Assert(err, IsNil)
for _, id := range targetsRole.KeyIDs {
targetKeyIDs[id] = struct{}{}
_, ok = root.Keys[id]
if !ok {
c.Fatal("missing key")
}
key := db.GetKey(id)
c.Assert(key, NotNil)
c.Assert(key.ID, Equals, id)
}
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].ID(), Equals, rootKey.ID)
// check the keys were saved correctly
localKeys, err = local.GetKeys("targets")
c.Assert(err, IsNil)
c.Assert(localKeys, HasLen, 2)
for _, key := range localKeys {
found := false
for _, id := range targetsRole.KeyIDs {
if id == key.ID() {
found = true
}
}
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())
c.Assert(stagedRoot.Keys, DeepEquals, root.Keys)
c.Assert(stagedRoot.Roles, DeepEquals, root.Roles)
}
func (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"})
// 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")
genKey(c, r, "targets")
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)
c.Assert(root.Keys, HasLen, 5)
// revoke a key
targetsRole, ok := root.Roles["targets"]
if !ok {
c.Fatal("missing targets role")
}
c.Assert(targetsRole.KeyIDs, HasLen, 2)
id := targetsRole.KeyIDs[0]
c.Assert(r.RevokeKey("targets", id), IsNil)
// 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)
c.Assert(root.Keys, HasLen, 4)
targetsRole, ok = root.Roles["targets"]
if !ok {
c.Fatal("missing targets role")
}
c.Assert(targetsRole.KeyIDs, HasLen, 1)
c.Assert(targetsRole.KeyIDs[0], Not(Equals), id)
}
func (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)
// signing with no keys returns ErrInsufficientKeys
c.Assert(r.Sign("root.json"), Equals, ErrInsufficientKeys{"root.json"})
checkSigIDs := func(keyIDs ...string) {
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))
for i, id := range keyIDs {
c.Assert(s.Signatures[i].KeyID, Equals, id)
}
}
// signing with an available key generates a signature
key, err := keys.NewKey()
c.Assert(err, IsNil)
c.Assert(local.SaveKey("root", key.SerializePrivate()), IsNil)
c.Assert(r.Sign("root.json"), IsNil)
checkSigIDs(key.ID)
// signing again does not generate a duplicate signature
c.Assert(r.Sign("root.json"), IsNil)
checkSigIDs(key.ID)
// signing with a new available key generates another signature
newKey, err := keys.NewKey()
c.Assert(err, IsNil)
c.Assert(local.SaveKey("root", newKey.SerializePrivate()), IsNil)
c.Assert(r.Sign("root.json"), IsNil)
checkSigIDs(key.ID, newKey.ID)
}
func (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"})
// commit without targets.json
genKey(c, r, "root")
c.Assert(r.Commit(), DeepEquals, ErrMissingMetadata{"targets.json"})
// commit without snapshot.json
genKey(c, r, "targets")
c.Assert(r.AddTarget("foo.txt", nil), IsNil)
c.Assert(r.Commit(), DeepEquals, ErrMissingMetadata{"snapshot.json"})
// commit without timestamp.json
genKey(c, r, "snapshot")
c.Assert(r.Snapshot(CompressionTypeNone), 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", signed.ErrNoSignatures})
// commit success
genKey(c, r, "timestamp")
c.Assert(r.Snapshot(CompressionTypeNone), 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 root.json in snapshot.json: wrong length"))
// commit with an invalid targets hash in snapshot.json
c.Assert(r.Snapshot(CompressionTypeNone), 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"))
// commit with an invalid timestamp
c.Assert(r.Snapshot(CompressionTypeNone), IsNil)
// TODO: Change this test once Snapshot() supports compression and we
// can guarantee the error will end in "wrong length" by
// compressing a file and thus changing the size of snapshot.json
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(CompressionTypeNone), IsNil)
c.Assert(r.Timestamp(), IsNil)
c.Assert(r.Commit(), DeepEquals, ErrNotEnoughKeys{"timestamp", 0, 1})
}
func (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(CompressionTypeNone, past),
r.TimestampWithExpires(past),
} {
c.Assert(err, Equals, ErrInvalidExpires{past})
}
genKey(c, r, "root")
root, err := r.root()
c.Assert(err, IsNil)
c.Assert(root.Version, Equals, 1)
expires := time.Now().Add(24 * time.Hour)
_, err = r.GenKeyWithExpires("root", expires)
c.Assert(err, IsNil)
root, err = r.root()
c.Assert(err, IsNil)
c.Assert(root.Expires.Unix(), DeepEquals, expires.Unix())
c.Assert(root.Version, Equals, 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)
root, err = r.root()
c.Assert(err, IsNil)
c.Assert(root.Expires.Unix(), DeepEquals, expires.Unix())
c.Assert(root.Version, Equals, 3)
expires = time.Now().Add(6 * time.Hour)
genKey(c, r, "targets")
c.Assert(r.AddTargetWithExpires("foo.txt", nil, expires), IsNil)
targets, err := r.targets()
c.Assert(err, IsNil)
c.Assert(targets.Expires.Unix(), Equals, expires.Unix())
c.Assert(targets.Version, Equals, 1)
expires = time.Now().Add(2 * time.Hour)
c.Assert(r.RemoveTargetWithExpires("foo.txt", expires), IsNil)
targets, err = r.targets()
c.Assert(err, IsNil)
c.Assert(targets.Expires.Unix(), Equals, expires.Unix())
c.Assert(targets.Version, Equals, 2)
expires = time.Now().Add(time.Hour)
genKey(c, r, "snapshot")
c.Assert(r.SnapshotWithExpires(CompressionTypeNone, expires), IsNil)
snapshot, err := r.snapshot()
c.Assert(err, IsNil)
c.Assert(snapshot.Expires.Unix(), Equals, expires.Unix())
c.Assert(snapshot.Version, Equals, 1)
c.Assert(r.Snapshot(CompressionTypeNone), IsNil)
snapshot, err = r.snapshot()
c.Assert(err, IsNil)
c.Assert(snapshot.Version, Equals, 2)
expires = time.Now().Add(10 * time.Minute)
genKey(c, r, "timestamp")
c.Assert(r.TimestampWithExpires(expires), IsNil)
timestamp, err := r.timestamp()
c.Assert(err, IsNil)
c.Assert(timestamp.Expires.Unix(), Equals, expires.Unix())
c.Assert(timestamp.Version, Equals, 1)
c.Assert(r.Timestamp(), IsNil)
timestamp, err = r.timestamp()
c.Assert(err, IsNil)
c.Assert(timestamp.Version, Equals, 2)
}