diff --git a/Dockerfile b/Dockerfile index 7c32747402..e3e0c0f066 100644 --- a/Dockerfile +++ b/Dockerfile @@ -165,7 +165,7 @@ RUN set -x \ && rm -rf "$GOPATH" # Install notary server -ENV NOTARY_VERSION docker-v1.10-2 +ENV NOTARY_VERSION docker-v1.10-3 RUN set -x \ && export GOPATH="$(mktemp -d)" \ && git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \ diff --git a/api/client/trust.go b/api/client/trust.go index f347689972..d06db5d938 100644 --- a/api/client/trust.go +++ b/api/client/trust.go @@ -284,13 +284,15 @@ func notaryError(repoName string, err error) error { case signed.ErrInvalidKeyType: return fmt.Errorf("Warning: potential malicious behavior - trust data mismatch for remote repository %s: %v", repoName, err) case signed.ErrNoKeys: - return fmt.Errorf("Error: could not find signing keys for remote repository %s: %v", repoName, err) + return fmt.Errorf("Error: could not find signing keys for remote repository %s, or could not decrypt signing key: %v", repoName, err) case signed.ErrLowVersion: return fmt.Errorf("Warning: potential malicious behavior - trust data version is lower than expected for remote repository %s: %v", repoName, err) - case signed.ErrInsufficientSignatures: + case signed.ErrRoleThreshold: return fmt.Errorf("Warning: potential malicious behavior - trust data has insufficient signatures for remote repository %s: %v", repoName, err) case client.ErrRepositoryNotExist: - return fmt.Errorf("Error: remote trust data repository not initialized for %s: %v", repoName, err) + return fmt.Errorf("Error: remote trust data does not exist for %s: %v", repoName, err) + case signed.ErrInsufficientSignatures: + return fmt.Errorf("Error: could not produce valid signature for %s. If Yubikey was used, was touch input provided?: %v", repoName, err) } return err diff --git a/hack/vendor.sh b/hack/vendor.sh index a74d55133a..045d37595c 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -49,7 +49,7 @@ clone git github.com/docker/distribution a7ae88da459b98b481a245e5b1750134724ac67 clone git github.com/vbatts/tar-split v0.9.11 # get desired notary commit, might also need to be updated in Dockerfile -clone git github.com/docker/notary docker-v1.10-2 +clone git github.com/docker/notary docker-v1.10-3 clone git google.golang.org/grpc 174192fc93efcb188fc8f46ca447f0da606b6885 https://github.com/grpc/grpc-go.git clone git github.com/miekg/pkcs11 80f102b5cac759de406949c47f0928b99bd64cdf diff --git a/integration-cli/docker_cli_create_test.go b/integration-cli/docker_cli_create_test.go index 4bd90b0095..096074c27e 100644 --- a/integration-cli/docker_cli_create_test.go +++ b/integration-cli/docker_cli_create_test.go @@ -312,7 +312,7 @@ func (s *DockerTrustSuite) TestUntrustedCreate(c *check.C) { s.trustedCmd(createCmd) out, _, err := runCommandWithOutput(createCmd) c.Assert(err, check.Not(check.IsNil)) - c.Assert(string(out), checker.Contains, "does not have trust data for", check.Commentf("Missing expected output on trusted create:\n%s", out)) + c.Assert(string(out), checker.Contains, "trust data unavailable. Has a notary repository been initialized?", check.Commentf("Missing expected output on trusted create:\n%s", out)) } @@ -402,7 +402,7 @@ func (s *DockerTrustSuite) TestTrustedCreateFromBadTrustServer(c *check.C) { s.trustedCmd(createCmd) out, _, err = runCommandWithOutput(createCmd) c.Assert(err, check.Not(check.IsNil)) - c.Assert(string(out), checker.Contains, "failed to validate data with current trusted certificates", check.Commentf("Missing expected output on trusted push:\n%s", out)) + c.Assert(string(out), checker.Contains, "valid signatures did not meet threshold", check.Commentf("Missing expected output on trusted push:\n%s", out)) } diff --git a/integration-cli/docker_cli_pull_trusted_test.go b/integration-cli/docker_cli_pull_trusted_test.go index 7bcfd5fa74..fbd50b5d96 100644 --- a/integration-cli/docker_cli_pull_trusted_test.go +++ b/integration-cli/docker_cli_pull_trusted_test.go @@ -59,7 +59,7 @@ func (s *DockerTrustSuite) TestUntrustedPull(c *check.C) { out, _, err := runCommandWithOutput(pullCmd) c.Assert(err, check.NotNil, check.Commentf(out)) - c.Assert(string(out), checker.Contains, "Error: remote trust data repository not initialized", check.Commentf(out)) + c.Assert(string(out), checker.Contains, "Error: remote trust data does not exist", check.Commentf(out)) } func (s *DockerTrustSuite) TestPullWhenCertExpired(c *check.C) { @@ -141,7 +141,7 @@ func (s *DockerTrustSuite) TestTrustedPullFromBadTrustServer(c *check.C) { out, _, err = runCommandWithOutput(pullCmd) c.Assert(err, check.NotNil, check.Commentf(out)) - c.Assert(string(out), checker.Contains, "failed to validate data with current trusted certificates", check.Commentf(out)) + c.Assert(string(out), checker.Contains, "valid signatures did not meet threshold", check.Commentf(out)) } func (s *DockerTrustSuite) TestTrustedPullWithExpiredSnapshot(c *check.C) { diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 0cf8a1c8a6..f34c235d35 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -3303,7 +3303,7 @@ func (s *DockerTrustSuite) TestTrustedRunFromBadTrustServer(c *check.C) { c.Fatalf("Expected to fail on this run due to different remote data: %s\n%s", err, out) } - if !strings.Contains(string(out), "failed to validate data with current trusted certificates") { + if !strings.Contains(string(out), "valid signatures did not meet threshold") { c.Fatalf("Missing expected output on trusted push:\n%s", out) } } diff --git a/vendor/src/github.com/docker/notary/CONTRIBUTING.md b/vendor/src/github.com/docker/notary/CONTRIBUTING.md index dbe4fe2d77..0d4d16fc20 100644 --- a/vendor/src/github.com/docker/notary/CONTRIBUTING.md +++ b/vendor/src/github.com/docker/notary/CONTRIBUTING.md @@ -19,7 +19,6 @@ Then please do not report your issue here - you should instead report it to [htt Then please do not open an issue here yet - you should first try one of the following support forums: - irc: #docker-trust on freenode - - mailing-list: or https://groups.google.com/a/dockerproject.org/forum/#!forum/trust ## Reporting an issue properly diff --git a/vendor/src/github.com/docker/notary/MAINTAINERS b/vendor/src/github.com/docker/notary/MAINTAINERS index 73741d6362..999e280cd2 100644 --- a/vendor/src/github.com/docker/notary/MAINTAINERS +++ b/vendor/src/github.com/docker/notary/MAINTAINERS @@ -16,6 +16,7 @@ "dmcgowan", "endophage", "nathanmccauley", + "riyazdf", ] [people] @@ -50,3 +51,8 @@ Name = "Nathan McCauley" Email = "nathan.mccauley@docker.com" GitHub = "nathanmccauley" + + [people.riyazdf] + Name = "Riyaz Faizullabhoy" + Email = "riyaz@docker.com" + GitHub = "riyazdf" diff --git a/vendor/src/github.com/docker/notary/Makefile b/vendor/src/github.com/docker/notary/Makefile index 641ba69d10..c632423ffa 100644 --- a/vendor/src/github.com/docker/notary/Makefile +++ b/vendor/src/github.com/docker/notary/Makefile @@ -92,14 +92,21 @@ build: go_version @echo "+ $@" @go build -tags "${NOTARY_BUILDTAGS}" -v ${GO_LDFLAGS} ./... +# When running `go test ./...`, it runs all the suites in parallel, which causes +# problems when running with a yubikey test: TESTOPTS = test: go_version + @echo Note: when testing with a yubikey plugged in, make sure to include 'TESTOPTS="-p 1"' @echo "+ $@ $(TESTOPTS)" + @echo go test -tags "${NOTARY_BUILDTAGS}" $(TESTOPTS) ./... +test-full: TESTOPTS = test-full: vet lint + @echo Note: when testing with a yubikey plugged in, make sure to include 'TESTOPTS="-p 1"' @echo "+ $@" - go test -tags "${NOTARY_BUILDTAGS}" -v ./... + @echo + go test -tags "${NOTARY_BUILDTAGS}" $(TESTOPTS) -v ./... protos: @protoc --go_out=plugins=grpc:. proto/*.proto @@ -118,14 +125,18 @@ gen-cover: go_version @mkdir -p "$(COVERDIR)" $(foreach PKG,$(PKGS),$(call gocover,$(PKG))) +# Generates the cover binaries and runs them all in serial, so this can be used +# run all tests with a yubikey without any problems cover: GO_EXC := go OPTS = -tags "${NOTARY_BUILDTAGS}" -coverpkg "$(shell ./coverpkg.sh $(1) $(NOTARY_PKG))" cover: gen-cover covmerge @go tool cover -html="$(COVERPROFILE)" -# Codecov knows how to merge multiple coverage files +# Generates the cover binaries and runs them all in serial, so this can be used +# run all tests with a yubikey without any problems ci: OPTS = -tags "${NOTARY_BUILDTAGS}" -race -coverpkg "$(shell ./coverpkg.sh $(1) $(NOTARY_PKG))" GO_EXC := godep go +# Codecov knows how to merge multiple coverage files, so covmerge is not needed ci: gen-cover covmerge: @@ -151,10 +162,10 @@ notary-dockerfile: @docker build --rm --force-rm -t notary . server-dockerfile: - @docker build --rm --force-rm -f Dockerfile.server -t notary-server . + @docker build --rm --force-rm -f server.Dockerfile -t notary-server . signer-dockerfile: - @docker build --rm --force-rm -f Dockerfile.signer -t notary-signer . + @docker build --rm --force-rm -f signer.Dockerfile -t notary-signer . docker-images: notary-dockerfile server-dockerfile signer-dockerfile diff --git a/vendor/src/github.com/docker/notary/client/client.go b/vendor/src/github.com/docker/notary/client/client.go index b065936f2b..a3c7b7fe29 100644 --- a/vendor/src/github.com/docker/notary/client/client.go +++ b/vendor/src/github.com/docker/notary/client/client.go @@ -419,7 +419,7 @@ func (r *NotaryRepository) RemoveTarget(targetName string, roles ...string) erro // subtree and also the "targets/x" subtree, as we will defer parsing it until // we explicitly reach it in our iteration of the provided list of roles. func (r *NotaryRepository) ListTargets(roles ...string) ([]*TargetWithRole, error) { - _, err := r.Update() + _, err := r.Update(false) if err != nil { return nil, err } @@ -479,7 +479,7 @@ func (r *NotaryRepository) listSubtree(targets map[string]*TargetWithRole, role // will be returned // See the IMPORTANT section on ListTargets above. Those roles also apply here. func (r *NotaryRepository) GetTargetByName(name string, roles ...string) (*TargetWithRole, error) { - c, err := r.Update() + c, err := r.Update(false) if err != nil { return nil, err } @@ -514,7 +514,7 @@ func (r *NotaryRepository) GetChangelist() (changelist.Changelist, error) { func (r *NotaryRepository) Publish() error { var initialPublish bool // update first before publishing - _, err := r.Update() + _, err := r.Update(true) if err != nil { // If the remote is not aware of the repo, then this is being published // for the first time. Try to load from disk instead for publishing. @@ -555,13 +555,21 @@ func (r *NotaryRepository) Publish() error { // 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 || initialPublish { + // check if our root file is nearing expiry or dirty. Resign if it is. If + // root is not dirty but we are publishing for the first time, then just + // publish the existing root we have. + if nearExpiry(r.tufRepo.Root) || r.tufRepo.Root.Dirty { rootJSON, err := serializeCanonicalRole(r.tufRepo, data.CanonicalRootRole) if err != nil { return err } updatedFiles[data.CanonicalRootRole] = rootJSON + } else if initialPublish { + rootJSON, err := r.tufRepo.Root.MarshalJSON() + if err != nil { + return err + } + updatedFiles[data.CanonicalRootRole] = rootJSON } // iterate through all the targets files - if they are dirty, sign and update @@ -714,75 +722,94 @@ func (r *NotaryRepository) saveMetadata(ignoreSnapshot bool) error { return r.fileStore.SetMeta(data.CanonicalSnapshotRole, snapshotJSON) } +// returns a properly constructed ErrRepositoryNotExist error based on this +// repo's information +func (r *NotaryRepository) errRepositoryNotExist() error { + host := r.baseURL + parsed, err := url.Parse(r.baseURL) + if err == nil { + host = parsed.Host // try to exclude the scheme and any paths + } + return ErrRepositoryNotExist{remote: host, gun: r.gun} +} + // Update bootstraps a trust anchor (root.json) before updating all the // metadata from the repo. -func (r *NotaryRepository) Update() (*tufclient.Client, error) { - c, err := r.bootstrapClient() +func (r *NotaryRepository) Update(forWrite bool) (*tufclient.Client, error) { + c, err := r.bootstrapClient(forWrite) if err != nil { if _, ok := err.(store.ErrMetaNotFound); ok { - host := r.baseURL - parsed, err := url.Parse(r.baseURL) - if err == nil { - host = parsed.Host // try to exclude the scheme and any paths - } - return nil, ErrRepositoryNotExist{remote: host, gun: r.gun} + return nil, r.errRepositoryNotExist() } return nil, err } err = c.Update() if err != nil { + if notFound, ok := err.(store.ErrMetaNotFound); ok && notFound.Resource == data.CanonicalRootRole { + return nil, r.errRepositoryNotExist() + } return nil, err } return c, nil } -func (r *NotaryRepository) bootstrapClient() (*tufclient.Client, error) { - var rootJSON []byte - remote, err := getRemoteStore(r.baseURL, r.gun, r.roundTrip) - if err == nil { +// bootstrapClient attempts to bootstrap a root.json to be used as the trust +// anchor for a repository. The checkInitialized argument indicates whether +// we should always attempt to contact the server to determine if the repository +// is initialized or not. If set to true, we will always attempt to download +// and return an error if the remote repository errors. +func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Client, error) { + var ( + rootJSON []byte + err error + signedRoot *data.SignedRoot + ) + // try to read root from cache first. We will trust this root + // until we detect a problem during update which will cause + // us to download a new root and perform a rotation. + rootJSON, cachedRootErr := r.fileStore.GetMeta("root", maxSize) + + if cachedRootErr == nil { + signedRoot, cachedRootErr = r.validateRoot(rootJSON) + } + + remote, remoteErr := getRemoteStore(r.baseURL, r.gun, r.roundTrip) + if remoteErr != nil { + logrus.Error(remoteErr) + } else if cachedRootErr != nil || checkInitialized { + // remoteErr was nil and we had a cachedRootErr (or are specifically + // checking for initialization of the repo). + // if remote store successfully set up, try and get root from remote - rootJSON, err = remote.GetMeta("root", maxSize) - } - - // if remote store couldn't be setup, or we failed to get a root from it - // load the root from cache (offline operation) - if err != nil { - if err, ok := err.(store.ErrMetaNotFound); ok { - // if the error was MetaNotFound then we successfully contacted - // the store and it doesn't know about the repo. + tmpJSON, err := remote.GetMeta("root", maxSize) + if err != nil { + // we didn't have a root in cache and were unable to load one from + // the server. Nothing we can do but error. return nil, err } - result, cacheErr := r.fileStore.GetMeta("root", maxSize) - if cacheErr != nil { - // if cache didn't return a root, we cannot proceed - just return - // the original error. - return nil, err - } - rootJSON = result - logrus.Debugf( - "Using local cache instead of remote due to failure: %s", err.Error()) - } - // can't just unmarshal into SignedRoot because validate root - // needs the root.Signed field to still be []byte for signature - // validation - root := &data.Signed{} - err = json.Unmarshal(rootJSON, root) - if err != nil { - return nil, err - } + if cachedRootErr != nil { + // we always want to use the downloaded root if there was a cache + // error. + signedRoot, err = r.validateRoot(tmpJSON) + if err != nil { + return nil, err + } - err = r.CertManager.ValidateRoot(root, r.gun) - if err != nil { - return nil, err + err = r.fileStore.SetMeta("root", tmpJSON) + if err != nil { + // if we can't write cache we should still continue, just log error + logrus.Errorf("could not save root to cache: %s", err.Error()) + } + } } kdb := keys.NewDB() r.tufRepo = tuf.NewRepo(kdb, r.CryptoService) - signedRoot, err := data.RootFromSigned(root) - if err != nil { - return nil, err + if signedRoot == nil { + return nil, ErrRepoNotInitialized{} } + err = r.tufRepo.SetRoot(signedRoot) if err != nil { return nil, err @@ -796,6 +823,28 @@ func (r *NotaryRepository) bootstrapClient() (*tufclient.Client, error) { ), nil } +// validateRoot MUST only be used during bootstrapping. It will only validate +// signatures of the root based on known keys, not expiry or other metadata. +// This is so that an out of date root can be loaded to be used in a rotation +// should the TUF update process detect a problem. +func (r *NotaryRepository) validateRoot(rootJSON []byte) (*data.SignedRoot, error) { + // can't just unmarshal into SignedRoot because validate root + // needs the root.Signed field to still be []byte for signature + // validation + root := &data.Signed{} + err := json.Unmarshal(rootJSON, root) + if err != nil { + return nil, err + } + + err = r.CertManager.ValidateRoot(root, r.gun) + if err != nil { + return nil, err + } + + return data.RootFromSigned(root) +} + // RotateKey removes all existing keys associated with the role, and either // creates and adds one new key or delegates managing the key to the server. // These changes are staged in a changelist until publish is called. diff --git a/vendor/src/github.com/docker/notary/client/helpers.go b/vendor/src/github.com/docker/notary/client/helpers.go index 23b9249561..304ac3d621 100644 --- a/vendor/src/github.com/docker/notary/client/helpers.go +++ b/vendor/src/github.com/docker/notary/client/helpers.go @@ -17,7 +17,7 @@ import ( // Use this to initialize remote HTTPStores from the config settings func getRemoteStore(baseURL, gun string, rt http.RoundTripper) (store.RemoteStore, error) { - return store.NewHTTPStore( + s, err := store.NewHTTPStore( baseURL+"/v2/"+gun+"/_trust/tuf/", "", "json", @@ -25,6 +25,10 @@ func getRemoteStore(baseURL, gun string, rt http.RoundTripper) (store.RemoteStor "key", rt, ) + if err != nil { + return store.OfflineStore{}, err + } + return s, err } func applyChangelist(repo *tuf.Repo, cl changelist.Changelist) error { diff --git a/vendor/src/github.com/docker/notary/client/repo_pkcs11.go b/vendor/src/github.com/docker/notary/client/repo_pkcs11.go index b93f9bf29c..dd697ff4ec 100644 --- a/vendor/src/github.com/docker/notary/client/repo_pkcs11.go +++ b/vendor/src/github.com/docker/notary/client/repo_pkcs11.go @@ -26,7 +26,7 @@ func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper, keyStores := []trustmanager.KeyStore{fileKeyStore} yubiKeyStore, _ := yubikey.NewYubiKeyStore(fileKeyStore, retriever) if yubiKeyStore != nil { - keyStores = append(keyStores, yubiKeyStore) + keyStores = []trustmanager.KeyStore{yubiKeyStore, fileKeyStore} } return repositoryFromKeystores(baseDir, gun, baseURL, rt, keyStores) diff --git a/vendor/src/github.com/docker/notary/cryptoservice/crypto_service.go b/vendor/src/github.com/docker/notary/cryptoservice/crypto_service.go index d473564da4..f5bfa073b0 100644 --- a/vendor/src/github.com/docker/notary/cryptoservice/crypto_service.go +++ b/vendor/src/github.com/docker/notary/cryptoservice/crypto_service.go @@ -73,9 +73,9 @@ func (cs *CryptoService) Create(role, algorithm string) (data.PublicKey, error) } -// GetPrivateKey returns a private key by ID. It tries to get the key first -// without a GUN (in which case it's a root key). If that fails, try to get -// the key with the GUN (non-root key). +// GetPrivateKey returns a private key and role if present by ID. +// It tries to get the key first without a GUN (in which case it's a root key). +// If that fails, try to get the key with the GUN (non-root key). // If that fails, then we don't have the key. func (cs *CryptoService) GetPrivateKey(keyID string) (k data.PrivateKey, role string, err error) { keyPaths := []string{keyID, filepath.Join(cs.gun, keyID)} diff --git a/vendor/src/github.com/docker/notary/docker-compose.yml b/vendor/src/github.com/docker/notary/docker-compose.yml index 5bd578c214..17b5798fa2 100644 --- a/vendor/src/github.com/docker/notary/docker-compose.yml +++ b/vendor/src/github.com/docker/notary/docker-compose.yml @@ -1,6 +1,6 @@ notaryserver: build: . - dockerfile: Dockerfile.server + dockerfile: server.Dockerfile links: - notarymysql - notarysigner @@ -15,7 +15,7 @@ notarysigner: - /dev/bus/usb/003/010:/dev/bus/usb/002/010 - /var/run/pcscd/pcscd.comm:/var/run/pcscd/pcscd.comm build: . - dockerfile: Dockerfile.signer + dockerfile: signer.Dockerfile links: - notarymysql command: -config=fixtures/signer-config.json diff --git a/vendor/src/github.com/docker/notary/Dockerfile.server b/vendor/src/github.com/docker/notary/server.Dockerfile similarity index 100% rename from vendor/src/github.com/docker/notary/Dockerfile.server rename to vendor/src/github.com/docker/notary/server.Dockerfile diff --git a/vendor/src/github.com/docker/notary/Dockerfile.signer b/vendor/src/github.com/docker/notary/signer.Dockerfile similarity index 100% rename from vendor/src/github.com/docker/notary/Dockerfile.signer rename to vendor/src/github.com/docker/notary/signer.Dockerfile diff --git a/vendor/src/github.com/docker/notary/tuf/client/client.go b/vendor/src/github.com/docker/notary/tuf/client/client.go index 9d28a8c129..263ee428b1 100644 --- a/vendor/src/github.com/docker/notary/tuf/client/client.go +++ b/vendor/src/github.com/docker/notary/tuf/client/client.go @@ -129,6 +129,7 @@ func (c Client) checkRoot() error { // downloadRoot is responsible for downloading the root.json func (c *Client) downloadRoot() error { + logrus.Debug("Downloading Root...") role := data.CanonicalRootRole size := maxSize var expectedSha256 []byte @@ -240,7 +241,7 @@ func (c Client) verifyRoot(role string, s *data.Signed, minVersion int) error { // Timestamps are special in that we ALWAYS attempt to download and only // use cache if the download fails (and the cache is still valid). func (c *Client) downloadTimestamp() error { - logrus.Debug("downloadTimestamp") + logrus.Debug("Downloading Timestamp...") role := data.CanonicalTimestampRole // We may not have a cached timestamp if this is the first time @@ -299,7 +300,7 @@ func (c *Client) downloadTimestamp() error { // downloadSnapshot is responsible for downloading the snapshot.json func (c *Client) downloadSnapshot() error { - logrus.Debug("downloadSnapshot") + logrus.Debug("Downloading Snapshot...") role := data.CanonicalSnapshotRole if c.local.Timestamp == nil { return ErrMissingMeta{role: "snapshot"} @@ -372,6 +373,7 @@ func (c *Client) downloadSnapshot() error { // It uses a pre-order tree traversal as it's necessary to download parents first // to obtain the keys to validate children. func (c *Client) downloadTargets(role string) error { + logrus.Debug("Downloading Targets...") stack := utils.NewStack() stack.Push(role) for !stack.Empty() { diff --git a/vendor/src/github.com/docker/notary/tuf/data/root.go b/vendor/src/github.com/docker/notary/tuf/data/root.go index 9ef8cd63d3..e555cbd2f5 100644 --- a/vendor/src/github.com/docker/notary/tuf/data/root.go +++ b/vendor/src/github.com/docker/notary/tuf/data/root.go @@ -43,10 +43,11 @@ func NewRoot(keys map[string]PublicKey, roles map[string]*RootRole, consistent b // ToSigned partially serializes a SignedRoot for further signing func (r SignedRoot) ToSigned() (*Signed, error) { - s, err := json.MarshalCanonical(r.Signed) + s, err := defaultSerializer.MarshalCanonical(r.Signed) if err != nil { return nil, err } + // cast into a json.RawMessage signed := json.RawMessage{} err = signed.UnmarshalJSON(s) if err != nil { @@ -60,6 +61,15 @@ func (r SignedRoot) ToSigned() (*Signed, error) { }, nil } +// MarshalJSON returns the serialized form of SignedRoot as bytes +func (r SignedRoot) MarshalJSON() ([]byte, error) { + signed, err := r.ToSigned() + if err != nil { + return nil, err + } + return defaultSerializer.Marshal(signed) +} + // RootFromSigned fully unpacks a Signed object into a SignedRoot func RootFromSigned(s *Signed) (*SignedRoot, error) { r := Root{} diff --git a/vendor/src/github.com/docker/notary/tuf/data/serializer.go b/vendor/src/github.com/docker/notary/tuf/data/serializer.go new file mode 100644 index 0000000000..91fa1bc93e --- /dev/null +++ b/vendor/src/github.com/docker/notary/tuf/data/serializer.go @@ -0,0 +1,36 @@ +package data + +import "github.com/jfrazelle/go/canonical/json" + +// Serializer is an interface that can marshal and unmarshal TUF data. This +// is expected to be a canonical JSON marshaller +type serializer interface { + MarshalCanonical(from interface{}) ([]byte, error) + Marshal(from interface{}) ([]byte, error) + Unmarshal(from []byte, to interface{}) error +} + +// CanonicalJSON marshals to and from canonical JSON +type canonicalJSON struct{} + +// MarshalCanonical returns the canonical JSON form of a thing +func (c canonicalJSON) MarshalCanonical(from interface{}) ([]byte, error) { + return json.MarshalCanonical(from) +} + +// Marshal returns the regular non-canonical JSON form of a thing +func (c canonicalJSON) Marshal(from interface{}) ([]byte, error) { + return json.Marshal(from) +} + +// Unmarshal unmarshals some JSON bytes +func (c canonicalJSON) Unmarshal(from []byte, to interface{}) error { + return json.Unmarshal(from, to) +} + +// defaultSerializer is a canonical JSON serializer +var defaultSerializer serializer = canonicalJSON{} + +func setDefaultSerializer(s serializer) { + defaultSerializer = s +} diff --git a/vendor/src/github.com/docker/notary/tuf/signed/ed25519.go b/vendor/src/github.com/docker/notary/tuf/signed/ed25519.go index 3f7ad1ed28..e09b550501 100644 --- a/vendor/src/github.com/docker/notary/tuf/signed/ed25519.go +++ b/vendor/src/github.com/docker/notary/tuf/signed/ed25519.go @@ -95,7 +95,7 @@ func (e *Ed25519) GetKey(keyID string) data.PublicKey { return data.PublicKeyFromPrivate(e.keys[keyID].privKey) } -// GetPrivateKey returns a single private key based on the ID +// GetPrivateKey returns a single private key and role if present, based on the ID func (e *Ed25519) GetPrivateKey(keyID string) (data.PrivateKey, string, error) { if k, ok := e.keys[keyID]; ok { return k.privKey, k.role, nil diff --git a/vendor/src/github.com/docker/notary/tuf/store/httpstore.go b/vendor/src/github.com/docker/notary/tuf/store/httpstore.go index 66e4bcc15a..ef69a611df 100644 --- a/vendor/src/github.com/docker/notary/tuf/store/httpstore.go +++ b/vendor/src/github.com/docker/notary/tuf/store/httpstore.go @@ -118,12 +118,12 @@ func tryUnmarshalError(resp *http.Response, defaultError error) error { return err } -func translateStatusToError(resp *http.Response) error { +func translateStatusToError(resp *http.Response, resource string) error { switch resp.StatusCode { case http.StatusOK: return nil case http.StatusNotFound: - return ErrMetaNotFound{} + return ErrMetaNotFound{Resource: resource} case http.StatusBadRequest: return tryUnmarshalError(resp, ErrInvalidOperation{}) default: @@ -148,7 +148,7 @@ func (s HTTPStore) GetMeta(name string, size int64) ([]byte, error) { return nil, err } defer resp.Body.Close() - if err := translateStatusToError(resp); err != nil { + if err := translateStatusToError(resp, name); err != nil { logrus.Debugf("received HTTP status %d when requesting %s.", resp.StatusCode, name) return nil, err } @@ -179,7 +179,7 @@ func (s HTTPStore) SetMeta(name string, blob []byte) error { return err } defer resp.Body.Close() - return translateStatusToError(resp) + return translateStatusToError(resp, "POST "+name) } // NewMultiPartMetaRequest builds a request with the provided metadata updates @@ -223,7 +223,8 @@ func (s HTTPStore) SetMultiMeta(metas map[string][]byte) error { return err } defer resp.Body.Close() - return translateStatusToError(resp) + // if this 404's something is pretty wrong + return translateStatusToError(resp, "POST metadata endpoint") } func (s HTTPStore) buildMetaURL(name string) (*url.URL, error) { @@ -271,7 +272,7 @@ func (s HTTPStore) GetTarget(path string) (io.ReadCloser, error) { return nil, err } defer resp.Body.Close() - if err := translateStatusToError(resp); err != nil { + if err := translateStatusToError(resp, path); err != nil { return nil, err } return resp.Body, nil @@ -292,7 +293,7 @@ func (s HTTPStore) GetKey(role string) ([]byte, error) { return nil, err } defer resp.Body.Close() - if err := translateStatusToError(resp); err != nil { + if err := translateStatusToError(resp, role+" key"); err != nil { return nil, err } body, err := ioutil.ReadAll(resp.Body) diff --git a/vendor/src/github.com/docker/notary/tuf/store/offlinestore.go b/vendor/src/github.com/docker/notary/tuf/store/offlinestore.go new file mode 100644 index 0000000000..d32e113c0a --- /dev/null +++ b/vendor/src/github.com/docker/notary/tuf/store/offlinestore.go @@ -0,0 +1,43 @@ +package store + +import ( + "io" +) + +// ErrOffline is used to indicate we are operating offline +type ErrOffline struct{} + +func (e ErrOffline) Error() string { + return "client is offline" +} + +var err = ErrOffline{} + +// OfflineStore is to be used as a placeholder for a nil store. It simply +// return ErrOffline for every operation +type OfflineStore struct{} + +// GetMeta return ErrOffline +func (es OfflineStore) GetMeta(name string, size int64) ([]byte, error) { + return nil, err +} + +// SetMeta return ErrOffline +func (es OfflineStore) SetMeta(name string, blob []byte) error { + return err +} + +// SetMultiMeta return ErrOffline +func (es OfflineStore) SetMultiMeta(map[string][]byte) error { + return err +} + +// GetKey return ErrOffline +func (es OfflineStore) GetKey(role string) ([]byte, error) { + return nil, err +} + +// GetTarget return ErrOffline +func (es OfflineStore) GetTarget(path string) (io.ReadCloser, error) { + return nil, err +}