Repository: docker/notary
Pull request #335 (2015-12-04 02:45:08)

Client supports server snapshot
This adds support to `NotaryRepository.Initialize` and `NotaryRepository.Publish` for server signing snapshots

endophage: Taking a look at the `serverManagesSnapshot` stuff. I think this is going to need to be determined more dynamically. I figured there would be a command that creates a change object to rotate the snapshot key to one generated on the server (essentially pushes snapshot responsibility to the server), then on publish we just wouldn't push a snapshot if we didn't have the key, and let the server tell us whether that represents a valid update.
cyli: @endpohage:  Good point, thanks!  I moved that to the Initialize function instead.  Publish just tries to do its best to sign a snapshot, and then let's the server accept or not.
cyli: @endophage @diogomonica Probably ready for another look?

Patch:
diff --git a/client/client.go b/client/client.go
index 1ee9c735..af5e5985 100644
--- a/client/client.go
+++ b/client/client.go
@@ -53,6 +53,17 @@ type ErrExpired struct {
 	signed.ErrExpired
 }
 
+// ErrInvalidRemoteRole is returned when the server is requested to manage
+// an unsupported key type
+type ErrInvalidRemoteRole struct {
+	Role string
+}
+
+func (e ErrInvalidRemoteRole) Error() string {
+	return fmt.Sprintf(
+		"notary does not support the server managing the %s key", e.Role)
+}
+
 const (
 	tufDir = "tuf"
 )
@@ -137,15 +148,43 @@ func NewTarget(targetName string, targetPath string) (*Target, error) {
 
 // Initialize creates a new repository by using rootKey as the root Key for the
 // TUF repository.
-func (r *NotaryRepository) Initialize(rootKeyID string) error {
+func (r *NotaryRepository) Initialize(rootKeyID string, serverManagedRoles ...string) error {
 	privKey, _, err := r.CryptoService.GetPrivateKey(rootKeyID)
 	if err != nil {
 		return err
 	}
 
+	// currently we only support server managing timestamps and snapshots, and
+	// nothing else - timestamps are always managed by the server, and implicit
+	// (do not have to be passed in as part of `serverManagedRoles`, so that
+	// the API of Initialize doens't change).
+	var serverManagesSnapshot bool
+	locallyManagedKeys := []string{
+		data.CanonicalTargetsRole,
+		data.CanonicalSnapshotRole,
+		// root is also locally managed, but that should have been created
+		// already
+	}
+	remotelyManagedKeys := []string{data.CanonicalTimestampRole}
+	for _, role := range serverManagedRoles {
+		switch role {
+		case data.CanonicalTimestampRole:
+			continue // timestamp is already in the right place
+		case data.CanonicalSnapshotRole:
+			// because we put Snapshot last
+			locallyManagedKeys = []string{data.CanonicalTargetsRole}
+			remotelyManagedKeys = append(
+				remotelyManagedKeys, data.CanonicalSnapshotRole)
+			serverManagesSnapshot = true
+		default:
+			return ErrInvalidRemoteRole{Role: role}
+		}
+	}
+
 	// Hard-coded policy: the generated certificate expires in 10 years.
 	startTime := time.Now()
-	rootCert, err := cryptoservice.GenerateCertificate(privKey, r.gun, startTime, startTime.AddDate(10, 0, 0))
+	rootCert, err := cryptoservice.GenerateCertificate(
+		privKey, r.gun, startTime, startTime.AddDate(10, 0, 0))
 
 	if err != nil {
 		return err
@@ -167,44 +206,35 @@ func (r *NotaryRepository) Initialize(rootKeyID string) error {
 		return fmt.Errorf("invalid format for root key: %s", privKey.Algorithm())
 	}
 
-	// All the timestamp keys are generated by the remote server.
-	remote, err := getRemoteStore(r.baseURL, r.gun, r.roundTrip)
-	if err != nil {
-		return err
-	}
-	rawTSKey, err := remote.GetKey("timestamp")
-	if err != nil {
-		return err
-	}
-
-	timestampKey, err := data.UnmarshalPublicKey(rawTSKey)
+	kdb := keys.NewDB()
+	err = addKeyForRole(kdb, data.CanonicalRootRole, rootKey)
 	if err != nil {
 		return err
 	}
 
-	logrus.Debugf("got remote %s timestamp key with keyID: %s", timestampKey.Algorithm(), timestampKey.ID())
-
-	// This is currently hardcoding the targets and snapshots keys to ECDSA
-	// Targets and snapshot keys are always generated locally.
-	targetsKey, err := r.CryptoService.Create("targets", data.ECDSAKey)
-	if err != nil {
-		return err
-	}
-	snapshotKey, err := r.CryptoService.Create("snapshot", data.ECDSAKey)
-	if err != nil {
-		return err
+	// we want to create all the local keys first so we don't have to
+	// make unnecessary network calls
+	for _, role := range locallyManagedKeys {
+		// This is currently hardcoding the keys to ECDSA.
+		key, err := r.CryptoService.Create(role, data.ECDSAKey)
+		if err != nil {
+			return err
+		}
+		if err := addKeyForRole(kdb, role, key); err != nil {
+			return err
+		}
 	}
-
-	kdb := keys.NewDB()
-
-	kdb.AddKey(rootKey)
-	kdb.AddKey(targetsKey)
-	kdb.AddKey(snapshotKey)
-	kdb.AddKey(timestampKey)
-
-	err = initRoles(kdb, rootKey, targetsKey, snapshotKey, timestampKey)
-	if err != nil {
-		return err
+	for _, role := range remotelyManagedKeys {
+		// This key is generated by the remote server.
+		key, err := getRemoteKey(r.baseURL, r.gun, role, r.roundTrip)
+		if err != nil {
+			return err
+		}
+		logrus.Debugf("got remote %s %s key with keyID: %s",
+			role, key.Algorithm(), key.ID())
+		if err := addKeyForRole(kdb, role, key); err != nil {
+			return err
+		}
 	}
 
 	r.tufRepo = tuf.NewRepo(kdb, r.CryptoService)
@@ -225,7 +255,7 @@ func (r *NotaryRepository) Initialize(rootKeyID string) error {
 		return err
 	}
 
-	return r.saveMetadata()
+	return r.saveMetadata(serverManagesSnapshot)
 }
 
 // AddTarget adds a new target to the repository, forcing a timestamps check from TUF
@@ -331,28 +361,24 @@ func (r *NotaryRepository) GetChangelist() (changelist.Changelist, error) {
 // Conceptually it performs an operation similar to a `git rebase`
 func (r *NotaryRepository) Publish() error {
 	var updateRoot bool
-	var root *data.Signed
 	// attempt to initialize the repo from the remote store
 	c, err := r.bootstrapClient()
 	if err != nil {
 		if _, ok := err.(store.ErrMetaNotFound); ok {
 			// if the remote store return a 404 (translated into ErrMetaNotFound),
-			// the repo hasn't been initialized yet. Attempt to load it from disk.
+			// there is no trust data for yet. Attempt to load it from disk.
 			err := r.bootstrapRepo()
 			if err != nil {
-				// Repo hasn't been initialized, It must be initialized before
-				// it can be published. Return an error and let caller determine
-				// what it wants to do.
-				logrus.Debug(err.Error())
-				logrus.Debug("Repository not initialized during Publish")
-				return &ErrRepoNotInitialized{}
-			}
-			// We had local data but the server doesn't know about the repo yet,
-			// ensure we will push the initial root file
-			root, err = r.tufRepo.Root.ToSigned()
-			if err != nil {
+				// There are lots of reasons there might be an error, such as
+				// corrupt metadata.  We need better errors from bootstrapRepo.
+				logrus.Debugf("Unable to load repository from local files: %s",
+					err.Error())
 				return err
 			}
+			// We had local data but the server doesn't know about the repo yet,
+			// ensure we will push the initial root file.  The root may not
+			// be marked as Dirty, since there may not be any changes that
+			// update it, so use a different boolean.
 			updateRoot = true
 		} else {
 			// The remote store returned an error other than 404. We're
@@ -362,8 +388,8 @@ func (r *NotaryRepository) Publish() error {
 		}
 	} else {
 		// If we were successfully able to bootstrap the client (which only pulls
-		// root.json), update it the rest of the tuf metadata in preparation for
-		// applying the changelist.
+		// root.json), update it with the rest of the tuf metadata in
+		// preparation for applying the changelist.
 		err = c.Update()
 		if err != nil {
 			if err, ok := err.(signed.ErrExpired); ok {
@@ -383,53 +409,58 @@ func (r *NotaryRepository) Publish() error {
 		return err
 	}
 
+	// these are the tuf files we will need to update, serialized as JSON before
+	// we send anything to remote
+	updatedFiles := make(map[string][]byte)
+
 	// check if our root file is nearing expiry. Resign if it is.
-	if nearExpiry(r.tufRepo.Root) || r.tufRepo.Root.Dirty {
+	if nearExpiry(r.tufRepo.Root) || r.tufRepo.Root.Dirty || updateRoot {
+		rootJSON, err := serializeCanonicalRole(r.tufRepo, data.CanonicalRootRole)
 		if err != nil {
 			return err
 		}
-		root, err = r.tufRepo.SignRoot(data.DefaultExpires("root"))
-		if err != nil {
-			return err
-		}
-		updateRoot = true
-	}
-	// we will always resign targets and snapshots
-	targets, err := r.tufRepo.SignTargets("targets", data.DefaultExpires("targets"))
-	if err != nil {
-		return err
+		updatedFiles[data.CanonicalRootRole] = rootJSON
 	}
-	snapshot, err := r.tufRepo.SignSnapshot(data.DefaultExpires("snapshot"))
+
+	// we will always re-sign targets
+	targetsJSON, err := serializeCanonicalRole(r.tufRepo, data.CanonicalTargetsRole)
 	if err != nil {
 		return err
 	}
+	updatedFiles[data.CanonicalTargetsRole] = targetsJSON
 
-	remote, err := getRemoteStore(r.baseURL, r.gun, r.roundTrip)
-	if err != nil {
-		return err
+	// if we initialized the repo while designating the server as the snapshot
+	// signer, then there won't be a snapshots file.  However, we might now
+	// have a local key (if there was a rotation), so initialize one.
+	if r.tufRepo.Snapshot == nil {
+		if err := r.tufRepo.InitSnapshot(); err != nil {
+			return err
+		}
 	}
 
-	// ensure we can marshal all the json before sending anything to remote
-	targetsJSON, err := json.Marshal(targets)
-	if err != nil {
+	snapshotJSON, err := serializeCanonicalRole(
+		r.tufRepo, data.CanonicalSnapshotRole)
+
+	if err == nil {
+		// Only update the snapshot if we've sucessfully signed it.
+		updatedFiles[data.CanonicalSnapshotRole] = snapshotJSON
+	} else if _, ok := err.(signed.ErrNoKeys); ok {
+		// If signing fails due to us not having the snapshot key, then
+		// assume the server is going to sign, and do not include any snapshot
+		// data.
+		logrus.Debugf("Client does not have the key to sign snapshot. " +
+			"Assuming that server should sign the snapshot.")
+	} else {
+		logrus.Debugf("Client was unable to sign the snapshot: %s", err.Error())
 		return err
 	}
-	snapshotJSON, err := json.Marshal(snapshot)
+
+	remote, err := getRemoteStore(r.baseURL, r.gun, r.roundTrip)
 	if err != nil {
 		return err
 	}
-	update := make(map[string][]byte)
-	// if we need to update the root, marshal it and push the update to remote
-	if updateRoot {
-		rootJSON, err := json.Marshal(root)
-		if err != nil {
-			return err
-		}
-		update["root"] = rootJSON
-	}
-	update["targets"] = targetsJSON
-	update["snapshot"] = snapshotJSON
-	err = remote.SetMultiMeta(update)
+
+	err = remote.SetMultiMeta(updatedFiles)
 	if err != nil {
 		return err
 	}
@@ -443,6 +474,11 @@ func (r *NotaryRepository) Publish() error {
 	return nil
 }
 
+// bootstrapRepo loads the repository from the local file system.  This attempts
+// to load metadata for all roles.  Since server snapshots are supported,
+// if the snapshot metadata fails to load, that's ok.
+// This can also be unified with some cache reading tools from tuf/client.
+// This assumes that bootstrapRepo is only used by Publish()
 func (r *NotaryRepository) bootstrapRepo() error {
 	kdb := keys.NewDB()
 	tufRepo := tuf.NewRepo(kdb, r.CryptoService)
@@ -471,30 +507,32 @@ func (r *NotaryRepository) bootstrapRepo() error {
 		return err
 	}
 	tufRepo.SetTargets("targets", targets)
+
 	snapshotJSON, err := r.fileStore.GetMeta("snapshot", 0)
-	if err != nil {
-		return err
-	}
-	snapshot := &data.SignedSnapshot{}
-	err = json.Unmarshal(snapshotJSON, snapshot)
-	if err != nil {
+	if err == nil {
+		snapshot := &data.SignedSnapshot{}
+		err = json.Unmarshal(snapshotJSON, snapshot)
+		if err != nil {
+			return err
+		}
+		tufRepo.SetSnapshot(snapshot)
+	} else if _, ok := err.(store.ErrMetaNotFound); !ok {
 		return err
 	}
-	tufRepo.SetSnapshot(snapshot)
 
 	r.tufRepo = tufRepo
 
 	return nil
 }
 
-func (r *NotaryRepository) saveMetadata() error {
+func (r *NotaryRepository) saveMetadata(ignoreSnapshot bool) error {
 	logrus.Debugf("Saving changes to Trusted Collection.")
 
-	signedRoot, err := r.tufRepo.SignRoot(data.DefaultExpires("root"))
+	rootJSON, err := serializeCanonicalRole(r.tufRepo, data.CanonicalRootRole)
 	if err != nil {
 		return err
 	}
-	rootJSON, err := json.Marshal(signedRoot)
+	err = r.fileStore.SetMeta(data.CanonicalRootRole, rootJSON)
 	if err != nil {
 		return err
 	}
@@ -512,27 +550,22 @@ func (r *NotaryRepository) saveMetadata() error {
 		targetsToSave[t] = targetsJSON
 	}
 
-	signedSnapshot, err := r.tufRepo.SignSnapshot(data.DefaultExpires("snapshot"))
-	if err != nil {
-		return err
+	for role, blob := range targetsToSave {
+		parentDir := filepath.Dir(role)
+		os.MkdirAll(parentDir, 0755)
+		r.fileStore.SetMeta(role, blob)
 	}
-	snapshotJSON, err := json.Marshal(signedSnapshot)
-	if err != nil {
-		return err
+
+	if ignoreSnapshot {
+		return nil
 	}
 
-	err = r.fileStore.SetMeta("root", rootJSON)
+	snapshotJSON, err := serializeCanonicalRole(r.tufRepo, data.CanonicalSnapshotRole)
 	if err != nil {
 		return err
 	}
 
-	for role, blob := range targetsToSave {
-		parentDir := filepath.Dir(role)
-		os.MkdirAll(parentDir, 0755)
-		r.fileStore.SetMeta(role, blob)
-	}
-
-	return r.fileStore.SetMeta("snapshot", snapshotJSON)
+	return r.fileStore.SetMeta(data.CanonicalSnapshotRole, snapshotJSON)
 }
 
 func (r *NotaryRepository) bootstrapClient() (*tufclient.Client, error) {
diff --git a/client/helpers.go b/client/helpers.go
index 596e7ad2..8befcffb 100644
--- a/client/helpers.go
+++ b/client/helpers.go
@@ -2,6 +2,7 @@ package client
 
 import (
 	"encoding/json"
+	"fmt"
 	"net/http"
 	"time"
 
@@ -112,35 +113,55 @@ func nearExpiry(r *data.SignedRoot) bool {
 	return r.Signed.Expires.Before(plus6mo)
 }
 
-func initRoles(kdb *keys.KeyDB, rootKey, targetsKey, snapshotKey, timestampKey data.PublicKey) error {
-	rootRole, err := data.NewRole("root", 1, []string{rootKey.ID()}, nil, nil)
+// Fetches a public key from a remote store, given a gun and role
+func getRemoteKey(url, gun, role string, rt http.RoundTripper) (data.PublicKey, error) {
+	remote, err := getRemoteStore(url, gun, rt)
 	if err != nil {
-		return err
+		return nil, err
 	}
-	targetsRole, err := data.NewRole("targets", 1, []string{targetsKey.ID()}, nil, nil)
+	rawPubKey, err := remote.GetKey(role)
 	if err != nil {
-		return err
-	}
-	snapshotRole, err := data.NewRole("snapshot", 1, []string{snapshotKey.ID()}, nil, nil)
-	if err != nil {
-		return err
+		return nil, err
 	}
-	timestampRole, err := data.NewRole("timestamp", 1, []string{timestampKey.ID()}, nil, nil)
+
+	pubKey, err := data.UnmarshalPublicKey(rawPubKey)
 	if err != nil {
-		return err
+		return nil, err
 	}
 
-	if err := kdb.AddRole(rootRole); err != nil {
+	return pubKey, nil
+}
+
+// add a key to a KeyDB, and create a role for the key and add it.
+func addKeyForRole(kdb *keys.KeyDB, role string, key data.PublicKey) error {
+	theRole, err := data.NewRole(role, 1, []string{key.ID()}, nil, nil)
+	if err != nil {
 		return err
 	}
-	if err := kdb.AddRole(targetsRole); err != nil {
+	kdb.AddKey(key)
+	if err := kdb.AddRole(theRole); err != nil {
 		return err
 	}
-	if err := kdb.AddRole(snapshotRole); err != nil {
-		return err
+	return nil
+}
+
+// signs and serializes the metadata for a canonical role in a tuf repo to JSON
+func serializeCanonicalRole(tufRepo *tuf.Repo, role string) (out []byte, err error) {
+	var s *data.Signed
+	switch role {
+	case data.CanonicalRootRole:
+		s, err = tufRepo.SignRoot(data.DefaultExpires(role))
+	case data.CanonicalSnapshotRole:
+		s, err = tufRepo.SignSnapshot(data.DefaultExpires(role))
+	case data.CanonicalTargetsRole:
+		s, err = tufRepo.SignTargets(role, data.DefaultExpires(role))
+	default:
+		err = fmt.Errorf("%s not supported role to sign on the client", role)
 	}
-	if err := kdb.AddRole(timestampRole); err != nil {
-		return err
+
+	if err != nil {
+		return
 	}
-	return nil
+
+	return json.Marshal(s)
 }
diff --git a/server/errors/errors.go b/errors/errors.go
similarity index 92%
rename from server/errors/errors.go
rename to errors/errors.go
index f335c005..f9d35c18 100644
--- a/server/errors/errors.go
+++ b/errors/errors.go
@@ -50,12 +50,6 @@ var (
 		Description:    "The user requested metadata that is not known to the server.",
 		HTTPStatusCode: http.StatusNotFound,
 	})
-	ErrInvalidUpdate = errcode.Register(errGroup, errcode.ErrorDescriptor{
-		Value:          "INVALID_UPDATE",
-		Message:        "Update sent by the client is invalid.",
-		Description:    "The user-uploaded TUF data has been parsed but failed validation.",
-		HTTPStatusCode: http.StatusBadRequest,
-	})
 	ErrMalformedUpload = errcode.Register(errGroup, errcode.ErrorDescriptor{
 		Value:          "MALFORMED_UPLOAD",
 		Message:        "The body of your request is malformed.",
diff --git a/server/handlers/default.go b/server/handlers/default.go
index 5753a851..c15e3bd2 100644
--- a/server/handlers/default.go
+++ b/server/handlers/default.go
@@ -12,25 +12,24 @@ import (
 	"golang.org/x/net/context"
 
 	"github.com/Sirupsen/logrus"
-	"github.com/docker/notary/server/errors"
+	"github.com/docker/notary/errors"
 	"github.com/docker/notary/server/snapshot"
 	"github.com/docker/notary/server/storage"
 	"github.com/docker/notary/server/timestamp"
 	"github.com/docker/notary/tuf/data"
 	"github.com/docker/notary/tuf/signed"
-	"github.com/docker/notary/tuf/validation"
 )
 
 // MainHandler is the default handler for the server
 func MainHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
-	// For now it only supports `GET`
-	if r.Method != "GET" {
+	if r.Method == "GET" {
+		_, err := w.Write([]byte("{}"))
+		if err != nil {
+			return errors.ErrUnknown.WithDetail(err)
+		}
+	} else {
 		return errors.ErrGenericNotFound.WithDetail(nil)
 	}
-
-	if _, err := w.Write([]byte("{}")); err != nil {
-		return errors.ErrUnknown.WithDetail(err)
-	}
 	return nil
 }
 
@@ -88,15 +87,11 @@ func atomicUpdateHandler(ctx context.Context, w http.ResponseWriter, r *http.Req
 	}
 	updates, err = validateUpdate(cryptoService, gun, updates, store)
 	if err != nil {
-		serializable, serializableError := validation.NewSerializableError(err)
-		if serializableError != nil {
-			return errors.ErrInvalidUpdate.WithDetail(nil)
-		}
-		return errors.ErrInvalidUpdate.WithDetail(serializable)
+		return errors.ErrMalformedUpload.WithDetail(err)
 	}
 	err = store.UpdateMany(gun, updates)
 	if err != nil {
-		return errors.ErrUpdating.WithDetail(nil)
+		return errors.ErrUpdating.WithDetail(err)
 	}
 	return nil
 }
@@ -280,9 +275,3 @@ func getKeyHandler(ctx context.Context, w http.ResponseWriter, r *http.Request,
 	w.Write(out)
 	return nil
 }
-
-// NotFoundHandler is used as a generic catch all handler to return the ErrMetadataNotFound
-// 404 response
-func NotFoundHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
-	return errors.ErrMetadataNotFound.WithDetail(nil)
-}
diff --git a/server/handlers/errors.go b/server/handlers/errors.go
new file mode 100644
index 00000000..45b0ce4f
--- /dev/null
+++ b/server/handlers/errors.go
@@ -0,0 +1,56 @@
+package handlers
+
+import (
+	"fmt"
+)
+
+// VALIDATION ERRORS:
+
+// ErrValidation represents a general validation error
+type ErrValidation struct {
+	msg string
+}
+
+func (err ErrValidation) Error() string {
+	return fmt.Sprintf("An error occurred during validation: %s", err.msg)
+}
+
+// ErrBadHierarchy represents a missing snapshot at this current time.
+// When delegations are implemented it will also represent a missing
+// delegation parent
+type ErrBadHierarchy struct {
+	msg string
+}
+
+func (err ErrBadHierarchy) Error() string {
+	return fmt.Sprintf("Hierarchy of updates in incorrect: %s", err.msg)
+}
+
+// ErrBadRoot represents a failure validating the root
+type ErrBadRoot struct {
+	msg string
+}
+
+func (err ErrBadRoot) Error() string {
+	return fmt.Sprintf("The root being updated is invalid: %s", err.msg)
+}
+
+// ErrBadTargets represents a failure to validate a targets (incl delegations)
+type ErrBadTargets struct {
+	msg string
+}
+
+func (err ErrBadTargets) Error() string {
+	return fmt.Sprintf("The targets being updated is invalid: %s", err.msg)
+}
+
+// ErrBadSnapshot represents a failure to validate the snapshot
+type ErrBadSnapshot struct {
+	msg string
+}
+
+func (err ErrBadSnapshot) Error() string {
+	return fmt.Sprintf("The snapshot being updated is invalid: %s", err.msg)
+}
+
+// END VALIDATION ERRORS
diff --git a/server/handlers/validation.go b/server/handlers/validation.go
index 9ec1f268..91eedf73 100644
--- a/server/handlers/validation.go
+++ b/server/handlers/validation.go
@@ -15,7 +15,6 @@ import (
 	"github.com/docker/notary/tuf/keys"
 	"github.com/docker/notary/tuf/signed"
 	"github.com/docker/notary/tuf/utils"
-	"github.com/docker/notary/tuf/validation"
 )
 
 // validateUpload checks that the updates being pushed
@@ -50,26 +49,26 @@ func validateUpdate(cs signed.CryptoService, gun string, updates []storage.MetaU
 		// against a previous root
 		if root, err = validateRoot(gun, oldRootJSON, rootUpdate.Data, store); err != nil {
 			logrus.Error("ErrBadRoot: ", err.Error())
-			return nil, validation.ErrBadRoot{Msg: err.Error()}
+			return nil, ErrBadRoot{msg: err.Error()}
 		}
 
 		// setting root will update keys db
 		if err = repo.SetRoot(root); err != nil {
 			logrus.Error("ErrValidation: ", err.Error())
-			return nil, validation.ErrValidation{Msg: err.Error()}
+			return nil, ErrValidation{msg: err.Error()}
 		}
 		logrus.Debug("Successfully validated root")
 	} else {
 		if oldRootJSON == nil {
-			return nil, validation.ErrValidation{Msg: "no pre-existing root and no root provided in update."}
+			return nil, ErrValidation{msg: "no pre-existing root and no root provided in update."}
 		}
 		parsedOldRoot := &data.SignedRoot{}
 		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, ErrValidation{msg: "pre-existing root is corrupted and no root provided in update."}
 		}
 		if err = repo.SetRoot(parsedOldRoot); err != nil {
 			logrus.Error("ErrValidation: ", err.Error())
-			return nil, validation.ErrValidation{Msg: err.Error()}
+			return nil, ErrValidation{msg: err.Error()}
 		}
 	}
 
@@ -78,7 +77,7 @@ func validateUpdate(cs signed.CryptoService, gun string, updates []storage.MetaU
 	if _, ok := roles[targetsRole]; ok {
 		if t, err = validateTargets(targetsRole, roles, kdb); err != nil {
 			logrus.Error("ErrBadTargets: ", err.Error())
-			return nil, validation.ErrBadTargets{Msg: err.Error()}
+			return nil, ErrBadTargets{msg: err.Error()}
 		}
 		repo.SetTargets(targetsRole, t)
 	}
@@ -101,7 +100,7 @@ func validateUpdate(cs signed.CryptoService, gun string, updates []storage.MetaU
 
 		if err := validateSnapshot(snapshotRole, oldSnap, roles[snapshotRole], roles, kdb); err != nil {
 			logrus.Error("ErrBadSnapshot: ", err.Error())
-			return nil, validation.ErrBadSnapshot{Msg: err.Error()}
+			return nil, ErrBadSnapshot{msg: err.Error()}
 		}
 		logrus.Debug("Successfully validated snapshot")
 	} else {
@@ -155,12 +154,12 @@ func prepRepo(gun string, repo *tuf.Repo, store storage.MetaStore) error {
 func generateSnapshot(gun string, kdb *keys.KeyDB, repo *tuf.Repo, store storage.MetaStore) (*storage.MetaUpdate, error) {
 	role := kdb.GetRole(data.RoleName(data.CanonicalSnapshotRole))
 	if role == nil {
-		return nil, validation.ErrBadRoot{Msg: "root did not include snapshot role"}
+		return nil, ErrBadRoot{msg: "root did not include snapshot role"}
 	}
 
 	algo, keyBytes, err := store.GetKey(gun, data.CanonicalSnapshotRole)
 	if role == nil {
-		return nil, validation.ErrBadRoot{Msg: "root did not include snapshot key"}
+		return nil, ErrBadRoot{msg: "root did not include snapshot key"}
 	}
 	foundK := data.NewPublicKey(algo, keyBytes)
 
@@ -172,9 +171,7 @@ func generateSnapshot(gun string, kdb *keys.KeyDB, repo *tuf.Repo, store storage
 		}
 	}
 	if !validKey {
-		return nil, validation.ErrBadHierarchy{
-			Missing: data.CanonicalSnapshotRole,
-			Msg:     "no snapshot was included in update and server does not hold current snapshot key for repository"}
+		return nil, ErrBadHierarchy{msg: "no snapshot was included in update and server does not hold current snapshot key for repository"}
 	}
 
 	currentJSON, err := store.GetCurrent(gun, data.CanonicalSnapshotRole)
diff --git a/server/server.go b/server/server.go
index 4fb863b1..cb09a48e 100644
--- a/server/server.go
+++ b/server/server.go
@@ -105,8 +105,7 @@ func RootHandler(ac auth.AccessController, ctx context.Context, trust signed.Cry
 
 	r.Methods("GET").Path("/_notary_server/health").HandlerFunc(health.StatusHandler)
 	r.Methods("GET").Path("/_notary_server/metrics").Handler(prometheus.Handler())
-	r.Methods("GET", "POST", "PUT", "HEAD", "DELETE").Path("/{other:.*}").Handler(
-		hand(handlers.NotFoundHandler))
+	r.Methods("GET", "POST", "PUT", "HEAD", "DELETE").Path("/{other:.*}").Handler(hand(utils.NotFoundHandler))
 
 	return r
 }
diff --git a/signer/api/api.go b/signer/api/api.go
index 967fbbe7..6297f652 100644
--- a/signer/api/api.go
+++ b/signer/api/api.go
@@ -34,12 +34,14 @@ func getCryptoService(w http.ResponseWriter, algorithm string, cryptoServices si
 		return nil
 	}
 
-	if service, ok := cryptoServices[algorithm]; ok {
-		return service
+	service := cryptoServices[algorithm]
+
+	if service == nil {
+		http.Error(w, "algorithm "+algorithm+" not supported", http.StatusBadRequest)
+		return nil
 	}
 
-	http.Error(w, "algorithm "+algorithm+" not supported", http.StatusBadRequest)
-	return nil
+	return service
 }
 
 // KeyInfo returns a Handler that given a specific Key ID param, returns the public key bits of that key
diff --git a/signer/client/signer_trust.go b/signer/client/signer_trust.go
index a27297d7..99fc17af 100644
--- a/signer/client/signer_trust.go
+++ b/signer/client/signer_trust.go
@@ -107,7 +107,7 @@ type NotarySigner struct {
 	clientConn checkableConnectionState
 }
 
-// NewNotarySigner is a convenience method that returns NotarySigner
+// NewNotarySigner is a convinience method that returns NotarySigner
 func NewNotarySigner(hostname string, port string, tlsConfig *tls.Config) *NotarySigner {
 	var opts []grpc.DialOption
 	netAddr := net.JoinHostPort(hostname, port)
diff --git a/tuf/signed/errors.go b/tuf/signed/errors.go
index 346b1884..2c5174be 100644
--- a/tuf/signed/errors.go
+++ b/tuf/signed/errors.go
@@ -63,10 +63,10 @@ func (e ErrInvalidKeyLength) Error() string {
 
 // ErrNoKeys indicates no signing keys were found when trying to sign
 type ErrNoKeys struct {
-	keyIDs []string
+	KeyIDs []string
 }
 
 func (e ErrNoKeys) Error() string {
 	return fmt.Sprintf("could not find necessary signing keys, at least one of these keys must be available: %s",
-		strings.Join(e.keyIDs, ", "))
+		strings.Join(e.KeyIDs, ", "))
 }
diff --git a/tuf/signed/sign.go b/tuf/signed/sign.go
index 6c4d2eb7..9c840de6 100644
--- a/tuf/signed/sign.go
+++ b/tuf/signed/sign.go
@@ -46,7 +46,7 @@ func Sign(service CryptoService, s *data.Signed, keys ...data.PublicKey) error {
 
 	// Check to ensure we have at least one signing key
 	if len(privKeys) == 0 {
-		return ErrNoKeys{keyIDs: ids}
+		return ErrNoKeys{KeyIDs: ids}
 	}
 
 	// Do signing and generate list of signatures
diff --git a/tuf/store/errors.go b/tuf/store/errors.go
index b944c2d7..2e0eb0a3 100644
--- a/tuf/store/errors.go
+++ b/tuf/store/errors.go
@@ -1,9 +1,13 @@
 package store
 
+import "fmt"
+
 // ErrMetaNotFound indicates we did not find a particular piece
 // of metadata in the store
-type ErrMetaNotFound struct{}
+type ErrMetaNotFound struct {
+	Role string
+}
 
 func (err ErrMetaNotFound) Error() string {
-	return "no trust data available"
+	return fmt.Sprintf("no %s trust data available", err.Role)
 }
diff --git a/tuf/store/filestore.go b/tuf/store/filestore.go
index ff3f785f..de9d7a52 100644
--- a/tuf/store/filestore.go
+++ b/tuf/store/filestore.go
@@ -45,6 +45,9 @@ func (f *FilesystemStore) GetMeta(name string, size int64) ([]byte, error) {
 	path := filepath.Join(f.metaDir, fileName)
 	meta, err := ioutil.ReadFile(path)
 	if err != nil {
+		if os.IsNotExist(err) {
+			err = ErrMetaNotFound{Role: name}
+		}
 		return nil, err
 	}
 	return meta, nil
diff --git a/tuf/store/httpstore.go b/tuf/store/httpstore.go
index 56ea40ea..460c7826 100644
--- a/tuf/store/httpstore.go
+++ b/tuf/store/httpstore.go
@@ -1,18 +1,7 @@
-// A Store that can fetch and set metadata on a remote server.
-// Some API constraints:
-// - Response bodies for error codes should be unmarshallable as:
-//   {"errors": [{..., "detail": <serialized validation error>}]}
-//   else validation error details, etc. will be unparsable.  The errors
-//   should have a github.com/docker/notary/tuf/validation/SerializableError
-//   in the Details field.
-//   If writing your own server, please have a look at
-//   github.com/docker/distribution/registry/api/errcode
-
 package store
 
 import (
 	"bytes"
-	"encoding/json"
 	"errors"
 	"fmt"
 	"io"
@@ -23,7 +12,6 @@ import (
 	"path"
 
 	"github.com/Sirupsen/logrus"
-	"github.com/docker/notary/tuf/validation"
 )
 
 // ErrServerUnavailable indicates an error from the server. code allows us to
@@ -46,7 +34,7 @@ func (err ErrMaliciousServer) Error() string {
 }
 
 // ErrInvalidOperation indicates that the server returned a 400 response and
-// propagate any body we received.
+// propogate any body we received.
 type ErrInvalidOperation struct {
 	msg string
 }
@@ -95,29 +83,6 @@ func NewHTTPStore(baseURL, metaPrefix, metaExtension, targetsPrefix, keyExtensio
 	}, nil
 }
 
-func tryUnmarshalError(resp *http.Response, defaultError error) error {
-	bodyBytes, err := ioutil.ReadAll(resp.Body)
-	if err != nil {
-		return defaultError
-	}
-	var parsedErrors struct {
-		Errors []struct {
-			Detail validation.SerializableError `json:"detail"`
-		} `json:"errors"`
-	}
-	if err := json.Unmarshal(bodyBytes, &parsedErrors); err != nil {
-		return defaultError
-	}
-	if len(parsedErrors.Errors) != 1 {
-		return defaultError
-	}
-	err = parsedErrors.Errors[0].Detail.Error
-	if err == nil {
-		return defaultError
-	}
-	return err
-}
-
 func translateStatusToError(resp *http.Response) error {
 	switch resp.StatusCode {
 	case http.StatusOK:
@@ -125,7 +90,7 @@ func translateStatusToError(resp *http.Response) error {
 	case http.StatusNotFound:
 		return ErrMetaNotFound{}
 	case http.StatusBadRequest:
-		return tryUnmarshalError(resp, ErrInvalidOperation{})
+		return ErrInvalidOperation{}
 	default:
 		return ErrServerUnavailable{code: resp.StatusCode}
 	}
@@ -182,39 +147,29 @@ func (s HTTPStore) SetMeta(name string, blob []byte) error {
 	return translateStatusToError(resp)
 }
 
-// NewMultiPartMetaRequest builds a request with the provided metadata updates
-// in multipart form
-func NewMultiPartMetaRequest(url string, metas map[string][]byte) (*http.Request, error) {
+// SetMultiMeta does a single batch upload of multiple pieces of TUF metadata.
+// This should be preferred for updating a remote server as it enable the server
+// to remain consistent, either accepting or rejecting the complete update.
+func (s HTTPStore) SetMultiMeta(metas map[string][]byte) error {
+	url, err := s.buildMetaURL("")
+	if err != nil {
+		return err
+	}
 	body := &bytes.Buffer{}
 	writer := multipart.NewWriter(body)
 	for role, blob := range metas {
 		part, err := writer.CreateFormFile("files", role)
 		_, err = io.Copy(part, bytes.NewBuffer(blob))
 		if err != nil {
-			return nil, err
+			return err
 		}
 	}
-	err := writer.Close()
-	if err != nil {
-		return nil, err
-	}
-	req, err := http.NewRequest("POST", url, body)
-	if err != nil {
-		return nil, err
-	}
-	req.Header.Set("Content-Type", writer.FormDataContentType())
-	return req, nil
-}
-
-// SetMultiMeta does a single batch upload of multiple pieces of TUF metadata.
-// This should be preferred for updating a remote server as it enable the server
-// to remain consistent, either accepting or rejecting the complete update.
-func (s HTTPStore) SetMultiMeta(metas map[string][]byte) error {
-	url, err := s.buildMetaURL("")
+	err = writer.Close()
 	if err != nil {
 		return err
 	}
-	req, err := NewMultiPartMetaRequest(url.String(), metas)
+	req, err := http.NewRequest("POST", url.String(), body)
+	req.Header.Set("Content-Type", writer.FormDataContentType())
 	if err != nil {
 		return err
 	}
diff --git a/tuf/validation/errors.go b/tuf/validation/errors.go
deleted file mode 100644
index 471b9c9c..00000000
--- a/tuf/validation/errors.go
+++ /dev/null
@@ -1,126 +0,0 @@
-package validation
-
-import (
-	"encoding/json"
-	"fmt"
-)
-
-// VALIDATION ERRORS
-
-// ErrValidation represents a general validation error
-type ErrValidation struct {
-	Msg string
-}
-
-func (err ErrValidation) Error() string {
-	return fmt.Sprintf("An error occurred during validation: %s", err.Msg)
-}
-
-// ErrBadHierarchy represents missing metadata.  Currently: a missing snapshot
-// at this current time. When delegations are implemented it will also
-// represent a missing delegation parent
-type ErrBadHierarchy struct {
-	Missing string
-	Msg     string
-}
-
-func (err ErrBadHierarchy) Error() string {
-	return fmt.Sprintf("Metadata hierarchy is incomplete: %s", err.Msg)
-}
-
-// ErrBadRoot represents a failure validating the root
-type ErrBadRoot struct {
-	Msg string
-}
-
-func (err ErrBadRoot) Error() string {
-	return fmt.Sprintf("The root metadata is invalid: %s", err.Msg)
-}
-
-// ErrBadTargets represents a failure to validate a targets (incl delegations)
-type ErrBadTargets struct {
-	Msg string
-}
-
-func (err ErrBadTargets) Error() string {
-	return fmt.Sprintf("The targets metadata is invalid: %s", err.Msg)
-}
-
-// ErrBadSnapshot represents a failure to validate the snapshot
-type ErrBadSnapshot struct {
-	Msg string
-}
-
-func (err ErrBadSnapshot) Error() string {
-	return fmt.Sprintf("The snapshot metadata is invalid: %s", err.Msg)
-}
-
-// END VALIDATION ERRORS
-
-// SerializableError is a struct that can be used to serialize an error as JSON
-type SerializableError struct {
-	Name  string
-	Error error
-}
-
-// UnmarshalJSON attempts to unmarshal the error into the right type
-func (s *SerializableError) UnmarshalJSON(text []byte) (err error) {
-	var x struct{ Name string }
-	err = json.Unmarshal(text, &x)
-	if err != nil {
-		return
-	}
-	var theError error
-	switch x.Name {
-	case "ErrValidation":
-		var e struct{ Error ErrValidation }
-		err = json.Unmarshal(text, &e)
-		theError = e.Error
-	case "ErrBadHierarchy":
-		var e struct{ Error ErrBadHierarchy }
-		err = json.Unmarshal(text, &e)
-		theError = e.Error
-	case "ErrBadRoot":
-		var e struct{ Error ErrBadRoot }
-		err = json.Unmarshal(text, &e)
-		theError = e.Error
-	case "ErrBadTargets":
-		var e struct{ Error ErrBadTargets }
-		err = json.Unmarshal(text, &e)
-		theError = e.Error
-	case "ErrBadSnapshot":
-		var e struct{ Error ErrBadSnapshot }
-		err = json.Unmarshal(text, &e)
-		theError = e.Error
-	default:
-		err = fmt.Errorf("do not know how to unmarshall %s", x.Name)
-		return
-	}
-	if err != nil {
-		return
-	}
-	s.Name = x.Name
-	s.Error = theError
-	return nil
-}
-
-// NewSerializableError serializes one of the above errors into JSON
-func NewSerializableError(err error) (*SerializableError, error) {
-	// make sure it's one of our errors
-	var name string
-	switch err.(type) {
-	case ErrValidation:
-		name = "ErrValidation"
-	case ErrBadHierarchy:
-		name = "ErrBadHierarchy"
-	case ErrBadRoot:
-		name = "ErrBadRoot"
-	case ErrBadTargets:
-		name = "ErrBadTargets"
-	case ErrBadSnapshot:
-		name = "ErrBadSnapshot"
-	default:
-		return nil, fmt.Errorf("does not support serializing non-validation errors")
-	}
-	return &SerializableError{Name: name, Error: err}, nil
-}
diff --git a/utils/http.go b/utils/http.go
index 5bf191fd..6efd0b6f 100644
--- a/utils/http.go
+++ b/utils/http.go
@@ -8,6 +8,7 @@ import (
 	"github.com/docker/distribution/registry/api/errcode"
 	"github.com/docker/distribution/registry/api/v2"
 	"github.com/docker/distribution/registry/auth"
+	"github.com/docker/notary/errors"
 	"github.com/docker/notary/tuf/signed"
 	"github.com/gorilla/mux"
 	"golang.org/x/net/context"
@@ -94,3 +95,9 @@ func buildAccessRecords(repo string, actions ...string) []auth.Access {
 	}
 	return requiredAccess
 }
+
+// NotFoundHandler is used as a generic catch all handler to return the ErrMetadataNotFound
+// 404 response
+func NotFoundHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
+	return errors.ErrMetadataNotFound.WithDetail(nil)
+}


Test patch:
diff --git a/client/client_root_validation_test.go b/client/client_root_validation_test.go
index 1900bd5b..f50dbe6a 100644
--- a/client/client_root_validation_test.go
+++ b/client/client_root_validation_test.go
@@ -36,7 +36,7 @@ func validateRootSuccessfully(t *testing.T, rootType string) {
 	ts, mux, keys := simpleTestServer(t)
 	defer ts.Close()
 
-	repo, _ := initializeRepo(t, rootType, tempBaseDir, gun, ts.URL)
+	repo, _ := initializeRepo(t, rootType, tempBaseDir, gun, ts.URL, false)
 
 	// tests need to manually boostrap timestamp as client doesn't generate it
 	err = repo.tufRepo.InitTimestamp()
diff --git a/client/client_test.go b/client/client_test.go
index 39bf7528..1ccc1149 100644
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -3,6 +3,7 @@ package client
 import (
 	"bytes"
 	"crypto/rand"
+	regJson "encoding/json"
 	"fmt"
 	"io/ioutil"
 	"net/http"
@@ -10,7 +11,6 @@ import (
 	"os"
 	"path/filepath"
 	"sort"
-	"strings"
 	"testing"
 
 	"github.com/Sirupsen/logrus"
@@ -23,6 +23,7 @@ import (
 	"github.com/docker/notary/server/storage"
 	"github.com/docker/notary/trustmanager"
 	"github.com/docker/notary/tuf/data"
+	"github.com/docker/notary/tuf/signed"
 	"github.com/docker/notary/tuf/store"
 	"github.com/jfrazelle/go/canonical/json"
 	"github.com/stretchr/testify/assert"
@@ -88,7 +89,24 @@ func errorTestServer(t *testing.T, errorCode int) *httptest.Server {
 	return server
 }
 
-func initializeRepo(t *testing.T, rootType, tempBaseDir, gun, url string) (
+func initializeRepo(t *testing.T, rootType, tempBaseDir, gun, url string,
+	serverManagesSnapshot bool) (*NotaryRepository, string) {
+
+	serverManagedRoles := []string{}
+	if serverManagesSnapshot {
+		serverManagedRoles = []string{data.CanonicalSnapshotRole}
+	}
+
+	repo, rootPubKeyID := createRepoAndKey(t, rootType, tempBaseDir, gun, url)
+
+	err := repo.Initialize(rootPubKeyID, serverManagedRoles...)
+	assert.NoError(t, err, "error creating repository: %s", err)
+
+	return repo, rootPubKeyID
+}
+
+// Creates a new repository and adds a root key.  Returns the repo and key ID.
+func createRepoAndKey(t *testing.T, rootType, tempBaseDir, gun, url string) (
 	*NotaryRepository, string) {
 
 	repo, err := NewNotaryRepository(
@@ -98,26 +116,104 @@ func initializeRepo(t *testing.T, rootType, tempBaseDir, gun, url string) (
 	rootPubKey, err := repo.CryptoService.Create("root", rootType)
 	assert.NoError(t, err, "error generating root key: %s", err)
 
-	err = repo.Initialize(rootPubKey.ID())
-	assert.NoError(t, err, "error creating repository: %s", err)
-
 	return repo, rootPubKey.ID()
 }
 
-// TestInitRepo runs through the process of initializing a repository and makes
-// sure the repository looks correct on disk.
-// We test this with both an RSA and ECDSA root key
-func TestInitRepo(t *testing.T) {
-	testInitRepo(t, data.ECDSAKey)
+// Initializing a new repo while specifying that the server should manage the root
+// role will fail.
+func TestInitRepositoryManagedRolesIncludingRoot(t *testing.T) {
+	// Temporary directory where test files will be created
+	tempBaseDir, err := ioutil.TempDir("/tmp", "notary-test-")
+	assert.NoError(t, err, "failed to create a temporary directory")
+	defer os.RemoveAll(tempBaseDir)
+
+	repo, rootPubKeyID := createRepoAndKey(
+		t, data.ECDSAKey, tempBaseDir, "docker.com/notary", "http://localhost")
+	err = repo.Initialize(rootPubKeyID, data.CanonicalRootRole)
+	assert.Error(t, err)
+	assert.IsType(t, ErrInvalidRemoteRole{}, err)
+	// Just testing the error message here in this one case
+	assert.Equal(t, err.Error(),
+		"notary does not support the server managing the root key")
+}
+
+// Initializing a new repo while specifying that the server should manage some
+// invalid role will fail.
+func TestInitRepositoryManagedRolesInvalidRole(t *testing.T) {
+	// Temporary directory where test files will be created
+	tempBaseDir, err := ioutil.TempDir("/tmp", "notary-test-")
+	assert.NoError(t, err, "failed to create a temporary directory")
+	defer os.RemoveAll(tempBaseDir)
+
+	repo, rootPubKeyID := createRepoAndKey(
+		t, data.ECDSAKey, tempBaseDir, "docker.com/notary", "http://localhost")
+	err = repo.Initialize(rootPubKeyID, "randomrole")
+	assert.Error(t, err)
+	assert.IsType(t, ErrInvalidRemoteRole{}, err)
+}
+
+// Initializing a new repo while specifying that the server should manage the
+// targets role will fail.
+func TestInitRepositoryManagedRolesIncludingTargets(t *testing.T) {
+	// Temporary directory where test files will be created
+	tempBaseDir, err := ioutil.TempDir("/tmp", "notary-test-")
+	assert.NoError(t, err, "failed to create a temporary directory")
+	defer os.RemoveAll(tempBaseDir)
+
+	repo, rootPubKeyID := createRepoAndKey(
+		t, data.ECDSAKey, tempBaseDir, "docker.com/notary", "http://localhost")
+	err = repo.Initialize(rootPubKeyID, data.CanonicalTargetsRole)
+	assert.Error(t, err)
+	assert.IsType(t, ErrInvalidRemoteRole{}, err)
+}
+
+// Initializing a new repo while specifying that the server should manage the
+// timestamp key is fine - that's what it already does, so no error.
+func TestInitRepositoryManagedRolesIncludingTimestamp(t *testing.T) {
+	// Temporary directory where test files will be created
+	tempBaseDir, err := ioutil.TempDir("/tmp", "notary-test-")
+	assert.NoError(t, err, "failed to create a temporary directory")
+	defer os.RemoveAll(tempBaseDir)
+
+	ts, _, _ := simpleTestServer(t)
+	defer ts.Close()
+
+	repo, rootPubKeyID := createRepoAndKey(
+		t, data.ECDSAKey, tempBaseDir, "docker.com/notary", ts.URL)
+	err = repo.Initialize(rootPubKeyID, data.CanonicalTimestampRole)
+	assert.NoError(t, err)
+}
+
+// passing timestamp + snapshot, or just snapshot, is tested in the next two
+// test cases.
+
+// TestInitRepoServerOnlyManagesTimestampKey runs through the process of
+// initializing a repository and makes sure the repository looks correct on disk.
+// We test this with both an RSA and ECDSA root key.
+// This test case covers the default case where the server only manages the
+// timestamp key.
+func TestInitRepoServerOnlyManagesTimestampKey(t *testing.T) {
+	testInitRepo(t, data.ECDSAKey, false)
+	if !testing.Short() {
+		testInitRepo(t, data.RSAKey, false)
+	}
+}
+
+// TestInitRepoServerManagesTimestampAndSnapshotKeys runs through the process of
+// initializing a repository and makes sure the repository looks correct on disk.
+// We test this with both an RSA and ECDSA root key.
+// This test case covers the server managing both the timestap and snapshot keys.
+func TestInitRepoServerManagesTimestampAndSnapshotKeys(t *testing.T) {
+	testInitRepo(t, data.ECDSAKey, true)
 	if !testing.Short() {
-		testInitRepo(t, data.RSAKey)
+		testInitRepo(t, data.RSAKey, true)
 	}
 }
 
 // This creates a new KeyFileStore in the repo's base directory and makes sure
 // the repo has the right number of keys
 func assertRepoHasExpectedKeys(t *testing.T, repo *NotaryRepository,
-	rootKeyID string) {
+	rootKeyID string, expectedSnapshotKey bool) {
 
 	// The repo should have a keyFileStore and have created keys using it,
 	// so create a new KeyFileStore, and check that the keys do exist and are
@@ -137,17 +233,27 @@ func assertRepoHasExpectedKeys(t *testing.T, repo *NotaryRepository,
 		roles[role] = true
 	}
 	// there is a root key and a targets key
-	for _, role := range data.ValidRoles {
-		if role != data.CanonicalTimestampRole {
-			_, ok := roles[role]
-			assert.True(t, ok, fmt.Sprintf("missing %s key", role))
-		}
+	alwaysThere := []string{data.CanonicalRootRole, data.CanonicalTargetsRole}
+	for _, role := range alwaysThere {
+		_, ok := roles[role]
+		assert.True(t, ok, "missing %s key", role)
+	}
+
+	// there may be a snapshots key, depending on whether the server is managing
+	// the snapshots key
+	_, ok := roles[data.CanonicalSnapshotRole]
+	if expectedSnapshotKey {
+		assert.True(t, ok, "missing snapshot key")
+	} else {
+		assert.False(t, ok,
+			"there should be no snapshot key because the server manages it")
 	}
 
 	// The server manages the timestamp key - there should not be a timestamp
 	// key
-	_, ok := roles[data.CanonicalTimestampRole]
-	assert.False(t, ok)
+	_, ok = roles[data.CanonicalTimestampRole]
+	assert.False(t, ok,
+		"there should be no timestamp key because the server manages it")
 }
 
 // This creates a new certificate manager in the repo's base directory and
@@ -166,62 +272,67 @@ func assertRepoHasExpectedCerts(t *testing.T, repo *NotaryRepository) {
 	assert.NotEqual(t, certID, "")
 }
 
-// Sanity check the TUF metadata files. Verify that they exist, the JSON is
-// well-formed, and the signatures exist. For the root.json file, also check
-// that the root, snapshot, and targets key IDs are present.
-func assertRepoHasExpectedMetadata(t *testing.T, repo *NotaryRepository) {
-	expectedTUFMetadataFiles := []string{
-		filepath.Join(tufDir, filepath.FromSlash(repo.gun), "metadata", "root.json"),
-		filepath.Join(tufDir, filepath.FromSlash(repo.gun), "metadata", "snapshot.json"),
-		filepath.Join(tufDir, filepath.FromSlash(repo.gun), "metadata", "targets.json"),
-	}
-	for _, filename := range expectedTUFMetadataFiles {
-		fullPath := filepath.Join(repo.baseDir, filename)
-		_, err := os.Stat(fullPath)
+// Sanity check the TUF metadata files. Verify that it exists for a particular
+// role, the JSON is well-formed, and the signatures exist.
+// For the root.json file, also check that the root, snapshot, and
+// targets key IDs are present.
+func assertRepoHasExpectedMetadata(t *testing.T, repo *NotaryRepository,
+	role string, expected bool) {
+
+	filename := filepath.Join(tufDir, filepath.FromSlash(repo.gun),
+		"metadata", role+".json")
+	fullPath := filepath.Join(repo.baseDir, filename)
+	_, err := os.Stat(fullPath)
+
+	if expected {
 		assert.NoError(t, err, "missing TUF metadata file: %s", filename)
+	} else {
+		assert.Error(t, err,
+			"%s metadata should not exist, but does: %s", role, filename)
+		return
+	}
 
-		jsonBytes, err := ioutil.ReadFile(fullPath)
-		assert.NoError(t, err, "error reading TUF metadata file %s: %s", filename, err)
-
-		var decoded data.Signed
-		err = json.Unmarshal(jsonBytes, &decoded)
-		assert.NoError(t, err, "error parsing TUF metadata file %s: %s", filename, err)
-
-		assert.Len(t, decoded.Signatures, 1,
-			"incorrect number of signatures in TUF metadata file %s", filename)
-
-		assert.NotEmpty(t, decoded.Signatures[0].KeyID,
-			"empty key ID field in TUF metadata file %s", filename)
-		assert.NotEmpty(t, decoded.Signatures[0].Method,
-			"empty method field in TUF metadata file %s", filename)
-		assert.NotEmpty(t, decoded.Signatures[0].Signature,
-			"empty signature in TUF metadata file %s", filename)
-
-		// Special case for root.json: also check that the signed
-		// content for keys and roles
-		if strings.HasSuffix(filename, "root.json") {
-			var decodedRoot data.Root
-			err := json.Unmarshal(decoded.Signed, &decodedRoot)
-			assert.NoError(t, err, "error parsing root.json signed section: %s", err)
-
-			assert.Equal(t, "Root", decodedRoot.Type, "_type mismatch in root.json")
-
-			// Expect 1 key for each valid role in the Keys map - one for
-			// each of root, targets, snapshot, timestamp
-			assert.Len(t, decodedRoot.Keys, len(data.ValidRoles),
-				"wrong number of keys in root.json")
-			assert.Len(t, decodedRoot.Roles, len(data.ValidRoles),
-				"wrong number of roles in root.json")
-
-			for role := range data.ValidRoles {
-				_, ok := decodedRoot.Roles[role]
-				assert.True(t, ok, "Missing role %s in root.json", role)
-			}
+	jsonBytes, err := ioutil.ReadFile(fullPath)
+	assert.NoError(t, err, "error reading TUF metadata file %s: %s", filename, err)
+
+	var decoded data.Signed
+	err = json.Unmarshal(jsonBytes, &decoded)
+	assert.NoError(t, err, "error parsing TUF metadata file %s: %s", filename, err)
+
+	assert.Len(t, decoded.Signatures, 1,
+		"incorrect number of signatures in TUF metadata file %s", filename)
+
+	assert.NotEmpty(t, decoded.Signatures[0].KeyID,
+		"empty key ID field in TUF metadata file %s", filename)
+	assert.NotEmpty(t, decoded.Signatures[0].Method,
+		"empty method field in TUF metadata file %s", filename)
+	assert.NotEmpty(t, decoded.Signatures[0].Signature,
+		"empty signature in TUF metadata file %s", filename)
+
+	// Special case for root.json: also check that the signed
+	// content for keys and roles
+	if role == data.CanonicalRootRole {
+		var decodedRoot data.Root
+		err := json.Unmarshal(decoded.Signed, &decodedRoot)
+		assert.NoError(t, err, "error parsing root.json signed section: %s", err)
+
+		assert.Equal(t, "Root", decodedRoot.Type, "_type mismatch in root.json")
+
+		// Expect 1 key for each valid role in the Keys map - one for
+		// each of root, targets, snapshot, timestamp
+		assert.Len(t, decodedRoot.Keys, len(data.ValidRoles),
+			"wrong number of keys in root.json")
+		assert.Len(t, decodedRoot.Roles, len(data.ValidRoles),
+			"wrong number of roles in root.json")
+
+		for role := range data.ValidRoles {
+			_, ok := decodedRoot.Roles[role]
+			assert.True(t, ok, "Missing role %s in root.json", role)
 		}
 	}
 }
 
-func testInitRepo(t *testing.T, rootType string) {
+func testInitRepo(t *testing.T, rootType string, serverManagesSnapshot bool) {
 	gun := "docker.com/notary"
 	// Temporary directory where test files will be created
 	tempBaseDir, err := ioutil.TempDir("", "notary-test-")
@@ -232,11 +343,15 @@ func testInitRepo(t *testing.T, rootType string) {
 	ts, _, _ := simpleTestServer(t)
 	defer ts.Close()
 
-	repo, rootKeyID := initializeRepo(t, rootType, tempBaseDir, gun, ts.URL)
+	repo, rootKeyID := initializeRepo(t, rootType, tempBaseDir, gun, ts.URL,
+		serverManagesSnapshot)
 
-	assertRepoHasExpectedKeys(t, repo, rootKeyID)
+	assertRepoHasExpectedKeys(t, repo, rootKeyID, !serverManagesSnapshot)
 	assertRepoHasExpectedCerts(t, repo)
-	assertRepoHasExpectedMetadata(t, repo)
+	assertRepoHasExpectedMetadata(t, repo, data.CanonicalRootRole, true)
+	assertRepoHasExpectedMetadata(t, repo, data.CanonicalTargetsRole, true)
+	assertRepoHasExpectedMetadata(t, repo, data.CanonicalSnapshotRole,
+		!serverManagesSnapshot)
 }
 
 // TestInitRepoAttemptsExceeded tests error handling when passphrase.Retriever
@@ -347,7 +462,7 @@ func testAddTarget(t *testing.T, rootType string) {
 	ts, _, _ := simpleTestServer(t)
 	defer ts.Close()
 
-	repo, _ := initializeRepo(t, rootType, tempBaseDir, gun, ts.URL)
+	repo, _ := initializeRepo(t, rootType, tempBaseDir, gun, ts.URL, false)
 
 	// tests need to manually boostrap timestamp as client doesn't generate it
 	err = repo.tufRepo.InitTimestamp()
@@ -414,11 +529,7 @@ func testListEmptyTargets(t *testing.T, rootType string) {
 	ts := fullTestServer(t)
 	defer ts.Close()
 
-	repo, _ := initializeRepo(t, rootType, tempBaseDir, gun, ts.URL)
-
-	// tests need to manually boostrap timestamp as client doesn't generate it
-	err = repo.tufRepo.InitTimestamp()
-	assert.NoError(t, err, "error creating repository: %s", err)
+	repo, _ := initializeRepo(t, rootType, tempBaseDir, gun, ts.URL, false)
 
 	_, err = repo.ListTargets()
 	assert.Error(t, err) // no trust data
@@ -499,7 +610,7 @@ func testListTarget(t *testing.T, rootType string) {
 	ts, mux, keys := simpleTestServer(t)
 	defer ts.Close()
 
-	repo, _ := initializeRepo(t, rootType, tempBaseDir, gun, ts.URL)
+	repo, _ := initializeRepo(t, rootType, tempBaseDir, gun, ts.URL, false)
 
 	// tests need to manually boostrap timestamp as client doesn't generate it
 	err = repo.tufRepo.InitTimestamp()
@@ -564,7 +675,7 @@ func testValidateRootKey(t *testing.T, rootType string) {
 	ts, _, _ := simpleTestServer(t)
 	defer ts.Close()
 
-	initializeRepo(t, rootType, tempBaseDir, gun, ts.URL)
+	initializeRepo(t, rootType, tempBaseDir, gun, ts.URL, false)
 
 	rootJSONFile := filepath.Join(tempBaseDir, "tuf", filepath.FromSlash(gun), "metadata", "root.json")
 
@@ -618,7 +729,7 @@ func testGetChangelist(t *testing.T, rootType string) {
 	ts, _, _ := simpleTestServer(t)
 	defer ts.Close()
 
-	repo, _ := initializeRepo(t, rootType, tempBaseDir, gun, ts.URL)
+	repo, _ := initializeRepo(t, rootType, tempBaseDir, gun, ts.URL, false)
 	assert.Len(t, getChanges(t, repo), 0, "No changes should be in changelist yet")
 
 	// Create 2 targets
@@ -649,92 +760,269 @@ func testGetChangelist(t *testing.T, rootType string) {
 	assert.Equal(t, "latest", latestChange.Path())
 }
 
-// TestPublish creates a repo, instantiates a notary server, and publishes
-// the repo to the server.
+// Create a repo, instantiate a notary server, and publish the repo to the
+// server, signing all the non-timestamp metadata.
 // We test this with both an RSA and ECDSA root key
-func TestPublish(t *testing.T) {
-	testPublish(t, data.ECDSAKey)
+func TestPublishClientHasSnapshotKey(t *testing.T) {
+	testPublish(t, data.ECDSAKey, false)
 	if !testing.Short() {
-		testPublish(t, data.RSAKey)
+		testPublish(t, data.RSAKey, false)
 	}
 }
 
-func testPublish(t *testing.T, rootType string) {
+// Create a repo, instantiate a notary server (designating the server as the
+// snapshot signer) , and publish the repo to the server, signing the root and
+// targets metadata only.  The server should sign just fine.
+// We test this with both an RSA and ECDSA root key
+func TestPublishAfterInitServerHasSnapshotKey(t *testing.T) {
+	testPublish(t, data.ECDSAKey, true)
+	if !testing.Short() {
+		testPublish(t, data.RSAKey, true)
+	}
+}
+
+func testPublish(t *testing.T, rootType string, serverManagesSnapshot bool) {
 	// Temporary directory where test files will be created
-	tempBaseDir, err := ioutil.TempDir("", "notary-test-")
+	tempBaseDir, err := ioutil.TempDir("/tmp", "notary-test-")
 	defer os.RemoveAll(tempBaseDir)
-
 	assert.NoError(t, err, "failed to create a temporary directory: %s", err)
 
 	gun := "docker.com/notary"
 	ts := fullTestServer(t)
 	defer ts.Close()
-	repo, _ := initializeRepo(t, rootType, tempBaseDir, gun, ts.URL)
 
+	repo, _ := initializeRepo(t, rootType, tempBaseDir, gun, ts.URL,
+		serverManagesSnapshot)
+	assertPublishSucceeds(t, repo)
+}
+
+func assertPublishSucceeds(t *testing.T, repo1 *NotaryRepository) {
 	// Create 2 targets
-	latestTarget := addTarget(t, repo, "latest", "../fixtures/intermediate-ca.crt")
-	currentTarget := addTarget(t, repo, "current", "../fixtures/intermediate-ca.crt")
-	assert.Len(t, getChanges(t, repo), 2, "wrong number of changelist files found")
+	latestTarget := addTarget(t, repo1, "latest", "../fixtures/intermediate-ca.crt")
+	currentTarget := addTarget(t, repo1, "current", "../fixtures/intermediate-ca.crt")
+	assert.Len(t, getChanges(t, repo1), 2, "wrong number of changelist files found")
 
 	// Now test Publish
-	err = repo.Publish()
+	err := repo1.Publish()
 	assert.NoError(t, err)
-	assert.Len(t, getChanges(t, repo), 0, "wrong number of changelist files found")
+	assert.Len(t, getChanges(t, repo1), 0, "wrong number of changelist files found")
 
 	// Create a new repo and pull from the server
-	tempBaseDir2, err := ioutil.TempDir("", "notary-test-")
-	defer os.RemoveAll(tempBaseDir2)
+	tempBaseDir, err := ioutil.TempDir("/tmp", "notary-test-")
+	defer os.RemoveAll(tempBaseDir)
 
 	assert.NoError(t, err, "failed to create a temporary directory: %s", err)
 
-	repo2, err := NewNotaryRepository(tempBaseDir, gun, ts.URL, http.DefaultTransport, passphraseRetriever)
+	repo2, err := NewNotaryRepository(tempBaseDir, repo1.gun, repo1.baseURL,
+		http.DefaultTransport, passphraseRetriever)
 	assert.NoError(t, err, "error creating repository: %s", err)
 
-	targets, err := repo2.ListTargets()
-	assert.NoError(t, err)
-
 	// Should be two targets
-	assert.Len(t, targets, 2, "unexpected number of targets returned by ListTargets")
+	for _, repo := range []*NotaryRepository{repo1, repo2} {
+		targets, err := repo.ListTargets()
+		assert.NoError(t, err)
 
-	sort.Stable(targetSorter(targets))
+		assert.Len(t, targets, 2, "unexpected number of targets returned by ListTargets")
 
-	assert.Equal(t, currentTarget, targets[0], "current target does not match")
-	assert.Equal(t, latestTarget, targets[1], "latest target does not match")
+		sort.Stable(targetSorter(targets))
 
-	// Also test GetTargetByName
-	newLatestTarget, err := repo2.GetTargetByName("latest")
+		assert.Equal(t, currentTarget, targets[0], "current target does not match")
+		assert.Equal(t, latestTarget, targets[1], "latest target does not match")
+
+		// Also test GetTargetByName
+		newLatestTarget, err := repo.GetTargetByName("latest")
+		assert.NoError(t, err)
+		assert.Equal(t, latestTarget, newLatestTarget, "latest target does not match")
+
+		newCurrentTarget, err := repo.GetTargetByName("current")
+		assert.NoError(t, err)
+		assert.Equal(t, currentTarget, newCurrentTarget, "current target does not match")
+	}
+}
+
+// After pulling a repo from the server, so there is a snapshots metadata file,
+// push a different target to the server (the server is still the snapshot
+// signer).  The server should sign just fine.
+// We test this with both an RSA and ECDSA root key
+func TestPublishAfterPullServerHasSnapshotKey(t *testing.T) {
+	testPublishAfterPullServerHasSnapshotKey(t, data.ECDSAKey)
+	if !testing.Short() {
+		testPublishAfterPullServerHasSnapshotKey(t, data.RSAKey)
+	}
+}
+
+func testPublishAfterPullServerHasSnapshotKey(t *testing.T, rootType string) {
+	// Temporary directory where test files will be created
+	tempBaseDir, err := ioutil.TempDir("/tmp", "notary-test-")
+	defer os.RemoveAll(tempBaseDir)
+	assert.NoError(t, err, "failed to create a temporary directory: %s", err)
+
+	gun := "docker.com/notary"
+	ts := fullTestServer(t)
+	defer ts.Close()
+
+	repo, _ := initializeRepo(t, rootType, tempBaseDir, gun, ts.URL, true)
+	// no timestamp metadata because that comes from the server
+	assertRepoHasExpectedMetadata(t, repo, data.CanonicalTimestampRole, false)
+	// no snapshot metadata because that comes from the server
+	assertRepoHasExpectedMetadata(t, repo, data.CanonicalSnapshotRole, false)
+
+	// Publish something
+	published := addTarget(t, repo, "v1", "../fixtures/intermediate-ca.crt")
+	err = repo.Publish()
 	assert.NoError(t, err)
-	assert.Equal(t, latestTarget, newLatestTarget, "latest target does not match")
+	// still no timestamp or snapshot metadata info
+	assertRepoHasExpectedMetadata(t, repo, data.CanonicalTimestampRole, false)
+	assertRepoHasExpectedMetadata(t, repo, data.CanonicalSnapshotRole, false)
 
-	newCurrentTarget, err := repo2.GetTargetByName("current")
+	// list, so that the snapshot metadata is pulled from server
+	targets, err := repo.ListTargets()
+	assert.NoError(t, err)
+	assert.Equal(t, []*Target{published}, targets)
+	// listing downloaded the timestamp and snapshot metadata info
+	assertRepoHasExpectedMetadata(t, repo, data.CanonicalTimestampRole, true)
+	assertRepoHasExpectedMetadata(t, repo, data.CanonicalSnapshotRole, true)
+
+	// Publish again should succeed
+	addTarget(t, repo, "v2", "../fixtures/intermediate-ca.crt")
+	err = repo.Publish()
 	assert.NoError(t, err)
-	assert.Equal(t, currentTarget, newCurrentTarget, "current target does not match")
 }
 
-func TestRotate(t *testing.T) {
+// If neither the client nor the server has the snapshot key, signing will fail
+// with an ErrNoKeys error.
+// We test this with both an RSA and ECDSA root key
+func TestPublishNoOneHasSnapshotKey(t *testing.T) {
+	testPublishNoOneHasSnapshotKey(t, data.ECDSAKey)
+	if !testing.Short() {
+		testPublishNoOneHasSnapshotKey(t, data.RSAKey)
+	}
+}
+
+func testPublishNoOneHasSnapshotKey(t *testing.T, rootType string) {
 	// Temporary directory where test files will be created
-	tempBaseDir, err := ioutil.TempDir("", "notary-test-")
+	tempBaseDir, err := ioutil.TempDir("/tmp", "notary-test-")
 	defer os.RemoveAll(tempBaseDir)
-
 	assert.NoError(t, err, "failed to create a temporary directory: %s", err)
 
 	gun := "docker.com/notary"
-
 	ts := fullTestServer(t)
 	defer ts.Close()
 
-	repo, _ := initializeRepo(t, data.ECDSAKey, tempBaseDir, gun, ts.URL)
+	// create repo and delete the snapshot key and metadata
+	repo, _ := initializeRepo(t, rootType, tempBaseDir, gun, ts.URL, false)
+	snapshotRole, ok := repo.tufRepo.Root.Signed.Roles[data.CanonicalSnapshotRole]
+	assert.True(t, ok)
+	for _, keyID := range snapshotRole.KeyIDs {
+		repo.CryptoService.RemoveKey(keyID)
+	}
 
-	// Adding a target will allow us to confirm the repository is still valid after
-	// rotating the keys.
-	addTarget(t, repo, "latest", "../fixtures/intermediate-ca.crt")
+	// Publish something
+	addTarget(t, repo, "v1", "../fixtures/intermediate-ca.crt")
+	err = repo.Publish()
+	assert.Error(t, err)
+	assert.IsType(t, store.ErrInvalidOperation{}, err)
+}
 
-	// Publish
+// If the snapshot metadata is corrupt, whether the client or server has the
+// snapshot key, we can't publish.
+// We test this with both an RSA and ECDSA root key
+func TestPublishSnapshotCorrupt(t *testing.T) {
+	testPublishBadExistingSnapshot(t, data.ECDSAKey, true, true)
+	testPublishBadExistingSnapshot(t, data.ECDSAKey, false, true)
+}
+
+// If the snapshot metadata is unreadable, whether the client or server has the
+// snapshot key, we can't publish.
+// We test this with both an RSA and ECDSA root key
+func TestPublishSnapshotUnreadable(t *testing.T) {
+	testPublishBadExistingSnapshot(t, data.ECDSAKey, true, false)
+	testPublishBadExistingSnapshot(t, data.ECDSAKey, false, false)
+}
+
+func testPublishBadExistingSnapshot(t *testing.T, rootType string,
+	serverManagesSnapshot bool, readable bool) {
+
+	// Temporary directory where test files will be created
+	tempBaseDir, err := ioutil.TempDir("/tmp", "notary-test-")
+	defer os.RemoveAll(tempBaseDir)
+	assert.NoError(t, err, "failed to create a temporary directory: %s", err)
+
+	gun := "docker.com/notary"
+	ts := fullTestServer(t)
+	defer ts.Close()
+
+	repo, _ := initializeRepo(
+		t, rootType, tempBaseDir, gun, ts.URL, serverManagesSnapshot)
+
+	addTarget(t, repo, "v1", "../fixtures/intermediate-ca.crt")
+
+	var expectedErrType interface{}
+	if readable {
+		// write a corrupt snapshots file
+		repo.fileStore.SetMeta(data.CanonicalSnapshotRole, []byte("this isn't JSON"))
+		expectedErrType = &regJson.SyntaxError{}
+	} else {
+		// create a directory instead of a file
+		path := fmt.Sprintf("%s.%s",
+			filepath.Join(tempBaseDir, tufDir, filepath.FromSlash(gun),
+				"metadata", data.CanonicalSnapshotRole), "json")
+		os.RemoveAll(path)
+		err := os.Mkdir(path, 0755)
+		defer os.RemoveAll(path)
+		assert.NoError(t, err)
+
+		expectedErrType = &os.PathError{}
+	}
 	err = repo.Publish()
-	assert.NoError(t, err)
+	assert.Error(t, err)
+	assert.IsType(t, expectedErrType, err)
+}
 
-	// Get root.json and capture targets + snapshot key IDs
-	repo.GetTargetByName("latest") // force a pull
+type cannotCreateKeys struct {
+	signed.CryptoService
+}
+
+func (cs cannotCreateKeys) Create(_, _ string) (data.PublicKey, error) {
+	return nil, fmt.Errorf("Oh no I cannot create keys")
+}
+
+// If there is an error creating the local keys, no call is made to get a
+// remote key.
+func TestPublishSnapshotLocalKeysCreatedFirst(t *testing.T) {
+	// Temporary directory where test files will be created
+	tempBaseDir, err := ioutil.TempDir("/tmp", "notary-test-")
+	defer os.RemoveAll(tempBaseDir)
+	assert.NoError(t, err, "failed to create a temporary directory: %s", err)
+	gun := "docker.com/notary"
+
+	requestMade := false
+	ts := httptest.NewServer(http.HandlerFunc(
+		func(http.ResponseWriter, *http.Request) { requestMade = true }))
+	defer ts.Close()
+
+	repo, err := NewNotaryRepository(
+		tempBaseDir, gun, ts.URL, http.DefaultTransport, passphraseRetriever)
+	assert.NoError(t, err, "error creating repo: %s", err)
+
+	cs := cryptoservice.NewCryptoService(gun,
+		trustmanager.NewKeyMemoryStore(passphraseRetriever))
+
+	rootPubKey, err := cs.Create(data.CanonicalRootRole, data.ECDSAKey)
+	assert.NoError(t, err, "error generating root key: %s", err)
+
+	repo.CryptoService = cannotCreateKeys{CryptoService: cs}
+
+	err = repo.Initialize(rootPubKey.ID(), data.CanonicalSnapshotRole)
+	assert.Error(t, err)
+	assert.Contains(t, err.Error(), "Oh no I cannot create keys")
+	assert.False(t, requestMade)
+}
+
+// Rotates the keys.  After the rotation, downloading the latest metadata
+// and assert that the keys have changed
+func assertRotationSuccessful(t *testing.T, repo *NotaryRepository) {
+	// capture targets + snapshot key IDs
 	targetsKeyIDs := repo.tufRepo.Root.Signed.Roles["targets"].KeyIDs
 	snapshotKeyIDs := repo.tufRepo.Root.Signed.Roles["snapshot"].KeyIDs
 	assert.Len(t, targetsKeyIDs, 1)
@@ -744,7 +1032,7 @@ func TestRotate(t *testing.T) {
 	repo.RotateKeys()
 
 	// Publish
-	err = repo.Publish()
+	err := repo.Publish()
 	assert.NoError(t, err)
 
 	// Get root.json. Check targets + snapshot keys have changed
@@ -763,6 +1051,58 @@ func TestRotate(t *testing.T) {
 	assert.Len(t, changes, 0, "wrong number of changelist files found")
 }
 
+// Initialize a repo
+// Publish some content (so that the server has a root.json)
+// Rotate keys
+// Download the latest metadata and assert that the keys have changed.
+func TestRotateAfterPublish(t *testing.T) {
+	// Temporary directory where test files will be created
+	tempBaseDir, err := ioutil.TempDir("", "notary-test-")
+	defer os.RemoveAll(tempBaseDir)
+
+	assert.NoError(t, err, "failed to create a temporary directory: %s", err)
+
+	gun := "docker.com/notary"
+
+	ts := fullTestServer(t)
+	defer ts.Close()
+
+	repo, _ := initializeRepo(t, data.ECDSAKey, tempBaseDir, gun, ts.URL, false)
+
+	// Adding a target will allow us to confirm the repository is still valid after
+	// rotating the keys.
+	addTarget(t, repo, "latest", "../fixtures/intermediate-ca.crt")
+
+	// Publish
+	err = repo.Publish()
+	assert.NoError(t, err)
+
+	repo.GetTargetByName("latest") // force a pull
+	assertRotationSuccessful(t, repo)
+}
+
+// Initialize repo to have the server sign snapshots (remote snapshot key)
+// Without downloading a server-signed snapshot file, rotate keys so that
+//    snapshots are locally signed (local snapshot key)
+// Assert that we can publish.
+func TestPublishAfterChangedFromRemoteKeyToLocalKey(t *testing.T) {
+	// Temporary directory where test files will be created
+	tempBaseDir, err := ioutil.TempDir("", "notary-test-")
+	defer os.RemoveAll(tempBaseDir)
+
+	assert.NoError(t, err, "failed to create a temporary directory: %s", err)
+
+	gun := "docker.com/notary"
+	ts := fullTestServer(t)
+	defer ts.Close()
+
+	repo, _ := initializeRepo(t, data.ECDSAKey, tempBaseDir, gun, ts.URL, true)
+	// Adding a target will allow us to confirm the repository is still valid
+	// after rotating the keys.
+	addTarget(t, repo, "latest", "../fixtures/intermediate-ca.crt")
+	assertRotationSuccessful(t, repo)
+}
+
 // If there is no local cache, notary operations return the remote error code
 func TestRemoteServerUnavailableNoLocalCache(t *testing.T) {
 	tempBaseDir, err := ioutil.TempDir("", "notary-test-")
diff --git a/cmd/notary-server/main_test.go b/cmd/notary-server/main_test.go
index efe5415b..095f48c1 100644
--- a/cmd/notary-server/main_test.go
+++ b/cmd/notary-server/main_test.go
@@ -297,7 +297,7 @@ func TestGetTrustServiceTLSFailure(t *testing.T) {
 	assert.Equal(t, 0, registerCalled)
 }
 
-// Just to ensure that errors are propagated
+// Just to ensure that errors are propogated
 func TestGetStoreInvalid(t *testing.T) {
 	config := `{"storage": {"backend": "asdf", "db_url": "/tmp/1234"}}`
 
diff --git a/cmd/notary-signer/main_test.go b/cmd/notary-signer/main_test.go
index 107b01f3..bd219ae7 100644
--- a/cmd/notary-signer/main_test.go
+++ b/cmd/notary-signer/main_test.go
@@ -37,7 +37,7 @@ func configure(jsonConfig string) *viper.Viper {
 // If the TLS configuration is invalid, an error is returned.  This doesn't test
 // all the cases of the TLS configuration being invalid, since it's just
 // calling configuration.ParseTLSConfig - this test just makes sure the
-// error is propagated.
+// error is propogated.
 func TestGetAddrAndTLSConfigInvalidTLS(t *testing.T) {
 	invalids := []string{
 		`{"server": {"http_addr": ":1234", "grpc_addr": ":2345"}}`,
diff --git a/server/handlers/default_test.go b/server/handlers/default_test.go
index 03135274..31647829 100644
--- a/server/handlers/default_test.go
+++ b/server/handlers/default_test.go
@@ -3,7 +3,6 @@ package handlers
 import (
 	"bytes"
 	"encoding/json"
-	"fmt"
 	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
@@ -12,13 +11,9 @@ import (
 	"golang.org/x/net/context"
 
 	ctxu "github.com/docker/distribution/context"
-	"github.com/docker/distribution/registry/api/errcode"
-	"github.com/docker/notary/server/errors"
 	"github.com/docker/notary/server/storage"
 	"github.com/docker/notary/tuf/data"
 	"github.com/docker/notary/tuf/signed"
-	"github.com/docker/notary/tuf/store"
-	"github.com/docker/notary/tuf/validation"
 
 	"github.com/docker/notary/tuf/testutils"
 	"github.com/docker/notary/utils"
@@ -172,16 +167,16 @@ func TestGetKeyHandlerCreatesOnce(t *testing.T) {
 }
 
 func TestGetHandlerRoot(t *testing.T) {
-	metaStore := storage.NewMemStorage()
+	store := storage.NewMemStorage()
 	_, repo, _ := testutils.EmptyRepo()
 
 	ctx := context.Background()
-	ctx = context.WithValue(ctx, "metaStore", metaStore)
+	ctx = context.WithValue(ctx, "metaStore", store)
 
 	root, err := repo.SignRoot(data.DefaultExpires("root"))
 	rootJSON, err := json.Marshal(root)
 	assert.NoError(t, err)
-	metaStore.UpdateCurrent("gun", storage.MetaUpdate{Role: "root", Version: 1, Data: rootJSON})
+	store.UpdateCurrent("gun", storage.MetaUpdate{Role: "root", Version: 1, Data: rootJSON})
 
 	req := &http.Request{
 		Body: ioutil.NopCloser(bytes.NewBuffer(nil)),
@@ -199,22 +194,20 @@ func TestGetHandlerRoot(t *testing.T) {
 }
 
 func TestGetHandlerTimestamp(t *testing.T) {
-	metaStore := storage.NewMemStorage()
+	store := storage.NewMemStorage()
 	_, repo, crypto := testutils.EmptyRepo()
 
-	ctx := getContext(handlerState{store: metaStore, crypto: crypto})
+	ctx := getContext(handlerState{store: store, crypto: crypto})
 
 	sn, err := repo.SignSnapshot(data.DefaultExpires("snapshot"))
 	snJSON, err := json.Marshal(sn)
 	assert.NoError(t, err)
-	metaStore.UpdateCurrent(
-		"gun", storage.MetaUpdate{Role: "snapshot", Version: 1, Data: snJSON})
+	store.UpdateCurrent("gun", storage.MetaUpdate{Role: "snapshot", Version: 1, Data: snJSON})
 
 	ts, err := repo.SignTimestamp(data.DefaultExpires("timestamp"))
 	tsJSON, err := json.Marshal(ts)
 	assert.NoError(t, err)
-	metaStore.UpdateCurrent(
-		"gun", storage.MetaUpdate{Role: "timestamp", Version: 1, Data: tsJSON})
+	store.UpdateCurrent("gun", storage.MetaUpdate{Role: "timestamp", Version: 1, Data: tsJSON})
 
 	req := &http.Request{
 		Body: ioutil.NopCloser(bytes.NewBuffer(nil)),
@@ -232,16 +225,15 @@ func TestGetHandlerTimestamp(t *testing.T) {
 }
 
 func TestGetHandlerSnapshot(t *testing.T) {
-	metaStore := storage.NewMemStorage()
+	store := storage.NewMemStorage()
 	_, repo, crypto := testutils.EmptyRepo()
 
-	ctx := getContext(handlerState{store: metaStore, crypto: crypto})
+	ctx := getContext(handlerState{store: store, crypto: crypto})
 
 	sn, err := repo.SignSnapshot(data.DefaultExpires("snapshot"))
 	snJSON, err := json.Marshal(sn)
 	assert.NoError(t, err)
-	metaStore.UpdateCurrent(
-		"gun", storage.MetaUpdate{Role: "snapshot", Version: 1, Data: snJSON})
+	store.UpdateCurrent("gun", storage.MetaUpdate{Role: "snapshot", Version: 1, Data: snJSON})
 
 	req := &http.Request{
 		Body: ioutil.NopCloser(bytes.NewBuffer(nil)),
@@ -259,10 +251,10 @@ func TestGetHandlerSnapshot(t *testing.T) {
 }
 
 func TestGetHandler404(t *testing.T) {
-	metaStore := storage.NewMemStorage()
+	store := storage.NewMemStorage()
 
 	ctx := context.Background()
-	ctx = context.WithValue(ctx, "metaStore", metaStore)
+	ctx = context.WithValue(ctx, "metaStore", store)
 
 	req := &http.Request{
 		Body: ioutil.NopCloser(bytes.NewBuffer(nil)),
@@ -280,11 +272,11 @@ func TestGetHandler404(t *testing.T) {
 }
 
 func TestGetHandlerNilData(t *testing.T) {
-	metaStore := storage.NewMemStorage()
-	metaStore.UpdateCurrent("gun", storage.MetaUpdate{Role: "root", Version: 1, Data: nil})
+	store := storage.NewMemStorage()
+	store.UpdateCurrent("gun", storage.MetaUpdate{Role: "root", Version: 1, Data: nil})
 
 	ctx := context.Background()
-	ctx = context.WithValue(ctx, "metaStore", metaStore)
+	ctx = context.WithValue(ctx, "metaStore", store)
 
 	req := &http.Request{
 		Body: ioutil.NopCloser(bytes.NewBuffer(nil)),
@@ -311,77 +303,3 @@ func TestGetHandlerNoStorage(t *testing.T) {
 	err := GetHandler(ctx, nil, req)
 	assert.Error(t, err)
 }
-
-// a validation failure, such as a snapshots file being missing, will be
-// propagated as a detail in the error (which gets serialized as the body of the
-// response)
-func TestAtomicUpdateValidationFailurePropagated(t *testing.T) {
-	metaStore := storage.NewMemStorage()
-	gun := "testGUN"
-	vars := map[string]string{"imageName": gun}
-
-	kdb, repo, cs := testutils.EmptyRepo()
-	copyTimestampKey(t, kdb, metaStore, gun)
-	state := handlerState{store: metaStore, crypto: cs}
-
-	r, tg, sn, ts, err := testutils.Sign(repo)
-	assert.NoError(t, err)
-	rs, tgs, _, _, err := testutils.Serialize(r, tg, sn, ts)
-	assert.NoError(t, err)
-
-	req, err := store.NewMultiPartMetaRequest("", map[string][]byte{
-		data.CanonicalRootRole:    rs,
-		data.CanonicalTargetsRole: tgs,
-	})
-
-	rw := httptest.NewRecorder()
-
-	err = atomicUpdateHandler(getContext(state), rw, req, vars)
-	assert.Error(t, err)
-	errorObj, ok := err.(errcode.Error)
-	assert.True(t, ok, "Expected an errcode.Error, got %v", err)
-	assert.Equal(t, errors.ErrInvalidUpdate, errorObj.Code)
-	serializable, ok := errorObj.Detail.(*validation.SerializableError)
-	assert.True(t, ok, "Expected a SerializableObject, got %v", errorObj.Detail)
-	assert.IsType(t, validation.ErrBadHierarchy{}, serializable.Error)
-}
-
-type failStore struct {
-	storage.MemStorage
-}
-
-func (s *failStore) GetCurrent(_, _ string) ([]byte, error) {
-	return nil, fmt.Errorf("oh no! storage has failed")
-}
-
-// a non-validation failure, such as the storage failing, will not be propagated
-// as a detail in the error (which gets serialized as the body of the response)
-func TestAtomicUpdateNonValidationFailureNotPropagated(t *testing.T) {
-	metaStore := storage.NewMemStorage()
-	gun := "testGUN"
-	vars := map[string]string{"imageName": gun}
-
-	kdb, repo, cs := testutils.EmptyRepo()
-	copyTimestampKey(t, kdb, metaStore, gun)
-	state := handlerState{store: &failStore{*metaStore}, crypto: cs}
-
-	r, tg, sn, ts, err := testutils.Sign(repo)
-	assert.NoError(t, err)
-	rs, tgs, sns, _, err := testutils.Serialize(r, tg, sn, ts)
-	assert.NoError(t, err)
-
-	req, err := store.NewMultiPartMetaRequest("", map[string][]byte{
-		data.CanonicalRootRole:     rs,
-		data.CanonicalTargetsRole:  tgs,
-		data.CanonicalSnapshotRole: sns,
-	})
-
-	rw := httptest.NewRecorder()
-
-	err = atomicUpdateHandler(getContext(state), rw, req, vars)
-	assert.Error(t, err)
-	errorObj, ok := err.(errcode.Error)
-	assert.True(t, ok, "Expected an errcode.Error, got %v", err)
-	assert.Equal(t, errors.ErrInvalidUpdate, errorObj.Code)
-	assert.Nil(t, errorObj.Detail)
-}
diff --git a/server/handlers/validation_test.go b/server/handlers/validation_test.go
index dfa6034d..31d5aafc 100644
--- a/server/handlers/validation_test.go
+++ b/server/handlers/validation_test.go
@@ -12,7 +12,6 @@ import (
 	"github.com/docker/notary/tuf/keys"
 	"github.com/docker/notary/tuf/signed"
 	"github.com/docker/notary/tuf/testutils"
-	"github.com/docker/notary/tuf/validation"
 	"github.com/stretchr/testify/assert"
 
 	"github.com/docker/notary/server/storage"
@@ -212,7 +211,7 @@ func TestValidateNoRoot(t *testing.T) {
 	copyTimestampKey(t, kdb, store, "testGUN")
 	_, err = validateUpdate(cs, "testGUN", updates, store)
 	assert.Error(t, err)
-	assert.IsType(t, validation.ErrValidation{}, err)
+	assert.IsType(t, ErrValidation{}, err)
 }
 
 func TestValidateSnapshotMissing(t *testing.T) {
@@ -229,7 +228,7 @@ func TestValidateSnapshotMissing(t *testing.T) {
 	copyTimestampKey(t, kdb, store, "testGUN")
 	_, err = validateUpdate(cs, "testGUN", updates, store)
 	assert.Error(t, err)
-	assert.IsType(t, validation.ErrBadHierarchy{}, err)
+	assert.IsType(t, ErrBadHierarchy{}, err)
 }
 
 func TestValidateSnapshotGenerateNoPrev(t *testing.T) {
@@ -464,7 +463,7 @@ func TestValidateRootNoTimestampKey(t *testing.T) {
 	// do not copy the targets key to the storage, and try to update the root
 	_, err = validateUpdate(cs, "testGUN", updates, store)
 	assert.Error(t, err)
-	assert.IsType(t, validation.ErrBadRoot{}, err)
+	assert.IsType(t, ErrBadRoot{}, err)
 
 	// there should still be no timestamp keys - one should not have been
 	// created
@@ -494,7 +493,7 @@ func TestValidateRootInvalidTimestampKey(t *testing.T) {
 
 	_, err = validateUpdate(cs, "testGUN", updates, store)
 	assert.Error(t, err)
-	assert.IsType(t, validation.ErrBadRoot{}, err)
+	assert.IsType(t, ErrBadRoot{}, err)
 }
 
 // If the timestamp role has a threshold > 1, validation fails.
@@ -543,7 +542,7 @@ func TestValidateRootInvalidZeroThreshold(t *testing.T) {
 
 // ### Role missing negative tests ###
 // These tests remove a role from the Root file and
-// check for a validation.ErrBadRoot
+// check for a ErrBadRoot
 func TestValidateRootRoleMissing(t *testing.T) {
 	kdb, repo, cs := testutils.EmptyRepo()
 	store := storage.NewMemStorage()
@@ -560,7 +559,7 @@ func TestValidateRootRoleMissing(t *testing.T) {
 	copyTimestampKey(t, kdb, store, "testGUN")
 	_, err = validateUpdate(cs, "testGUN", updates, store)
 	assert.Error(t, err)
-	assert.IsType(t, validation.ErrBadRoot{}, err)
+	assert.IsType(t, ErrBadRoot{}, err)
 }
 
 func TestValidateTargetsRoleMissing(t *testing.T) {
@@ -579,7 +578,7 @@ func TestValidateTargetsRoleMissing(t *testing.T) {
 	copyTimestampKey(t, kdb, store, "testGUN")
 	_, err = validateUpdate(cs, "testGUN", updates, store)
 	assert.Error(t, err)
-	assert.IsType(t, validation.ErrBadRoot{}, err)
+	assert.IsType(t, ErrBadRoot{}, err)
 }
 
 func TestValidateSnapshotRoleMissing(t *testing.T) {
@@ -598,7 +597,7 @@ func TestValidateSnapshotRoleMissing(t *testing.T) {
 	copyTimestampKey(t, kdb, store, "testGUN")
 	_, err = validateUpdate(cs, "testGUN", updates, store)
 	assert.Error(t, err)
-	assert.IsType(t, validation.ErrBadRoot{}, err)
+	assert.IsType(t, ErrBadRoot{}, err)
 }
 
 // ### End role missing negative tests ###
@@ -623,7 +622,7 @@ func TestValidateRootSigMissing(t *testing.T) {
 	copyTimestampKey(t, kdb, store, "testGUN")
 	_, err = validateUpdate(cs, "testGUN", updates, store)
 	assert.Error(t, err)
-	assert.IsType(t, validation.ErrBadRoot{}, err)
+	assert.IsType(t, ErrBadRoot{}, err)
 }
 
 func TestValidateTargetsSigMissing(t *testing.T) {
@@ -643,7 +642,7 @@ func TestValidateTargetsSigMissing(t *testing.T) {
 	copyTimestampKey(t, kdb, store, "testGUN")
 	_, err = validateUpdate(cs, "testGUN", updates, store)
 	assert.Error(t, err)
-	assert.IsType(t, validation.ErrBadTargets{}, err)
+	assert.IsType(t, ErrBadTargets{}, err)
 }
 
 func TestValidateSnapshotSigMissing(t *testing.T) {
@@ -663,7 +662,7 @@ func TestValidateSnapshotSigMissing(t *testing.T) {
 	copyTimestampKey(t, kdb, store, "testGUN")
 	_, err = validateUpdate(cs, "testGUN", updates, store)
 	assert.Error(t, err)
-	assert.IsType(t, validation.ErrBadSnapshot{}, err)
+	assert.IsType(t, ErrBadSnapshot{}, err)
 }
 
 // ### End signature missing negative tests ###
@@ -686,7 +685,7 @@ func TestValidateRootCorrupt(t *testing.T) {
 	copyTimestampKey(t, kdb, store, "testGUN")
 	_, err = validateUpdate(cs, "testGUN", updates, store)
 	assert.Error(t, err)
-	assert.IsType(t, validation.ErrBadRoot{}, err)
+	assert.IsType(t, ErrBadRoot{}, err)
 }
 
 func TestValidateTargetsCorrupt(t *testing.T) {
@@ -706,7 +705,7 @@ func TestValidateTargetsCorrupt(t *testing.T) {
 	copyTimestampKey(t, kdb, store, "testGUN")
 	_, err = validateUpdate(cs, "testGUN", updates, store)
 	assert.Error(t, err)
-	assert.IsType(t, validation.ErrBadTargets{}, err)
+	assert.IsType(t, ErrBadTargets{}, err)
 }
 
 func TestValidateSnapshotCorrupt(t *testing.T) {
@@ -726,7 +725,7 @@ func TestValidateSnapshotCorrupt(t *testing.T) {
 	copyTimestampKey(t, kdb, store, "testGUN")
 	_, err = validateUpdate(cs, "testGUN", updates, store)
 	assert.Error(t, err)
-	assert.IsType(t, validation.ErrBadSnapshot{}, err)
+	assert.IsType(t, ErrBadSnapshot{}, err)
 }
 
 // ### End corrupted metadata negative tests ###
@@ -753,7 +752,7 @@ func TestValidateRootModifiedSize(t *testing.T) {
 	copyTimestampKey(t, kdb, store, "testGUN")
 	_, err = validateUpdate(cs, "testGUN", updates, store)
 	assert.Error(t, err)
-	assert.IsType(t, validation.ErrBadRoot{}, err)
+	assert.IsType(t, ErrBadRoot{}, err)
 }
 
 func TestValidateTargetsModifiedSize(t *testing.T) {
@@ -774,7 +773,7 @@ func TestValidateTargetsModifiedSize(t *testing.T) {
 	copyTimestampKey(t, kdb, store, "testGUN")
 	_, err = validateUpdate(cs, "testGUN", updates, store)
 	assert.Error(t, err)
-	assert.IsType(t, validation.ErrBadSnapshot{}, err)
+	assert.IsType(t, ErrBadSnapshot{}, err)
 }
 
 // ### End snapshot size mismatch negative tests ###
@@ -802,7 +801,7 @@ func TestValidateRootModifiedHash(t *testing.T) {
 	copyTimestampKey(t, kdb, store, "testGUN")
 	_, err = validateUpdate(cs, "testGUN", updates, store)
 	assert.Error(t, err)
-	assert.IsType(t, validation.ErrBadSnapshot{}, err)
+	assert.IsType(t, ErrBadSnapshot{}, err)
 }
 
 func TestValidateTargetsModifiedHash(t *testing.T) {
@@ -827,7 +826,7 @@ func TestValidateTargetsModifiedHash(t *testing.T) {
 	copyTimestampKey(t, kdb, store, "testGUN")
 	_, err = validateUpdate(cs, "testGUN", updates, store)
 	assert.Error(t, err)
-	assert.IsType(t, validation.ErrBadSnapshot{}, err)
+	assert.IsType(t, ErrBadSnapshot{}, err)
 }
 
 // ### End snapshot hash mismatch negative tests ###
diff --git a/server/integration_test.go b/server/integration_test.go
deleted file mode 100644
index 0f017055..00000000
--- a/server/integration_test.go
+++ /dev/null
@@ -1,49 +0,0 @@
-// This makes sure that the server is compatible with the tuf httpstore.
-
-package server
-
-import (
-	"fmt"
-	"net/http"
-	"net/http/httptest"
-	"testing"
-
-	"github.com/docker/notary/server/storage"
-	"github.com/docker/notary/tuf/data"
-	"github.com/docker/notary/tuf/signed"
-	"github.com/docker/notary/tuf/store"
-	"github.com/docker/notary/tuf/testutils"
-	"github.com/docker/notary/tuf/validation"
-	"github.com/stretchr/testify/assert"
-	"golang.org/x/net/context"
-)
-
-// Ensures that the httpstore can interpret the errors returned from the server
-func TestValidationErrorFormat(t *testing.T) {
-	ctx := context.WithValue(
-		context.Background(), "metaStore", storage.NewMemStorage())
-	ctx = context.WithValue(ctx, "keyAlgorithm", data.ED25519Key)
-
-	handler := RootHandler(nil, ctx, signed.NewEd25519())
-	server := httptest.NewServer(handler)
-	defer server.Close()
-
-	client, err := store.NewHTTPStore(
-		fmt.Sprintf("%s/v2/gun/_trust/tuf/", server.URL),
-		"",
-		"json",
-		"",
-		"key",
-		http.DefaultTransport,
-	)
-
-	_, repo, _ := testutils.EmptyRepo()
-	r, tg, sn, ts, err := testutils.Sign(repo)
-	assert.NoError(t, err)
-	rs, _, _, _, err := testutils.Serialize(r, tg, sn, ts)
-	assert.NoError(t, err)
-
-	err = client.SetMultiMeta(map[string][]byte{data.CanonicalRootRole: rs})
-	assert.Error(t, err)
-	assert.IsType(t, validation.ErrBadRoot{}, err)
-}
diff --git a/signer/api/api_test.go b/signer/api/api_test.go
index ac63a376..579f51a3 100644
--- a/signer/api/api_test.go
+++ b/signer/api/api_test.go
@@ -225,26 +225,3 @@ func TestSignHandlerReturns404WithNonexistentKey(t *testing.T) {
 
 	assert.Equal(t, 404, res.StatusCode)
 }
-
-func TestCreateKeyHandlerWithInvalidAlgorithm(t *testing.T) {
-	keyStore := trustmanager.NewKeyMemoryStore(passphraseRetriever)
-	cryptoService := cryptoservice.NewCryptoService("", keyStore)
-	setup(signer.CryptoServiceIndex{data.ED25519Key: cryptoService, data.RSAKey: cryptoService, data.ECDSAKey: cryptoService})
-
-	// The `rbtree-algorithm` is expected as not supported
-	createKeyURL := fmt.Sprintf("%s/%s", createKeyBaseURL, "rbtree-algorithm")
-
-	request, err := http.NewRequest("POST", createKeyURL, nil)
-	assert.Nil(t, err)
-
-	res, err := http.DefaultClient.Do(request)
-	assert.Nil(t, err)
-
-	assert.Equal(t, http.StatusBadRequest, res.StatusCode)
-
-	body, err := ioutil.ReadAll(res.Body)
-	assert.Nil(t, err)
-
-	// The body may contains some `\r\n`, so we use assert.Contains not assert.Equals
-	assert.Contains(t, string(body), "algorithm rbtree-algorithm not supported")
-}
diff --git a/signer/keydbstore/keydbstore_test.go b/signer/keydbstore/keydbstore_test.go
index 64736d61..b0c9dbae 100644
--- a/signer/keydbstore/keydbstore_test.go
+++ b/signer/keydbstore/keydbstore_test.go
@@ -64,8 +64,8 @@ func testGetSuccessFromCache(t *testing.T, dbStore *KeyDBStore,
 	testGetSuccess(t, dbStore, expectedKey)
 }
 
-// Creating a new KeyDBStore propagates any db opening error
-func TestNewKeyDBStorePropagatesDBError(t *testing.T) {
+// Creating a new KeyDBStore propogates any db opening error
+func TestNewKeyDBStorePropogatesDBError(t *testing.T) {
 	dbStore, err := NewKeyDBStore(retriever, "ignoredalias", "nodb", "somestring")
 	assert.Error(t, err)
 	assert.Nil(t, dbStore)
diff --git a/tuf/store/filestore_test.go b/tuf/store/filestore_test.go
index 9f41597b..f6d35163 100644
--- a/tuf/store/filestore_test.go
+++ b/tuf/store/filestore_test.go
@@ -56,3 +56,22 @@ func TestGetMeta(t *testing.T) {
 
 	assert.Equal(t, testContent, content, "Content read from file was corrupted.")
 }
+
+func TestGetMetaNoSuchMetadata(t *testing.T) {
+	testDir, err := ioutil.TempDir("/tmp", "testFileSystemStore")
+	assert.NoError(t, err)
+	// ensure that the random directory doesn't exist
+	os.RemoveAll(testDir)
+
+	// don't use the constructor, which creates the directories - just
+	s := FilesystemStore{
+		baseDir:       testDir,
+		metaDir:       "metadata",
+		metaExtension: "json",
+		targetsDir:    "targets",
+	}
+
+	_, err = s.GetMeta("testMeta", int64(5))
+	assert.Error(t, err)
+	assert.IsType(t, ErrMetaNotFound{}, err)
+}
diff --git a/tuf/store/httpstore_test.go b/tuf/store/httpstore_test.go
index 3b22c1fb..9f5c3c1c 100644
--- a/tuf/store/httpstore_test.go
+++ b/tuf/store/httpstore_test.go
@@ -1,7 +1,6 @@
 package store
 
 import (
-	"bytes"
 	"encoding/base64"
 	"encoding/hex"
 	"fmt"
@@ -16,7 +15,6 @@ import (
 
 	"github.com/docker/notary/tuf/data"
 	"github.com/docker/notary/tuf/signed"
-	"github.com/docker/notary/tuf/validation"
 	"github.com/jfrazelle/go/canonical/json"
 )
 
@@ -201,46 +199,3 @@ func Test50XErrors(t *testing.T) {
 func Test400Error(t *testing.T) {
 	testErrorCode(t, http.StatusBadRequest, ErrInvalidOperation{})
 }
-
-// If it's a 400, translateStatusToError attempts to parse the body into
-// an error.  If successful (and a recognized error) that error is returned.
-func TestTranslateErrorsParse400Errors(t *testing.T) {
-	origErr := validation.ErrBadRoot{Msg: "bad"}
-
-	serialObj, err := validation.NewSerializableError(origErr)
-	assert.NoError(t, err)
-	serialization, err := json.Marshal(serialObj)
-	assert.NoError(t, err)
-	errorBody := bytes.NewBuffer([]byte(fmt.Sprintf(
-		`{"errors": [{"otherstuff": "what", "detail": %s}]}`,
-		string(serialization))))
-	errorResp := http.Response{
-		StatusCode: http.StatusBadRequest,
-		Body:       ioutil.NopCloser(errorBody),
-	}
-
-	finalError := translateStatusToError(&errorResp)
-	assert.Equal(t, origErr, finalError)
-}
-
-// If it's a 400, translateStatusToError attempts to parse the body into
-// an error.  If parsing fails, an InvalidOperation is returned instead.
-func TestTranslateErrorsWhenCannotParse400(t *testing.T) {
-	invalids := []string{
-		`{"errors": [{"otherstuff": "what", "detail": {"Name": "Muffin"}}]}`,
-		`{"errors": [{"otherstuff": "what", "detail": {}}]}`,
-		`{"errors": [{"otherstuff": "what"}]}`,
-		`{"errors": []}`,
-		`{}`,
-		"400",
-	}
-	for _, body := range invalids {
-		errorResp := http.Response{
-			StatusCode: http.StatusBadRequest,
-			Body:       ioutil.NopCloser(bytes.NewBuffer([]byte(body))),
-		}
-
-		err := translateStatusToError(&errorResp)
-		assert.IsType(t, ErrInvalidOperation{}, err)
-	}
-}
diff --git a/tuf/validation/errors_test.go b/tuf/validation/errors_test.go
deleted file mode 100644
index 092b67c4..00000000
--- a/tuf/validation/errors_test.go
+++ /dev/null
@@ -1,84 +0,0 @@
-package validation
-
-import (
-	"encoding/json"
-	"fmt"
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-)
-
-// NewSerializableError errors if some random error is not returned
-func TestNewSerializableErrorNonValidationError(t *testing.T) {
-	_, err := NewSerializableError(fmt.Errorf("not validation error"))
-	assert.Error(t, err)
-}
-
-// NewSerializableError succeeds if a validation error is passed to it
-func TestNewSerializableErrorValidationError(t *testing.T) {
-	vError := ErrValidation{"validation error"}
-	s, err := NewSerializableError(vError)
-	assert.NoError(t, err)
-	assert.Equal(t, "ErrValidation", s.Name)
-	assert.Equal(t, vError, s.Error)
-}
-
-// We can unmarshal a marshalled SerializableError for all validation errors
-func TestUnmarshalSerialiableErrorSuccessfully(t *testing.T) {
-	validationErrors := []error{
-		ErrValidation{"bad validation"},
-		ErrBadHierarchy{Missing: "root", Msg: "badness"},
-		ErrBadRoot{"bad root"},
-		ErrBadTargets{"bad targets"},
-		ErrBadSnapshot{"bad snapshot"},
-	}
-
-	for _, validError := range validationErrors {
-		origS, err := NewSerializableError(validError)
-		assert.NoError(t, err)
-		jsonBytes, err := json.Marshal(origS)
-		assert.NoError(t, err)
-
-		var newS SerializableError
-		err = json.Unmarshal(jsonBytes, &newS)
-		assert.NoError(t, err)
-
-		assert.Equal(t, validError, newS.Error)
-	}
-}
-
-// If the name is unrecognized, unmarshalling will error
-func TestUnmarshalUnknownErrorName(t *testing.T) {
-	origS := SerializableError{Name: "boop", Error: ErrBadRoot{"bad"}}
-	b, err := json.Marshal(origS)
-	assert.NoError(t, err)
-
-	var newS SerializableError
-	err = json.Unmarshal(b, &newS)
-	assert.Error(t, err)
-}
-
-// If the error is unmarshallable, unmarshalling will error even if the name
-// is valid
-func TestUnmarshalInvalidError(t *testing.T) {
-	var newS SerializableError
-	err := json.Unmarshal([]byte(`{"Name": "ErrBadRoot", "Error": "meh"}`), &newS)
-	assert.Error(t, err)
-}
-
-// If there is no name, unmarshalling will error even if the error is valid
-func TestUnmarshalNoName(t *testing.T) {
-	origS := SerializableError{Error: ErrBadRoot{"bad"}}
-	b, err := json.Marshal(origS)
-	assert.NoError(t, err)
-
-	var newS SerializableError
-	err = json.Unmarshal(b, &newS)
-	assert.Error(t, err)
-}
-
-func TestUnmarshalInvalidJSON(t *testing.T) {
-	var newS SerializableError
-	err := json.Unmarshal([]byte("{"), &newS)
-	assert.Error(t, err)
-}
diff --git a/utils/http_test.go b/utils/http_test.go
index 62d991cb..d2d1ef79 100644
--- a/utils/http_test.go
+++ b/utils/http_test.go
@@ -7,9 +7,10 @@ import (
 	"strings"
 	"testing"
 
-	"github.com/docker/distribution/registry/api/errcode"
 	"github.com/docker/notary/tuf/signed"
 	"golang.org/x/net/context"
+
+	"github.com/docker/notary/errors"
 )
 
 func MockContextHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
@@ -17,7 +18,7 @@ func MockContextHandler(ctx context.Context, w http.ResponseWriter, r *http.Requ
 }
 
 func MockBetterErrorHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
-	return errcode.ErrorCodeUnknown.WithDetail("Test Error")
+	return errors.ErrUnknown.WithDetail("Test Error")
 }
 
 func TestRootHandlerFactory(t *testing.T) {