Error (and add tests for this) if the root in the server store is corrupt

Signed-off-by: Ying Li <ying.li@docker.com>
This commit is contained in:
Ying Li 2016-03-09 14:16:57 -08:00 committed by David Lawrence
parent 3b80293a0c
commit 210eab829f
2 changed files with 134 additions and 15 deletions

View File

@ -55,7 +55,7 @@ func validateUpdate(cs signed.CryptoService, gun string, updates []storage.MetaU
// against a previous root // against a previous root
if root, err = validateRoot(gun, oldRootJSON, rootUpdate.Data); err != nil { if root, err = validateRoot(gun, oldRootJSON, rootUpdate.Data); err != nil {
logrus.Error("ErrBadRoot: ", err.Error()) logrus.Error("ErrBadRoot: ", err.Error())
return nil, validation.ErrBadRoot{Msg: err.Error()} return nil, err
} }
// setting root will update keys db // setting root will update keys db
@ -71,7 +71,7 @@ func validateUpdate(cs signed.CryptoService, gun string, updates []storage.MetaU
} }
parsedOldRoot := &data.SignedRoot{} parsedOldRoot := &data.SignedRoot{}
if err := json.Unmarshal(oldRootJSON, parsedOldRoot); err != nil { if err := json.Unmarshal(oldRootJSON, parsedOldRoot); err != nil {
return nil, validation.ErrValidation{Msg: "pre-existing root is corrupted and no root provided in update."} return nil, fmt.Errorf("pre-existing root is corrupt")
} }
if err = repo.SetRoot(parsedOldRoot); err != nil { if err = repo.SetRoot(parsedOldRoot); err != nil {
logrus.Error("ErrValidation: ", err.Error()) logrus.Error("ErrValidation: ", err.Error())
@ -384,23 +384,23 @@ func validateRoot(gun string, oldRoot, newRoot []byte) (
parsedNewSigned := &data.Signed{} parsedNewSigned := &data.Signed{}
err := json.Unmarshal(newRoot, parsedNewSigned) err := json.Unmarshal(newRoot, parsedNewSigned)
if err != nil { if err != nil {
return nil, err return nil, validation.ErrBadRoot{Msg: err.Error()}
} }
// validates the structure of the root metadata // validates the structure of the root metadata
parsedNewRoot, err := data.RootFromSigned(parsedNewSigned) parsedNewRoot, err := data.RootFromSigned(parsedNewSigned)
if err != nil { if err != nil {
return nil, err return nil, validation.ErrBadRoot{Msg: err.Error()}
} }
newRootRole, _ := parsedNewRoot.BuildBaseRole(data.CanonicalRootRole) newRootRole, _ := parsedNewRoot.BuildBaseRole(data.CanonicalRootRole)
if err != nil { // should never happen, since the root metadata has been validated if err != nil { // should never happen, since the root metadata has been validated
return nil, err return nil, validation.ErrBadRoot{Msg: err.Error()}
} }
newTimestampRole, err := parsedNewRoot.BuildBaseRole(data.CanonicalTimestampRole) newTimestampRole, err := parsedNewRoot.BuildBaseRole(data.CanonicalTimestampRole)
if err != nil { // should never happen, since the root metadata has been validated if err != nil { // should never happen, since the root metadata has been validated
return nil, err return nil, validation.ErrBadRoot{Msg: err.Error()}
} }
// According to the TUF spec, any role may have more than one signing // According to the TUF spec, any role may have more than one signing
// key and require a threshold signature. However, notary-server // key and require a threshold signature. However, notary-server
@ -417,7 +417,7 @@ func validateRoot(gun string, oldRoot, newRoot []byte) (
} }
if err := signed.VerifyRoot(parsedNewSigned, newRootRole.Threshold, newRootRole.Keys); err != nil { if err := signed.VerifyRoot(parsedNewSigned, newRootRole.Threshold, newRootRole.Keys); err != nil {
return nil, err return nil, validation.ErrBadRoot{Msg: err.Error()}
} }
return parsedNewRoot, nil return parsedNewRoot, nil
@ -429,13 +429,13 @@ func checkAgainstOldRoot(oldRoot []byte, newRootRole data.BaseRole, newSigned *d
err := json.Unmarshal(oldRoot, parsedOldRoot) err := json.Unmarshal(oldRoot, parsedOldRoot)
if err != nil { if err != nil {
logrus.Warn("Old root could not be parsed, and cannot be used to check the new root.") logrus.Warn("Old root could not be parsed, and cannot be used to check the new root.")
return nil return err
} }
oldRootRole, err := parsedOldRoot.BuildBaseRole(data.CanonicalRootRole) oldRootRole, err := parsedOldRoot.BuildBaseRole(data.CanonicalRootRole)
if err != nil { if err != nil {
logrus.Warn("Old root does not have a valid root role, and cannot be used to check the new root.") logrus.Warn("Old root does not have a valid root role, and cannot be used to check the new root.")
return nil return err
} }
// if the set of keys has changed between the old root and new root, then a root // if the set of keys has changed between the old root and new root, then a root
@ -453,8 +453,9 @@ func checkAgainstOldRoot(oldRoot []byte, newRootRole data.BaseRole, newSigned *d
if rotation { if rotation {
if err := signed.VerifyRoot(newSigned, oldRootRole.Threshold, oldRootRole.Keys); err != nil { if err := signed.VerifyRoot(newSigned, oldRootRole.Threshold, oldRootRole.Keys); err != nil {
return fmt.Errorf("rotation detected and new root was not signed with at least %d old keys", return validation.ErrBadRoot{Msg: fmt.Sprintf(
oldRootRole.Threshold) "rotation detected and new root was not signed with at least %d old keys",
oldRootRole.Threshold)}
} }
} }

View File

@ -6,6 +6,7 @@ import (
"path" "path"
"reflect" "reflect"
"testing" "testing"
"time"
"github.com/docker/notary/cryptoservice" "github.com/docker/notary/cryptoservice"
"github.com/docker/notary/passphrase" "github.com/docker/notary/passphrase"
@ -27,22 +28,22 @@ type getFailStore struct {
// GetCurrent returns the current metadata, or an error depending on whether // GetCurrent returns the current metadata, or an error depending on whether
// getFailStore is configured to return an error for this role // getFailStore is configured to return an error for this role
func (f getFailStore) GetCurrent(gun, tufRole string) ([]byte, error) { func (f getFailStore) GetCurrent(gun, tufRole string) (*time.Time, []byte, error) {
err := f.errsToReturn[tufRole] err := f.errsToReturn[tufRole]
if err == nil { if err == nil {
return f.MetaStore.GetCurrent(gun, tufRole) return f.MetaStore.GetCurrent(gun, tufRole)
} }
return nil, err return nil, nil, err
} }
// GetChecksum returns the metadata with this checksum, or an error depending on // GetChecksum returns the metadata with this checksum, or an error depending on
// whether getFailStore is configured to return an error for this role // whether getFailStore is configured to return an error for this role
func (f getFailStore) GetChecksum(gun, tufRole, checksum string) ([]byte, error) { func (f getFailStore) GetChecksum(gun, tufRole, checksum string) (*time.Time, []byte, error) {
err := f.errsToReturn[tufRole] err := f.errsToReturn[tufRole]
if err == nil { if err == nil {
return f.MetaStore.GetChecksum(gun, tufRole, checksum) return f.MetaStore.GetChecksum(gun, tufRole, checksum)
} }
return nil, err return nil, nil, err
} }
func copyKeys(t *testing.T, from signed.CryptoService, roles ...string) signed.CryptoService { func copyKeys(t *testing.T, from signed.CryptoService, roles ...string) signed.CryptoService {
@ -278,6 +279,81 @@ func TestValidateOldRoot(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
} }
func TestValidateOldRootCorrupt(t *testing.T) {
repo, cs, err := testutils.EmptyRepo("docker.com/notary")
assert.NoError(t, err)
store := storage.NewMemStorage()
r, tg, sn, ts, err := testutils.Sign(repo)
assert.NoError(t, err)
root, targets, snapshot, timestamp, err := getUpdates(r, tg, sn, ts)
assert.NoError(t, err)
badRoot := storage.MetaUpdate{
Version: root.Version,
Role: root.Role,
Data: root.Data[1:],
}
store.UpdateCurrent("testGUN", badRoot)
updates := []storage.MetaUpdate{root, targets, snapshot, timestamp}
serverCrypto := copyKeys(t, cs, data.CanonicalTimestampRole)
_, err = validateUpdate(serverCrypto, "testGUN", updates, store)
assert.Error(t, err)
assert.IsType(t, &json.SyntaxError{}, err)
}
func TestValidateOldRootCorruptRootRole(t *testing.T) {
repo, cs, err := testutils.EmptyRepo("docker.com/notary")
assert.NoError(t, err)
store := storage.NewMemStorage()
r, tg, sn, ts, err := testutils.Sign(repo)
assert.NoError(t, err)
root, targets, snapshot, timestamp, err := getUpdates(r, tg, sn, ts)
assert.NoError(t, err)
// so a valid root, but missing the root role
signedRoot, err := data.RootFromSigned(r)
assert.NoError(t, err)
delete(signedRoot.Signed.Roles, data.CanonicalRootRole)
badRootJSON, err := json.Marshal(signedRoot)
assert.NoError(t, err)
badRoot := storage.MetaUpdate{
Version: root.Version,
Role: root.Role,
Data: badRootJSON,
}
store.UpdateCurrent("testGUN", badRoot)
updates := []storage.MetaUpdate{root, targets, snapshot, timestamp}
serverCrypto := copyKeys(t, cs, data.CanonicalTimestampRole)
_, err = validateUpdate(serverCrypto, "testGUN", updates, store)
assert.Error(t, err)
assert.IsType(t, data.ErrInvalidRole{}, err)
}
func TestValidateRootGetCurrentRootBroken(t *testing.T) {
repo, cs, err := testutils.EmptyRepo("docker.com/notary")
assert.NoError(t, err)
store := getFailStore{
MetaStore: storage.NewMemStorage(),
errsToReturn: map[string]error{data.CanonicalRootRole: data.ErrNoSuchRole{}},
}
r, tg, sn, ts, err := testutils.Sign(repo)
assert.NoError(t, err)
root, targets, snapshot, timestamp, err := getUpdates(r, tg, sn, ts)
assert.NoError(t, err)
updates := []storage.MetaUpdate{root, targets, snapshot, timestamp}
serverCrypto := copyKeys(t, cs, data.CanonicalTimestampRole)
_, err = validateUpdate(serverCrypto, "testGUN", updates, store)
assert.Error(t, err)
assert.IsType(t, data.ErrNoSuchRole{}, err)
}
func TestValidateRootRotation(t *testing.T) { func TestValidateRootRotation(t *testing.T) {
repo, crypto, err := testutils.EmptyRepo("docker.com/notary") repo, crypto, err := testutils.EmptyRepo("docker.com/notary")
assert.NoError(t, err) assert.NoError(t, err)
@ -324,6 +400,48 @@ func TestValidateRootRotation(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
} }
func TestInvalidRootRotation(t *testing.T) {
repo, crypto, err := testutils.EmptyRepo("docker.com/notary")
assert.NoError(t, err)
store := storage.NewMemStorage()
r, tg, sn, ts, err := testutils.Sign(repo)
assert.NoError(t, err)
root, targets, snapshot, timestamp, err := getUpdates(r, tg, sn, ts)
assert.NoError(t, err)
store.UpdateCurrent("testGUN", root)
rootKey, err := crypto.Create("root", data.ED25519Key)
assert.NoError(t, err)
rootRole, err := data.NewRole("root", 1, []string{rootKey.ID()}, nil)
assert.NoError(t, err)
repo.Root.Signed.Roles["root"] = &rootRole.RootRole
repo.Root.Signed.Keys[rootKey.ID()] = rootKey
r, err = repo.SignRoot(data.DefaultExpires(data.CanonicalRootRole))
assert.NoError(t, err)
err = signed.Sign(crypto, r, rootKey)
assert.NoError(t, err)
rt, err := data.RootFromSigned(r)
assert.NoError(t, err)
repo.SetRoot(rt)
sn, err = repo.SignSnapshot(data.DefaultExpires(data.CanonicalSnapshotRole))
assert.NoError(t, err)
root, targets, snapshot, timestamp, err = getUpdates(r, tg, sn, ts)
assert.NoError(t, err)
updates := []storage.MetaUpdate{root, targets, snapshot, timestamp}
serverCrypto := copyKeys(t, crypto, data.CanonicalTimestampRole)
_, err = validateUpdate(serverCrypto, "testGUN", updates, store)
assert.Error(t, err)
assert.Contains(t, err.Error(), "new root was not signed with at least 1 old keys")
}
func TestValidateNoRoot(t *testing.T) { func TestValidateNoRoot(t *testing.T) {
repo, cs, err := testutils.EmptyRepo("docker.com/notary") repo, cs, err := testutils.EmptyRepo("docker.com/notary")
assert.NoError(t, err) assert.NoError(t, err)