diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 474c453d3f..58736171cd 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -63,7 +63,7 @@ }, { "ImportPath": "github.com/endophage/gotuf", - "Rev": "9640c9b3f2ff0ba75baf7d1a57632e16cb78d5e6" + "Rev": "b1fb060403583500ba06b11e35130b7c16c74c92" }, { "ImportPath": "github.com/go-sql-driver/mysql", diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/.gitignore b/Godeps/_workspace/src/github.com/endophage/gotuf/.gitignore index e09760442a..2928b6ae98 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/.gitignore +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/.gitignore @@ -1,2 +1,3 @@ /db/ *.bkp +*.swp diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/client/client.go b/Godeps/_workspace/src/github.com/endophage/gotuf/client/client.go index eddc34166a..3fdc0a8dec 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/client/client.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/client/client.go @@ -174,25 +174,7 @@ func (c *Client) downloadRoot() error { logrus.Debug("using cached root") s = old } - // this will confirm that the root has been signed by the old root role - // as c.keysDB contains the root keys we bootstrapped with. - // Still need to determine if there has been a root key update and - // confirm signature with new root key - err = signed.Verify(s, role, version, c.keysDB) - if err != nil { - logrus.Debug("root did not verify with existing keys") - return err - } - - // This will cause keyDB to get updated, overwriting any keyIDs associated - // with the roles in root.json - c.local.SetRoot(s) - // verify again now that the old keys have been replaced with the new keys. - // TODO(endophage): be more intelligent and only re-verify if we detect - // there has been a change in root keys - err = signed.Verify(s, role, version, c.keysDB) - if err != nil { - logrus.Debug("root did not verify with new keys") + if err := c.verifyRoot(role, s, version); err != nil { return err } if download { @@ -205,6 +187,39 @@ func (c *Client) downloadRoot() error { return nil } +func (c Client) verifyRoot(role string, s *data.Signed, minVersion int) error { + // this will confirm that the root has been signed by the old root role + // as c.keysDB contains the root keys we bootstrapped with. + // Still need to determine if there has been a root key update and + // confirm signature with new root key + logrus.Debug("verifying root with existing keys") + err := signed.Verify(s, role, minVersion, c.keysDB) + if err != nil { + logrus.Debug("root did not verify with existing keys") + return err + } + + // This will cause keyDB to get updated, overwriting any keyIDs associated + // with the roles in root.json + logrus.Debug("updating known root roles and keys") + err = c.local.SetRoot(s) + if err != nil { + logrus.Error(err.Error()) + return err + } + // verify again now that the old keys have been replaced with the new keys. + // TODO(endophage): be more intelligent and only re-verify if we detect + // there has been a change in root keys + logrus.Debug("verifying root with updated keys") + err = signed.Verify(s, role, minVersion, c.keysDB) + if err != nil { + logrus.Debug("root did not verify with new keys") + return err + } + logrus.Debug("successfully verified root") + return nil +} + // downloadTimestamp is responsible for downloading the timestamp.json func (c *Client) downloadTimestamp() error { logrus.Debug("downloadTimestamp") @@ -228,9 +243,13 @@ func (c *Client) downloadTimestamp() error { } // unlike root, targets and snapshot, always try and download timestamps // from remote, only using the cache one if we couldn't reach remote. + logrus.Debug("Downloading timestamp") raw, err := c.remote.GetMeta(role, maxSize) var s *data.Signed if err != nil || len(raw) == 0 { + if err, ok := err.(*store.ErrMetaNotFound); ok { + return err + } s = old } else { download = true @@ -244,6 +263,7 @@ func (c *Client) downloadTimestamp() error { if err != nil { return err } + logrus.Debug("successfully verified timestamp") if download { c.cache.SetMeta(role, raw) } @@ -314,6 +334,7 @@ func (c *Client) downloadSnapshot() error { if err != nil { return err } + logrus.Debug("successfully verified snapshot") c.local.SetSnapshot(s) if download { err = c.cache.SetMeta(role, raw) @@ -349,24 +370,24 @@ func (c *Client) downloadTargets(role string) error { return nil } -func (c Client) GetTargetsFile(roleName string, keyIDs []string, snapshotMeta data.Files, consistent bool, threshold int) (*data.Signed, error) { +func (c Client) GetTargetsFile(role string, keyIDs []string, snapshotMeta data.Files, consistent bool, threshold int) (*data.Signed, error) { // require role exists in snapshots - roleMeta, ok := snapshotMeta[roleName] + roleMeta, ok := snapshotMeta[role] if !ok { return nil, fmt.Errorf("Snapshot does not contain target role") } - expectedSha256, ok := snapshotMeta[roleName].Hashes["sha256"] + expectedSha256, ok := snapshotMeta[role].Hashes["sha256"] if !ok { - return nil, fmt.Errorf("Sha256 is currently the only hash supported by this client. No Sha256 found for targets role %s", roleName) + return nil, fmt.Errorf("Sha256 is currently the only hash supported by this client. No Sha256 found for targets role %s", role) } // try to get meta file from content addressed cache var download bool old := &data.Signed{} version := 0 - raw, err := c.cache.GetMeta(roleName, roleMeta.Length) + raw, err := c.cache.GetMeta(role, roleMeta.Length) if err != nil || raw == nil { - logrus.Debugf("Couldn't not find cached %s, must download", roleName) + logrus.Debugf("Couldn't not find cached %s, must download", role) download = true } else { // file may have been tampered with on disk. Always check the hash! @@ -390,11 +411,11 @@ func (c Client) GetTargetsFile(roleName string, keyIDs []string, snapshotMeta da var s *data.Signed if download { - rolePath, err := c.RoleTargetsPath(roleName, hex.EncodeToString(expectedSha256), consistent) + rolePath, err := c.RoleTargetsPath(role, hex.EncodeToString(expectedSha256), consistent) if err != nil { return nil, err } - raw, err = c.remote.GetMeta(rolePath, snapshotMeta[roleName].Length) + raw, err = c.remote.GetMeta(rolePath, snapshotMeta[role].Length) if err != nil { return nil, err } @@ -405,17 +426,18 @@ func (c Client) GetTargetsFile(roleName string, keyIDs []string, snapshotMeta da return nil, err } } else { - logrus.Debug("using cached ", roleName) + logrus.Debug("using cached ", role) s = old } - err = signed.Verify(s, roleName, version, c.keysDB) + err = signed.Verify(s, role, version, c.keysDB) if err != nil { return nil, err } + logrus.Debugf("successfully verified %s", role) if download { // if we error when setting meta, we should continue. - err = c.cache.SetMeta(roleName, raw) + err = c.cache.SetMeta(role, raw) if err != nil { logrus.Errorf("Failed to write snapshot to local cache: %s", err.Error()) } @@ -425,19 +447,19 @@ func (c Client) GetTargetsFile(roleName string, keyIDs []string, snapshotMeta da // RoleTargetsPath generates the appropriate filename for the targets file, // based on whether the repo is marked as consistent. -func (c Client) RoleTargetsPath(roleName string, hashSha256 string, consistent bool) (string, error) { +func (c Client) RoleTargetsPath(role string, hashSha256 string, consistent bool) (string, error) { if consistent { - dir := filepath.Dir(roleName) - if strings.Contains(roleName, "/") { - lastSlashIdx := strings.LastIndex(roleName, "/") - roleName = roleName[lastSlashIdx+1:] + dir := filepath.Dir(role) + if strings.Contains(role, "/") { + lastSlashIdx := strings.LastIndex(role, "/") + role = role[lastSlashIdx+1:] } - roleName = path.Join( + role = path.Join( dir, - fmt.Sprintf("%s.%s.json", hashSha256, roleName), + fmt.Sprintf("%s.%s.json", hashSha256, role), ) } - return roleName, nil + return role, nil } // TargetMeta ensures the repo is up to date, downloading the minimum diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/client/client_test.go b/Godeps/_workspace/src/github.com/endophage/gotuf/client/client_test.go new file mode 100644 index 0000000000..50faf0fa46 --- /dev/null +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/client/client_test.go @@ -0,0 +1,190 @@ +package client + +import ( + "testing" + + "github.com/Sirupsen/logrus" + tuf "github.com/endophage/gotuf" + "github.com/stretchr/testify/assert" + + "github.com/endophage/gotuf/data" + "github.com/endophage/gotuf/keys" + "github.com/endophage/gotuf/signed" + "github.com/endophage/gotuf/store" +) + +func TestRotation(t *testing.T) { + kdb := keys.NewDB() + signer := signed.NewEd25519() + repo := tuf.NewTufRepo(kdb, signer) + remote := store.NewMemoryStore(nil, nil) + cache := store.NewMemoryStore(nil, nil) + + // Generate initial root key and role and add to key DB + rootKey, err := signer.Create("root", data.ED25519Key) + assert.NoError(t, err, "Error creating root key") + rootRole, err := data.NewRole("root", 1, []string{rootKey.ID()}, nil, nil) + assert.NoError(t, err, "Error creating root role") + + kdb.AddKey(rootKey) + err = kdb.AddRole(rootRole) + assert.NoError(t, err, "Error adding root role to db") + + // Generate new key and role. These will appear in the root.json + // but will not be added to the keyDB. + replacementKey, err := signer.Create("root", data.ED25519Key) + assert.NoError(t, err, "Error creating replacement root key") + replacementRole, err := data.NewRole("root", 1, []string{replacementKey.ID()}, nil, nil) + assert.NoError(t, err, "Error creating replacement root role") + + // Generate a new root with the replacement key and role + testRoot, err := data.NewRoot( + map[string]data.PublicKey{replacementKey.ID(): replacementKey}, + map[string]*data.RootRole{"root": &replacementRole.RootRole}, + false, + ) + assert.NoError(t, err, "Failed to create new root") + + // Sign testRoot with both old and new keys + signedRoot, err := testRoot.ToSigned() + err = signed.Sign(signer, signedRoot, rootKey, replacementKey) + assert.NoError(t, err, "Failed to sign root") + var origKeySig bool + var replKeySig bool + for _, sig := range signedRoot.Signatures { + if sig.KeyID == rootKey.ID() { + origKeySig = true + } else if sig.KeyID == replacementKey.ID() { + replKeySig = true + } + } + assert.True(t, origKeySig, "Original root key signature not present") + assert.True(t, replKeySig, "Replacement root key signature not present") + + client := NewClient(repo, remote, kdb, cache) + + err = client.verifyRoot("root", signedRoot, 0) + assert.NoError(t, err, "Failed to verify key rotated root") +} + +func TestRotationNewSigMissing(t *testing.T) { + logrus.SetLevel(logrus.DebugLevel) + kdb := keys.NewDB() + signer := signed.NewEd25519() + repo := tuf.NewTufRepo(kdb, signer) + remote := store.NewMemoryStore(nil, nil) + cache := store.NewMemoryStore(nil, nil) + + // Generate initial root key and role and add to key DB + rootKey, err := signer.Create("root", data.ED25519Key) + assert.NoError(t, err, "Error creating root key") + rootRole, err := data.NewRole("root", 1, []string{rootKey.ID()}, nil, nil) + assert.NoError(t, err, "Error creating root role") + + kdb.AddKey(rootKey) + err = kdb.AddRole(rootRole) + assert.NoError(t, err, "Error adding root role to db") + + // Generate new key and role. These will appear in the root.json + // but will not be added to the keyDB. + replacementKey, err := signer.Create("root", data.ED25519Key) + assert.NoError(t, err, "Error creating replacement root key") + replacementRole, err := data.NewRole("root", 1, []string{replacementKey.ID()}, nil, nil) + assert.NoError(t, err, "Error creating replacement root role") + + assert.NotEqual(t, rootKey.ID(), replacementKey.ID(), "Key IDs are the same") + + // Generate a new root with the replacement key and role + testRoot, err := data.NewRoot( + map[string]data.PublicKey{replacementKey.ID(): replacementKey}, + map[string]*data.RootRole{"root": &replacementRole.RootRole}, + false, + ) + assert.NoError(t, err, "Failed to create new root") + + _, ok := testRoot.Signed.Keys[rootKey.ID()] + assert.False(t, ok, "Old root key appeared in test root") + + // Sign testRoot with both old and new keys + signedRoot, err := testRoot.ToSigned() + err = signed.Sign(signer, signedRoot, rootKey) + assert.NoError(t, err, "Failed to sign root") + var origKeySig bool + var replKeySig bool + for _, sig := range signedRoot.Signatures { + if sig.KeyID == rootKey.ID() { + origKeySig = true + } else if sig.KeyID == replacementKey.ID() { + replKeySig = true + } + } + assert.True(t, origKeySig, "Original root key signature not present") + assert.False(t, replKeySig, "Replacement root key signature was present and shouldn't be") + + client := NewClient(repo, remote, kdb, cache) + + err = client.verifyRoot("root", signedRoot, 0) + assert.Error(t, err, "Should have errored on verify as replacement signature was missing.") + +} + +func TestRotationOldSigMissing(t *testing.T) { + logrus.SetLevel(logrus.DebugLevel) + kdb := keys.NewDB() + signer := signed.NewEd25519() + repo := tuf.NewTufRepo(kdb, signer) + remote := store.NewMemoryStore(nil, nil) + cache := store.NewMemoryStore(nil, nil) + + // Generate initial root key and role and add to key DB + rootKey, err := signer.Create("root", data.ED25519Key) + assert.NoError(t, err, "Error creating root key") + rootRole, err := data.NewRole("root", 1, []string{rootKey.ID()}, nil, nil) + assert.NoError(t, err, "Error creating root role") + + kdb.AddKey(rootKey) + err = kdb.AddRole(rootRole) + assert.NoError(t, err, "Error adding root role to db") + + // Generate new key and role. These will appear in the root.json + // but will not be added to the keyDB. + replacementKey, err := signer.Create("root", data.ED25519Key) + assert.NoError(t, err, "Error creating replacement root key") + replacementRole, err := data.NewRole("root", 1, []string{replacementKey.ID()}, nil, nil) + assert.NoError(t, err, "Error creating replacement root role") + + assert.NotEqual(t, rootKey.ID(), replacementKey.ID(), "Key IDs are the same") + + // Generate a new root with the replacement key and role + testRoot, err := data.NewRoot( + map[string]data.PublicKey{replacementKey.ID(): replacementKey}, + map[string]*data.RootRole{"root": &replacementRole.RootRole}, + false, + ) + assert.NoError(t, err, "Failed to create new root") + + _, ok := testRoot.Signed.Keys[rootKey.ID()] + assert.False(t, ok, "Old root key appeared in test root") + + // Sign testRoot with both old and new keys + signedRoot, err := testRoot.ToSigned() + err = signed.Sign(signer, signedRoot, replacementKey) + assert.NoError(t, err, "Failed to sign root") + var origKeySig bool + var replKeySig bool + for _, sig := range signedRoot.Signatures { + if sig.KeyID == rootKey.ID() { + origKeySig = true + } else if sig.KeyID == replacementKey.ID() { + replKeySig = true + } + } + assert.False(t, origKeySig, "Original root key signature was present and shouldn't be") + assert.True(t, replKeySig, "Replacement root key signature was not present") + + client := NewClient(repo, remote, kdb, cache) + + err = client.verifyRoot("root", signedRoot, 0) + assert.Error(t, err, "Should have errored on verify as replacement signature was missing.") + +} diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/signed/verify.go b/Godeps/_workspace/src/github.com/endophage/gotuf/signed/verify.go index 4110c50095..b2dc24ebaa 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/signed/verify.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/signed/verify.go @@ -3,6 +3,7 @@ package signed import ( "encoding/json" "errors" + "strings" "time" "github.com/Sirupsen/logrus" @@ -115,6 +116,11 @@ func VerifySignatures(s *data.Signed, role string, db *keys.KeyDB) error { return ErrUnknownRole } + if roleData.Threshold < 1 { + return ErrRoleThreshold + } + logrus.Debugf("%s role has key IDs: %s", role, strings.Join(roleData.KeyIDs, ",")) + var decoded map[string]interface{} if err := json.Unmarshal(s.Signed, &decoded); err != nil { return err @@ -126,6 +132,7 @@ func VerifySignatures(s *data.Signed, role string, db *keys.KeyDB) error { valid := make(map[string]struct{}) for _, sig := range s.Signatures { + logrus.Debug("verifying signature for key ID: ", sig.KeyID) if !roleData.ValidKey(sig.KeyID) { logrus.Debugf("continuing b/c keyid was invalid: %s for roledata %s\n", sig.KeyID, roleData) continue diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/store/errors.go b/Godeps/_workspace/src/github.com/endophage/gotuf/store/errors.go index 69d2090f04..0bc8272f96 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/store/errors.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/store/errors.go @@ -1,13 +1,7 @@ package store -import ( - "fmt" -) - -type ErrMetaNotFound struct { - role string -} +type ErrMetaNotFound struct{} func (err ErrMetaNotFound) Error() string { - return fmt.Sprintf("no metadata for %s", err.role) + return "no trust data available" } diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/store/httpstore.go b/Godeps/_workspace/src/github.com/endophage/gotuf/store/httpstore.go index 7ec05842b2..213744b1c5 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/store/httpstore.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/store/httpstore.go @@ -67,8 +67,9 @@ func (s HTTPStore) GetMeta(name string, size int64) ([]byte, error) { return nil, err } defer resp.Body.Close() + logrus.Debugf("%d when retrieving metadata for %s", resp.StatusCode, name) if resp.StatusCode == http.StatusNotFound { - return nil, &ErrMetaNotFound{role: name} + return nil, &ErrMetaNotFound{} } b := io.LimitReader(resp.Body, int64(size)) body, err := ioutil.ReadAll(b) diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/store/httpstore_test.go b/Godeps/_workspace/src/github.com/endophage/gotuf/store/httpstore_test.go index 2f1d9e2dac..2c0be9f585 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/store/httpstore_test.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/store/httpstore_test.go @@ -47,7 +47,7 @@ func TestHTTPStoreGetMeta(t *testing.T) { rootPem := "-----BEGIN PUBLIC KEY-----\nMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEArvqUPYb6JJROPJQglPTj\n5uDrsxQKl34Mo+3pSlBVuD6puE4lDnG649a2YksJy+C8ZIPJgokn5w+C3alh+dMe\nzbdWHHxrY1h9CLpYz5cbMlE16303ubkt1rvwDqEezG0HDBzPaKj4oP9YJ9x7wbsq\ndvFcy+Qc3wWd7UWcieo6E0ihbJkYcY8chRXVLg1rL7EfZ+e3bq5+ojA2ECM5JqzZ\nzgDpqCv5hTCYYZp72MZcG7dfSPAHrcSGIrwg7whzz2UsEtCOpsJTuCl96FPN7kAu\n4w/WyM3+SPzzr4/RQXuY1SrLCFD8ebM2zHt/3ATLhPnGmyG5I0RGYoegFaZ2AViw\nlqZDOYnBtgDvKP0zakMtFMbkh2XuNBUBO7Sjs0YcZMjLkh9gYUHL1yWS3Aqus1Lw\nlI0gHS22oyGObVBWkZEgk/Foy08sECLGao+5VvhmGpfVuiz9OKFUmtPVjWzRE4ng\niekEu4drSxpH41inLGSvdByDWLpcTvWQI9nkgclh3AT/AgMBAAE=\n-----END PUBLIC KEY-----" k := data.NewPublicKey("RSA", []byte(rootPem)) - sigBytes, err := hex.DecodeString(p.Signatures[0].Signature.String()) + sigBytes := p.Signatures[0].Signature if err != nil { t.Fatal(err) } diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/store/memorystore.go b/Godeps/_workspace/src/github.com/endophage/gotuf/store/memorystore.go index 744fa83bc1..d32c9a4f3d 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/store/memorystore.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/store/memorystore.go @@ -2,12 +2,15 @@ package store import ( "bytes" + "fmt" + "io" "github.com/endophage/gotuf/data" "github.com/endophage/gotuf/errors" + "github.com/endophage/gotuf/utils" ) -func NewMemoryStore(meta map[string][]byte, files map[string][]byte) LocalStore { +func NewMemoryStore(meta map[string][]byte, files map[string][]byte) *memoryStore { if meta == nil { meta = make(map[string][]byte) } @@ -43,8 +46,8 @@ func (m *memoryStore) SetMultiMeta(metas map[string][]byte) error { return nil } -func (m *memoryStore) AddBlob(path string, meta data.FileMeta) { - +func (m *memoryStore) GetTarget(path string) (io.ReadCloser, error) { + return &utils.NoopCloser{Reader: bytes.NewReader(m.files[path])}, nil } func (m *memoryStore) WalkStagedTargets(paths []string, targetsFn targetsWalkFunc) error { @@ -81,18 +84,6 @@ func (m *memoryStore) Commit(map[string][]byte, bool, map[string]data.Hashes) er return nil } -func (m *memoryStore) GetKeys(role string) ([]data.PrivateKey, error) { - return m.keys[role], nil -} - -func (m *memoryStore) SaveKey(role string, key data.PrivateKey) error { - if _, ok := m.keys[role]; !ok { - m.keys[role] = make([]data.PrivateKey, 0) - } - m.keys[role] = append(m.keys[role], key) - return nil -} - -func (m *memoryStore) Clean() error { - return nil +func (m *memoryStore) GetKey(role string) ([]byte, error) { + return nil, fmt.Errorf("GetKey is not implemented for the memoryStore") } diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/tuf.go b/Godeps/_workspace/src/github.com/endophage/gotuf/tuf.go index e7583020cc..3e63ea929e 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/tuf.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/tuf.go @@ -8,6 +8,7 @@ import ( "encoding/json" "fmt" "path/filepath" + "strings" "time" "github.com/Sirupsen/logrus" @@ -263,9 +264,11 @@ func (tr *TufRepo) SetRoot(s *data.Signed) error { return err } for _, key := range r.Signed.Keys { + logrus.Debug("Adding key ", key.ID()) tr.keysDB.AddKey(key) } for roleName, role := range r.Signed.Roles { + logrus.Debugf("Adding role %s with keys %s", roleName, strings.Join(role.KeyIDs, ",")) baseRole, err := data.NewRole( roleName, role.Threshold, diff --git a/Godeps/_workspace/src/github.com/endophage/gotuf/utils/utils.go b/Godeps/_workspace/src/github.com/endophage/gotuf/utils/utils.go index 244b2176d8..38d46b031a 100644 --- a/Godeps/_workspace/src/github.com/endophage/gotuf/utils/utils.go +++ b/Godeps/_workspace/src/github.com/endophage/gotuf/utils/utils.go @@ -70,3 +70,11 @@ func FileExists(path string) bool { _, err := os.Stat(path) return os.IsNotExist(err) } + +type NoopCloser struct { + io.Reader +} + +func (nc *NoopCloser) Close() error { + return nil +}