| package tuf |
| |
| import ( |
| "crypto" |
| "crypto/rand" |
| "encoding/hex" |
| "encoding/json" |
| "errors" |
| "fmt" |
| "io/ioutil" |
| "os" |
| "path/filepath" |
| "reflect" |
| "sort" |
| "strings" |
| "testing" |
| "time" |
| |
| "github.com/secure-systems-lab/go-securesystemslib/cjson" |
| "github.com/theupdateframework/go-tuf/data" |
| "github.com/theupdateframework/go-tuf/encrypted" |
| "github.com/theupdateframework/go-tuf/internal/sets" |
| "github.com/theupdateframework/go-tuf/pkg/keys" |
| "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, 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, 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 (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) |
| } |
| |
| // Init() fails if targets have been added |
| c.Assert(r.AddTarget("foo.txt", nil), IsNil) |
| 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"}) |
| |
| // 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"}) |
| |
| // 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, 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"}) |
| |
| // 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) |
| |
| // signing with no keys returns ErrInsufficientKeys |
| c.Assert(r.Sign("root.json"), Equals, ErrInsufficientKeys{"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()...)...) |
| } |
| |
| 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"}) |
| |
| // 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(), 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 511 got 725")) |
| |
| // 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 725 got 899")) |
| |
| // 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, 1) |
| |
| targetsVersion, err := r.TargetsVersion() |
| c.Assert(err, IsNil) |
| c.Assert(targetsVersion, Equals, 1) |
| |
| snapshotVersion, err := r.SnapshotVersion() |
| c.Assert(err, IsNil) |
| c.Assert(snapshotVersion, Equals, 1) |
| |
| timestampVersion, err := r.SnapshotVersion() |
| c.Assert(err, IsNil) |
| c.Assert(timestampVersion, Equals, 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, 1) |
| |
| targetsVersion, err = r.TargetsVersion() |
| c.Assert(err, IsNil) |
| c.Assert(targetsVersion, Equals, 1) |
| |
| snapshotVersion, err = r.SnapshotVersion() |
| c.Assert(err, IsNil) |
| c.Assert(snapshotVersion, Equals, 2) |
| |
| timestampVersion, err = r.SnapshotVersion() |
| c.Assert(err, IsNil) |
| c.Assert(timestampVersion, Equals, 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, 2) |
| |
| targetsVersion, err = r.TargetsVersion() |
| c.Assert(err, IsNil) |
| c.Assert(targetsVersion, Equals, 1) |
| |
| snapshotVersion, err = r.SnapshotVersion() |
| c.Assert(err, IsNil) |
| c.Assert(snapshotVersion, Equals, 3) |
| |
| timestampVersion, err = r.TimestampVersion() |
| c.Assert(err, IsNil) |
| c.Assert(timestampVersion, Equals, 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 int) { |
| t.assertExists(util.VersionedPath(path, version)) |
| } |
| |
| func (t *tmpDir) assertVersionedFileNotExist(path string, version int) { |
| 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 := ioutil.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(ioutil.WriteFile(path, []byte(data), 0644), IsNil) |
| } |
| |
| func (t *tmpDir) readFile(path string) []byte { |
| t.assertExists(path) |
| data, err := ioutil.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, 1) |
| c.Assert(versions["targets.json"], Equals, 1) |
| c.Assert(versions["snapshot.json"], Equals, 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 _, path := range []string{"root.json", "targets.json", "snapshot.json"} { |
| repoPath := filepath.Join("repository", path) |
| if path != "root.json" { |
| c.Assert(len(hashes[path]) > 0, Equals, true) |
| } |
| tmp.assertHashedFilesNotExist(repoPath, hashes[path]) |
| tmp.assertVersionedFileExist(repoPath, versions[path]) |
| tmp.assertExists(repoPath) |
| } |
| |
| // target files should exist at hashed but not unhashed paths |
| for _, path := range []string{"targets/foo.txt", "targets/dir/bar.txt"} { |
| repoPath := filepath.Join("repository", path) |
| tmp.assertHashedFilesExist(repoPath, hashes[path]) |
| 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, 1) |
| c.Assert(versions["targets.json"], Equals, 2) |
| c.Assert(versions["snapshot.json"], Equals, 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 _, path := range []string{"root.json", "targets.json", "snapshot.json"} { |
| repoPath := filepath.Join("repository", path) |
| if path != "root.json" { |
| c.Assert(len(hashes[path]) > 0, Equals, true) |
| } |
| tmp.assertHashedFilesNotExist(repoPath, hashes[path]) |
| tmp.assertVersionedFileExist(repoPath, versions[path]) |
| 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, 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, 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, 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, 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, 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, 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, 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, 8) |
| |
| c.Assert(r.Timestamp(), IsNil) |
| c.Assert(r.Commit(), IsNil) |
| timestamp, err = r.timestamp() |
| c.Assert(err, IsNil) |
| c.Assert(timestamp.Version, Equals, 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": snapshot.Meta["targets.json"].FileMeta, |
| "snapshot.json": timestamp.Meta["snapshot.json"].FileMeta, |
| } { |
| 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.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}) |
| |
| // 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) |
| store = FileSystemStore(tmp.path, testPassphraseFunc) |
| |
| // 2. Test changing the passphrase when the keys file does not exist - should FAIL |
| c.Assert(store.(PassphraseChanger).ChangePassphrase("root"), NotNil) |
| |
| // 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.assertEmpty("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) |
| |
| 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, 1) |
| |
| root.Keys["unknown-key-id"] = signer.PublicData() |
| r.setTopLevelMeta("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, 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, 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) |
| |
| // 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) |
| |
| // 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, 2) |
| |
| targetsVersion, err := r.TargetsVersion() |
| c.Assert(err, IsNil) |
| c.Assert(targetsVersion, Equals, 1) |
| |
| snapshotVersion, err := r.SnapshotVersion() |
| c.Assert(err, IsNil) |
| c.Assert(snapshotVersion, Equals, 2) |
| |
| timestampVersion, err := r.TimestampVersion() |
| c.Assert(err, IsNil) |
| c.Assert(timestampVersion, Equals, 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) |
| rootSig, err := rootKey.SignMessage(rootMeta.Signed) |
| 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) |
| targetsSig, err := targetsKey.SignMessage(targetsMeta.Signed) |
| 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) |
| snapshotSig, err := snapshotKey.SignMessage(snapshotMeta.Signed) |
| 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) |
| timestampSig, err := timestampKey.SignMessage(timestampMeta.Signed) |
| 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) |
| |
| // 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) |
| |
| // add a signature with a bad role |
| rootMeta, err := r.SignedMeta("root.json") |
| c.Assert(err, IsNil) |
| rootSig, err := rootKey.Sign(rand.Reader, rootMeta.Signed, 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"}) |
| } |
| |
| // 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 (rs *RepoSuite) TestDelegations(c *C) { |
| tmp := newTmpDir(c) |
| tmp.writeStagedTarget("foo.txt", "foo") |
| 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") |
| |
| // 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) |
| |
| delegatedKey, err := keys.GenerateEd25519Key() |
| c.Assert(err, IsNil) |
| |
| err = local.SaveSigner("role1", delegatedKey) |
| c.Assert(err, IsNil) |
| |
| paths := []string{} |
| paths = append(paths, "test/*") |
| |
| delegatee := data.DelegatedRole{ |
| Name: "role1", |
| KeyIDs: delegatedKey.PublicData().IDs(), |
| Paths: paths, |
| Threshold: 1, |
| } |
| publicKeys := []*data.PublicKey{} |
| publicKeys = append(publicKeys, delegatedKey.PublicData()) |
| |
| err = r.AddTargetsDelegation("targets", delegatee, publicKeys) |
| c.Assert(err, IsNil) |
| |
| //test duplicate delegation |
| err = r.AddTargetsDelegation("targets", delegatee, publicKeys) |
| c.Assert(err, NotNil) |
| |
| targets, err := r.topLevelTargets() |
| c.Assert(err, IsNil) |
| c.Assert(targets.Delegations.Roles, HasLen, 1) |
| c.Assert(targets.Delegations.Keys, HasLen, 1) |
| |
| c.Assert(r.Snapshot(), IsNil) |
| c.Assert(r.Timestamp(), IsNil) |
| c.Assert(r.Commit(), IsNil) |
| |
| snapshot, err := r.snapshot() |
| c.Assert(err, IsNil) |
| // Snapshots has targets, role1 |
| c.Assert(snapshot.Meta, HasLen, 2) |
| c.Assert(snapshot.Meta["role1.json"].Version, Equals, 1) |
| |
| //add target to delegations |
| tmp.writeStagedTarget("test/bar.txt", "bar") |
| c.Assert(r.AddTarget("test/bar.txt", nil), IsNil) |
| targets, err = r.targets("role1") |
| c.Assert(err, IsNil) |
| c.Assert(targets.Targets, HasLen, 1) |
| // 3 characters in the file |
| c.Assert(targets.Targets["test/bar.txt"].Length, Equals, int64(3)) |
| |
| // add target to the top-level targets |
| tmp.writeStagedTarget("baz.txt", "baz") |
| c.Assert(r.AddTarget("baz.txt", nil), IsNil) |
| targets, err = r.targets("targets") |
| c.Assert(err, IsNil) |
| c.Assert(targets.Targets, HasLen, 2) |
| |
| // test AddTargetToPreferredRole |
| err = r.AddTargetToPreferredRole("test/bar.txt", nil, "targets") |
| c.Assert(err, IsNil) |
| targets, err = r.targets("targets") |
| c.Assert(err, IsNil) |
| c.Assert(targets.Targets, HasLen, 3) |
| |
| c.Assert(r.Snapshot(), IsNil) |
| c.Assert(r.Timestamp(), IsNil) |
| c.Assert(r.Commit(), IsNil) |
| |
| metas, err := local.GetMeta() |
| c.Assert(err, IsNil) |
| |
| for k, meta := range metas { |
| s := &data.Signed{} |
| err = json.Unmarshal(meta, s) |
| c.Assert(err, IsNil, Commentf("meta: %v", k)) |
| c.Assert(len(s.Signatures) > 0, Equals, true, Commentf("meta: %v", k)) |
| } |
| } |
| |
| func (rs *RepoSuite) TestHashedBinDelegations(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") |
| |
| // keys for the hashed bins |
| binsKey, err := keys.GenerateEd25519Key() |
| c.Assert(err, IsNil) |
| err = local.SaveSigner("bins", binsKey) |
| c.Assert(err, IsNil) |
| |
| leafKey, err := keys.GenerateEd25519Key() |
| c.Assert(err, IsNil) |
| err = local.SaveSigner("bins_0-1", leafKey) |
| c.Assert(err, IsNil) |
| err = local.SaveSigner("bins_2-3", leafKey) |
| c.Assert(err, IsNil) |
| err = local.SaveSigner("bins_4-5", leafKey) |
| c.Assert(err, IsNil) |
| err = local.SaveSigner("bins_6-7", leafKey) |
| c.Assert(err, IsNil) |
| err = local.SaveSigner("bins_8-9", leafKey) |
| c.Assert(err, IsNil) |
| err = local.SaveSigner("bins_a-b", leafKey) |
| c.Assert(err, IsNil) |
| err = local.SaveSigner("bins_c-d", leafKey) |
| c.Assert(err, IsNil) |
| err = local.SaveSigner("bins_e-f", leafKey) |
| c.Assert(err, IsNil) |
| |
| err = r.AddTargetsDelegation("targets", data.DelegatedRole{ |
| Name: "bins", |
| KeyIDs: binsKey.PublicData().IDs(), |
| Paths: []string{"*.txt"}, |
| Threshold: 1, |
| }, []*data.PublicKey{ |
| binsKey.PublicData(), |
| }) |
| c.Assert(err, IsNil) |
| |
| err = r.AddTargetsDelegationsForPathHashBins("bins", "bins_", 3, []*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) |
| |
| tmp.writeStagedTarget("foo.txt", "foo") |
| err = r.AddTarget("foo.txt", nil) |
| c.Assert(err, IsNil) |
| targets, err = r.targets("bins_c-d") |
| c.Assert(err, IsNil) |
| c.Assert(targets.Targets, HasLen, 1) |
| |
| c.Assert(r.Snapshot(), IsNil) |
| c.Assert(r.Timestamp(), IsNil) |
| c.Assert(r.Commit(), IsNil) |
| |
| metas, err := local.GetMeta() |
| c.Assert(err, IsNil) |
| |
| for k, meta := range metas { |
| s := &data.Signed{} |
| err = json.Unmarshal(meta, s) |
| c.Assert(err, IsNil, Commentf("meta: %v", k)) |
| c.Assert(len(s.Signatures) > 0, Equals, true, Commentf("meta: %v", k)) |
| } |
| } |