From 84dc2d9e70f1ad4422732421e2d6b91274f4dfae Mon Sep 17 00:00:00 2001 From: Riyaz Faizullabhoy Date: Thu, 25 Feb 2016 13:40:00 -0800 Subject: [PATCH] Vendor in notary v0.2.0 Signed-off-by: Riyaz Faizullabhoy --- Dockerfile | 2 +- Dockerfile.aarch64 | 2 +- Dockerfile.armhf | 2 +- Dockerfile.ppc64le | 18 +- Dockerfile.s390x | 2 +- hack/vendor.sh | 2 +- integration-cli/docker_cli_create_test.go | 12 +- .../docker_cli_pull_trusted_test.go | 11 +- integration-cli/docker_cli_run_test.go | 12 +- integration-cli/trust_server.go | 2 +- vendor/src/github.com/docker/notary/Makefile | 49 +- vendor/src/github.com/docker/notary/README.md | 3 +- .../src/github.com/docker/notary/circle.yml | 11 +- .../docker/notary/client/changelist/change.go | 19 +- .../github.com/docker/notary/client/client.go | 308 +++------ .../docker/notary/client/delegations.go | 294 ++++++++ .../docker/notary/client/helpers.go | 74 +- vendor/src/github.com/docker/notary/const.go | 33 + .../notary/cryptoservice/import_export.go | 46 +- .../docker/notary/docker-compose.yml | 41 +- .../docker/notary/notarymysql/LICENSE | 21 - .../docker/notary/server.Dockerfile | 10 +- .../docker/notary/signer.Dockerfile | 26 +- .../notary/trustmanager/keyfilestore.go | 21 +- .../docker/notary/trustmanager/x509utils.go | 28 +- .../trustmanager/yubikey/yubikeystore.go | 6 +- .../github.com/docker/notary/tuf/README.md | 2 +- .../docker/notary/tuf/client/client.go | 245 +++---- .../docker/notary/tuf/client/errors.go | 9 - .../docker/notary/tuf/data/errors.go | 22 + .../github.com/docker/notary/tuf/data/keys.go | 80 +-- .../docker/notary/tuf/data/roles.go | 199 ++++-- .../github.com/docker/notary/tuf/data/root.go | 85 ++- .../docker/notary/tuf/data/snapshot.go | 54 +- .../docker/notary/tuf/data/targets.go | 123 +++- .../docker/notary/tuf/data/timestamp.go | 51 +- .../docker/notary/tuf/data/types.go | 21 +- .../github.com/docker/notary/tuf/keys/db.go | 78 --- .../docker/notary/tuf/signed/verify.go | 53 +- .../docker/notary/tuf/store/filestore.go | 22 +- .../docker/notary/tuf/store/httpstore.go | 40 +- .../docker/notary/tuf/store/interfaces.go | 17 - .../docker/notary/tuf/store/memorystore.go | 120 ++-- .../src/github.com/docker/notary/tuf/tuf.go | 651 +++++++++++------- .../docker/notary/tuf/utils/utils.go | 23 + 45 files changed, 1755 insertions(+), 1195 deletions(-) create mode 100644 vendor/src/github.com/docker/notary/client/delegations.go delete mode 100644 vendor/src/github.com/docker/notary/notarymysql/LICENSE create mode 100644 vendor/src/github.com/docker/notary/tuf/data/errors.go delete mode 100644 vendor/src/github.com/docker/notary/tuf/keys/db.go diff --git a/Dockerfile b/Dockerfile index 76c2de26cf..dc62f07609 100644 --- a/Dockerfile +++ b/Dockerfile @@ -169,7 +169,7 @@ RUN set -x \ && rm -rf "$GOPATH" # Install notary server -ENV NOTARY_VERSION docker-v1.10-5 +ENV NOTARY_VERSION v0.2.0 RUN set -x \ && export GOPATH="$(mktemp -d)" \ && git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \ diff --git a/Dockerfile.aarch64 b/Dockerfile.aarch64 index 3274289a4d..947b393f46 100644 --- a/Dockerfile.aarch64 +++ b/Dockerfile.aarch64 @@ -119,7 +119,7 @@ RUN set -x \ && rm -rf "$GOPATH" # Install notary server -ENV NOTARY_VERSION docker-v1.10-5 +ENV NOTARY_VERSION v0.2.0 RUN set -x \ && export GOPATH="$(mktemp -d)" \ && git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \ diff --git a/Dockerfile.armhf b/Dockerfile.armhf index d95134bfad..fd6f8721fa 100644 --- a/Dockerfile.armhf +++ b/Dockerfile.armhf @@ -135,7 +135,7 @@ RUN set -x \ && rm -rf "$GOPATH" # Install notary server -ENV NOTARY_VERSION docker-v1.10-5 +ENV NOTARY_VERSION v0.2.0 RUN set -x \ && export GOPATH="$(mktemp -d)" \ && git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \ diff --git a/Dockerfile.ppc64le b/Dockerfile.ppc64le index b48b1ae695..fc1d929f48 100644 --- a/Dockerfile.ppc64le +++ b/Dockerfile.ppc64le @@ -127,16 +127,16 @@ RUN set -x \ go build -o /usr/local/bin/registry-v2-schema1 github.com/docker/distribution/cmd/registry \ && rm -rf "$GOPATH" -# TODO update this when we upgrade to Go 1.5.1+ + # Install notary server -#ENV NOTARY_VERSION docker-v1.10-5 -#RUN set -x \ -# && export GOPATH="$(mktemp -d)" \ -# && git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \ -# && (cd "$GOPATH/src/github.com/docker/notary" && git checkout -q "$NOTARY_VERSION") \ -# && GOPATH="$GOPATH/src/github.com/docker/notary/Godeps/_workspace:$GOPATH" \ -# go build -o /usr/local/bin/notary-server github.com/docker/notary/cmd/notary-server \ -# && rm -rf "$GOPATH" +ENV NOTARY_VERSION v0.2.0 +RUN set -x \ + && export GOPATH="$(mktemp -d)" \ + && git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \ + && (cd "$GOPATH/src/github.com/docker/notary" && git checkout -q "$NOTARY_VERSION") \ + && GOPATH="$GOPATH/src/github.com/docker/notary/Godeps/_workspace:$GOPATH" \ + go build -o /usr/local/bin/notary-server github.com/docker/notary/cmd/notary-server \ + && rm -rf "$GOPATH" # Get the "docker-py" source so we can run their integration tests ENV DOCKER_PY_COMMIT e2878cbcc3a7eef99917adc1be252800b0e41ece diff --git a/Dockerfile.s390x b/Dockerfile.s390x index f80ffb31b1..f46e1e0c9b 100644 --- a/Dockerfile.s390x +++ b/Dockerfile.s390x @@ -108,7 +108,7 @@ RUN set -x \ && rm -rf "$GOPATH" # Install notary server -ENV NOTARY_VERSION docker-v1.10-5 +ENV NOTARY_VERSION v0.2.0 RUN set -x \ && export GOPATH="$(mktemp -d)" \ && git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \ diff --git a/hack/vendor.sh b/hack/vendor.sh index 53deb94279..ee3dde1a89 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -52,7 +52,7 @@ clone git github.com/docker/distribution 7b66c50bb7e0e4b3b83f8fd134a9f6ea4be08b5 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-5 +clone git github.com/docker/notary v0.2.0 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 657c3fd6a2..81f3284368 100644 --- a/integration-cli/docker_cli_create_test.go +++ b/integration-cli/docker_cli_create_test.go @@ -365,7 +365,7 @@ func (s *DockerTrustSuite) TestCreateWhenCertExpired(c *check.C) { func (s *DockerTrustSuite) TestTrustedCreateFromBadTrustServer(c *check.C) { repoName := fmt.Sprintf("%v/dockerclievilcreate/trusted:latest", privateRegistryURL) - evilLocalConfigDir, err := ioutil.TempDir("", "evil-local-config-dir") + evilLocalConfigDir, err := ioutil.TempDir("", "evilcreate-local-config-dir") c.Assert(err, check.IsNil) // tag the image and upload it to the private registry @@ -404,12 +404,16 @@ func (s *DockerTrustSuite) TestTrustedCreateFromBadTrustServer(c *check.C) { c.Assert(err, check.IsNil) c.Assert(string(out), checker.Contains, "Signing and pushing trust metadata", check.Commentf("Missing expected output on trusted push:\n%s", out)) - // Now, try creating with the original client from this new trust server. This should fail. + // Now, try creating with the original client from this new trust server. This should fallback to our cached timestamp and metadata. createCmd = exec.Command(dockerBinary, "create", repoName) s.trustedCmd(createCmd) out, _, err = runCommandWithOutput(createCmd) - c.Assert(err, check.Not(check.IsNil)) - c.Assert(string(out), checker.Contains, "valid signatures did not meet threshold", check.Commentf("Missing expected output on trusted push:\n%s", out)) + if err != nil { + c.Fatalf("Error falling back to cached trust data: %s\n%s", err, out) + } + if !strings.Contains(string(out), "Error while downloading remote metadata, using cached timestamp") { + c.Fatalf("Missing expected output on trusted create:\n%s", out) + } } diff --git a/integration-cli/docker_cli_pull_trusted_test.go b/integration-cli/docker_cli_pull_trusted_test.go index 2f194d08a4..fdaaaa1a82 100644 --- a/integration-cli/docker_cli_pull_trusted_test.go +++ b/integration-cli/docker_cli_pull_trusted_test.go @@ -135,13 +135,16 @@ func (s *DockerTrustSuite) TestTrustedPullFromBadTrustServer(c *check.C) { c.Assert(err, check.IsNil, check.Commentf(out)) c.Assert(string(out), checker.Contains, "Signing and pushing trust metadata", check.Commentf(out)) - // Now, try pulling with the original client from this new trust server. This should fail. + // Now, try pulling with the original client from this new trust server. This should fall back to cached metadata. pullCmd = exec.Command(dockerBinary, "pull", repoName) s.trustedCmd(pullCmd) out, _, err = runCommandWithOutput(pullCmd) - - c.Assert(err, check.NotNil, check.Commentf(out)) - c.Assert(string(out), checker.Contains, "valid signatures did not meet threshold", check.Commentf(out)) + if err != nil { + c.Fatalf("Error falling back to cached trust data: %s\n%s", err, out) + } + if !strings.Contains(string(out), "Error while downloading remote metadata, using cached timestamp") { + c.Fatalf("Missing expected output on trusted pull:\n%s", 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 1cd40408bf..c2e0644796 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -3260,7 +3260,7 @@ func (s *DockerTrustSuite) TestTrustedRunFromBadTrustServer(c *check.C) { // Windows does not support this functionality testRequires(c, DaemonIsLinux) repoName := fmt.Sprintf("%v/dockerclievilrun/trusted:latest", privateRegistryURL) - evilLocalConfigDir, err := ioutil.TempDir("", "evil-local-config-dir") + evilLocalConfigDir, err := ioutil.TempDir("", "evilrun-local-config-dir") if err != nil { c.Fatalf("Failed to create local temp dir") } @@ -3316,15 +3316,15 @@ func (s *DockerTrustSuite) TestTrustedRunFromBadTrustServer(c *check.C) { c.Fatalf("Missing expected output on trusted push:\n%s", out) } - // Now, try running with the original client from this new trust server. This should fail. + // Now, try running with the original client from this new trust server. This should fallback to our cached timestamp and metadata. runCmd = exec.Command(dockerBinary, "run", repoName) s.trustedCmd(runCmd) out, _, err = runCommandWithOutput(runCmd) - if err == nil { - c.Fatalf("Expected to fail on this run due to different remote data: %s\n%s", err, out) - } - if !strings.Contains(string(out), "valid signatures did not meet threshold") { + if err != nil { + c.Fatalf("Error falling back to cached trust data: %s\n%s", err, out) + } + if !strings.Contains(string(out), "Error while downloading remote metadata, using cached timestamp") { c.Fatalf("Missing expected output on trusted push:\n%s", out) } } diff --git a/integration-cli/trust_server.go b/integration-cli/trust_server.go index 9629e84575..a8ec3181b1 100644 --- a/integration-cli/trust_server.go +++ b/integration-cli/trust_server.go @@ -237,7 +237,7 @@ func (s *DockerTrustSuite) setupDelegations(c *check.C, repoName, pwd string) { if err != nil { c.Fatalf("Error creating delegation key: %s\n", err) } - err = nRepo.AddDelegation("targets/releases", 1, []data.PublicKey{delgKey}, []string{""}) + err = nRepo.AddDelegation("targets/releases", []data.PublicKey{delgKey}, []string{""}) if err != nil { c.Fatalf("Error creating delegation: %s\n", err) } diff --git a/vendor/src/github.com/docker/notary/Makefile b/vendor/src/github.com/docker/notary/Makefile index 949d79864a..447514af09 100644 --- a/vendor/src/github.com/docker/notary/Makefile +++ b/vendor/src/github.com/docker/notary/Makefile @@ -42,14 +42,6 @@ GO_VERSION = $(shell go version | awk '{print $$3}') .DELETE_ON_ERROR: cover .DEFAULT: default -go_version: -ifeq (,$(findstring go1.5.,$(GO_VERSION))) - $(error Requires go version 1.5.x - found $(GO_VERSION)) -else - @echo -endif - - all: AUTHORS clean fmt vet fmt lint build test binaries AUTHORS: .git/HEAD @@ -71,7 +63,23 @@ ${PREFIX}/bin/notary-signer: NOTARY_VERSION $(shell find . -type f -name '*.go') @echo "+ $@" @godep go build -tags ${NOTARY_BUILDTAGS} -o $@ ${GO_LDFLAGS} ./cmd/notary-signer -vet: go_version +ifeq ($(shell uname -s),Darwin) +${PREFIX}/bin/static/notary-server: + @echo "notary-server: static builds not supported on OS X" + +${PREFIX}/bin/static/notary-signer: + @echo "notary-signer: static builds not supported on OS X" +else +${PREFIX}/bin/static/notary-server: NOTARY_VERSION $(shell find . -type f -name '*.go') + @echo "+ $@" + @godep go build -tags ${NOTARY_BUILDTAGS} -o $@ ${GO_LDFLAGS_STATIC} ./cmd/notary-server + +${PREFIX}/bin/static/notary-signer: NOTARY_VERSION $(shell find . -type f -name '*.go') + @echo "+ $@" + @godep go build -tags ${NOTARY_BUILDTAGS} -o $@ ${GO_LDFLAGS_STATIC} ./cmd/notary-signer +endif + +vet: @echo "+ $@" ifeq ($(shell uname -s), Darwin) @test -z "$(shell find . -iname *test*.go | grep -v _test.go | grep -v Godeps | xargs echo "This file should end with '_test':" | tee /dev/stderr)" @@ -88,14 +96,24 @@ lint: @echo "+ $@" @test -z "$$(golint ./... | grep -v .pb. | grep -v Godeps/_workspace/src/ | tee /dev/stderr)" -build: go_version +# Requires that the following: +# go get -u github.com/client9/misspell/cmd/misspell +# +# be run first + +# misspell target, don't include Godeps, binaries, python tests, or git files +misspell: + @echo "+ $@" + @test -z "$$(find . -name '*' | grep -v Godeps/_workspace/src/ | grep -v bin/ | grep -v misc/ | grep -v .git/ | xargs misspell | tee /dev/stderr)" + +build: @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 +test: @echo Note: when testing with a yubikey plugged in, make sure to include 'TESTOPTS="-p 1"' @echo "+ $@ $(TESTOPTS)" @echo @@ -121,7 +139,7 @@ define gocover $(GO_EXC) test $(OPTS) $(TESTOPTS) -covermode="$(COVERMODE)" -coverprofile="$(COVERDIR)/$(subst /,-,$(1)).$(subst $(_space),.,$(NOTARY_BUILDTAGS)).coverage.txt" "$(1)" || exit 1; endef -gen-cover: go_version +gen-cover: @mkdir -p "$(COVERDIR)" $(foreach PKG,$(PKGS),$(call gocover,$(PKG))) rm -f "$(COVERDIR)"/*testutils*.coverage.txt @@ -150,7 +168,10 @@ covmerge: clean-protos: @rm proto/*.pb.go -binaries: go_version ${PREFIX}/bin/notary-server ${PREFIX}/bin/notary ${PREFIX}/bin/notary-signer +binaries: ${PREFIX}/bin/notary-server ${PREFIX}/bin/notary ${PREFIX}/bin/notary-signer + @echo "+ $@" + +static: ${PREFIX}/bin/static/notary-server ${PREFIX}/bin/static/notary-signer @echo "+ $@" define template @@ -158,7 +179,7 @@ mkdir -p ${PREFIX}/cross/$(1)/$(2); GOOS=$(1) GOARCH=$(2) CGO_ENABLED=0 go build -o ${PREFIX}/cross/$(1)/$(2)/notary -a -tags "static_build netgo" -installsuffix netgo ${GO_LDFLAGS_STATIC} ./cmd/notary; endef -cross: go_version +cross: $(foreach GOARCH,$(GOARCHS),$(foreach GOOS,$(GOOSES),$(call template,$(GOOS),$(GOARCH)))) diff --git a/vendor/src/github.com/docker/notary/README.md b/vendor/src/github.com/docker/notary/README.md index ed3ca49351..96cd5be5c6 100644 --- a/vendor/src/github.com/docker/notary/README.md +++ b/vendor/src/github.com/docker/notary/README.md @@ -1,4 +1,5 @@ -# Notary [![Circle CI](https://circleci.com/gh/docker/notary/tree/master.svg?style=shield)](https://circleci.com/gh/docker/notary/tree/master) +# Notary +[![Circle CI](https://circleci.com/gh/docker/notary/tree/master.svg?style=shield)](https://circleci.com/gh/docker/notary/tree/master) [![CodeCov](https://codecov.io/github/docker/notary/coverage.svg?branch=master)](https://codecov.io/github/docker/notary) The Notary project comprises a [server](cmd/notary-server) and a [client](cmd/notary) for running and interacting with trusted collections. diff --git a/vendor/src/github.com/docker/notary/circle.yml b/vendor/src/github.com/docker/notary/circle.yml index 163d610f24..6b98a161f1 100644 --- a/vendor/src/github.com/docker/notary/circle.yml +++ b/vendor/src/github.com/docker/notary/circle.yml @@ -6,7 +6,7 @@ machine: post: # Install many go versions - - gvm install go1.5.1 -B --name=stable + - gvm install go1.6 -B --name=stable environment: # Convenient shortcuts to "common" locations @@ -37,10 +37,11 @@ dependencies: pwd: $BASE_STABLE post: - # For the stable go version, additionally install linting tools + # For the stable go version, additionally install linting and misspell tools - > gvm use stable && - go get github.com/golang/lint/golint + go get github.com/golang/lint/golint && + go get -u github.com/client9/misspell/cmd/misspell test: pre: # Output the go versions we are going to test @@ -62,6 +63,10 @@ test: - gvm use stable && make lint: pwd: $BASE_STABLE + # MISSPELL + - gvm use stable && make misspell: + pwd: $BASE_STABLE + override: # Test stable, and report # hacking this to be parallel diff --git a/vendor/src/github.com/docker/notary/client/changelist/change.go b/vendor/src/github.com/docker/notary/client/changelist/change.go index 311857aa61..3307189c81 100644 --- a/vendor/src/github.com/docker/notary/client/changelist/change.go +++ b/vendor/src/github.com/docker/notary/client/changelist/change.go @@ -17,7 +17,7 @@ const ( // Types for TufChanges are namespaced by the Role they // are relevant for. The Root and Targets roles are the // only ones for which user action can cause a change, as -// all changes in Snapshot and Timestamp are programatically +// all changes in Snapshot and Timestamp are programmatically // generated base on Root and Targets changes. const ( TypeRootRole = "role" @@ -82,14 +82,13 @@ func (c TufChange) Content() []byte { // this includes creating a delegations. This format is used to avoid // unexpected race conditions between humans modifying the same delegation type TufDelegation struct { - NewName string `json:"new_name,omitempty"` - NewThreshold int `json:"threshold, omitempty"` - AddKeys data.KeyList `json:"add_keys, omitempty"` - RemoveKeys []string `json:"remove_keys,omitempty"` - AddPaths []string `json:"add_paths,omitempty"` - RemovePaths []string `json:"remove_paths,omitempty"` - AddPathHashPrefixes []string `json:"add_prefixes,omitempty"` - RemovePathHashPrefixes []string `json:"remove_prefixes,omitempty"` + NewName string `json:"new_name,omitempty"` + NewThreshold int `json:"threshold, omitempty"` + AddKeys data.KeyList `json:"add_keys, omitempty"` + RemoveKeys []string `json:"remove_keys,omitempty"` + AddPaths []string `json:"add_paths,omitempty"` + RemovePaths []string `json:"remove_paths,omitempty"` + ClearAllPaths bool `json:"clear_paths,omitempty"` } // ToNewRole creates a fresh role object from the TufDelegation data @@ -98,5 +97,5 @@ func (td TufDelegation) ToNewRole(scope string) (*data.Role, error) { if td.NewName != "" { name = td.NewName } - return data.NewRole(name, td.NewThreshold, td.AddKeys.IDs(), td.AddPaths, td.AddPathHashPrefixes) + return data.NewRole(name, td.NewThreshold, td.AddKeys.IDs(), td.AddPaths) } diff --git a/vendor/src/github.com/docker/notary/client/client.go b/vendor/src/github.com/docker/notary/client/client.go index b383c94dca..cbb4977132 100644 --- a/vendor/src/github.com/docker/notary/client/client.go +++ b/vendor/src/github.com/docker/notary/client/client.go @@ -9,6 +9,7 @@ import ( "net/url" "os" "path/filepath" + "strings" "time" "github.com/Sirupsen/logrus" @@ -20,23 +21,13 @@ import ( "github.com/docker/notary/tuf" tufclient "github.com/docker/notary/tuf/client" "github.com/docker/notary/tuf/data" - "github.com/docker/notary/tuf/keys" "github.com/docker/notary/tuf/signed" "github.com/docker/notary/tuf/store" -) - -const ( - maxSize = 5 << 20 + "github.com/docker/notary/tuf/utils" ) func init() { - data.SetDefaultExpiryTimes( - map[string]int{ - "root": 3650, - "targets": 1095, - "snapshot": 1095, - }, - ) + data.SetDefaultExpiryTimes(notary.NotaryDefaultExpiries) } // ErrRepoNotInitialized is returned when trying to publish an uninitialized @@ -118,7 +109,6 @@ func repositoryFromKeystores(baseDir, gun, baseURL string, rt http.RoundTripper, nRepo.tufRepoPath, "metadata", "json", - "", ) if err != nil { return nil, err @@ -218,11 +208,16 @@ func (r *NotaryRepository) Initialize(rootKeyID string, serverManagedRoles ...st return fmt.Errorf("invalid format for root key: %s", privKey.Algorithm()) } - kdb := keys.NewDB() - err = addKeyForRole(kdb, data.CanonicalRootRole, rootKey) - if err != nil { - return err - } + var ( + rootRole = data.NewBaseRole( + data.CanonicalRootRole, + notary.MinThreshold, + rootKey, + ) + timestampRole data.BaseRole + snapshotRole data.BaseRole + targetsRole data.BaseRole + ) // we want to create all the local keys first so we don't have to // make unnecessary network calls @@ -232,8 +227,19 @@ func (r *NotaryRepository) Initialize(rootKeyID string, serverManagedRoles ...st if err != nil { return err } - if err := addKeyForRole(kdb, role, key); err != nil { - return err + switch role { + case data.CanonicalSnapshotRole: + snapshotRole = data.NewBaseRole( + role, + notary.MinThreshold, + key, + ) + case data.CanonicalTargetsRole: + targetsRole = data.NewBaseRole( + role, + notary.MinThreshold, + key, + ) } } for _, role := range remotelyManagedKeys { @@ -244,14 +250,31 @@ func (r *NotaryRepository) Initialize(rootKeyID string, serverManagedRoles ...st } 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 + switch role { + case data.CanonicalSnapshotRole: + snapshotRole = data.NewBaseRole( + role, + notary.MinThreshold, + key, + ) + case data.CanonicalTimestampRole: + timestampRole = data.NewBaseRole( + role, + notary.MinThreshold, + key, + ) } } - r.tufRepo = tuf.NewRepo(kdb, r.CryptoService) + r.tufRepo = tuf.NewRepo(r.CryptoService) - err = r.tufRepo.InitRoot(false) + err = r.tufRepo.InitRoot( + rootRole, + timestampRole, + snapshotRole, + targetsRole, + false, + ) if err != nil { logrus.Debug("Error on InitRoot: ", err.Error()) return err @@ -305,96 +328,6 @@ func addChange(cl *changelist.FileChangelist, c changelist.Change, roles ...stri return nil } -// AddDelegation creates a new changelist entry to add a delegation to the repository -// when the changelist gets applied at publish time. This does not do any validation -// other than checking the name of the delegation to add - all that will happen -// at publish time. -func (r *NotaryRepository) AddDelegation(name string, threshold int, - delegationKeys []data.PublicKey, paths []string) error { - - if !data.IsDelegation(name) { - return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"} - } - - cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) - if err != nil { - return err - } - defer cl.Close() - - logrus.Debugf(`Adding delegation "%s" with threshold %d, and %d keys\n`, - name, threshold, len(delegationKeys)) - - tdJSON, err := json.Marshal(&changelist.TufDelegation{ - NewThreshold: threshold, - AddKeys: data.KeyList(delegationKeys), - AddPaths: paths, - }) - if err != nil { - return err - } - - template := changelist.NewTufChange( - changelist.ActionCreate, - name, - changelist.TypeTargetsDelegation, - "", // no path - tdJSON, - ) - - return addChange(cl, template, name) -} - -// RemoveDelegation creates a new changelist entry to remove a delegation from -// the repository when the changelist gets applied at publish time. -// This does not validate that the delegation exists, since one might exist -// after applying all changes. -func (r *NotaryRepository) RemoveDelegation(name string, keyIDs, paths []string, removeAll bool) error { - - if !data.IsDelegation(name) { - return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"} - } - - cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) - if err != nil { - return err - } - defer cl.Close() - - logrus.Debugf(`Removing delegation "%s"\n`, name) - var template *changelist.TufChange - - // We use the Delete action only for force removal, Update is used for removing individual keys and paths - if removeAll { - template = changelist.NewTufChange( - changelist.ActionDelete, - name, - changelist.TypeTargetsDelegation, - "", // no path - nil, // deleting role, no data needed - ) - - } else { - tdJSON, err := json.Marshal(&changelist.TufDelegation{ - RemoveKeys: keyIDs, - RemovePaths: paths, - }) - if err != nil { - return err - } - - template = changelist.NewTufChange( - changelist.ActionUpdate, - name, - changelist.TypeTargetsDelegation, - "", // no path - tdJSON, - ) - } - - return addChange(cl, template, name) -} - // AddTarget creates new changelist entries to add a target to the given roles // in the repository when the changelist gets applied at publish time. // If roles are unspecified, the default role is "targets". @@ -452,10 +385,24 @@ func (r *NotaryRepository) ListTargets(roles ...string) ([]*TargetWithRole, erro } targets := make(map[string]*TargetWithRole) for _, role := range roles { - // we don't need to do anything special with removing role from - // roles because listSubtree always processes role and only excludes - // descendant delegations that appear in roles. - r.listSubtree(targets, role, roles...) + // Define an array of roles to skip for this walk (see IMPORTANT comment above) + skipRoles := utils.StrSliceRemove(roles, role) + + // Define a visitor function to populate the targets map in priority order + listVisitorFunc := func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} { + // We found targets so we should try to add them to our targets map + for targetName, targetMeta := range tgt.Signed.Targets { + // Follow the priority by not overriding previously set targets + // and check that this path is valid with this role + if _, ok := targets[targetName]; ok || !validRole.CheckPaths(targetName) { + continue + } + targets[targetName] = + &TargetWithRole{Target: Target{Name: targetName, Hashes: targetMeta.Hashes, Length: targetMeta.Length}, Role: validRole.Name} + } + return nil + } + r.tufRepo.WalkTargets("", role, listVisitorFunc, skipRoles...) } var targetList []*TargetWithRole @@ -466,34 +413,6 @@ func (r *NotaryRepository) ListTargets(roles ...string) ([]*TargetWithRole, erro return targetList, nil } -func (r *NotaryRepository) listSubtree(targets map[string]*TargetWithRole, role string, exclude ...string) { - excl := make(map[string]bool) - for _, r := range exclude { - excl[r] = true - } - roles := []string{role} - for len(roles) > 0 { - role = roles[0] - roles = roles[1:] - tgts, ok := r.tufRepo.Targets[role] - if !ok { - // not every role has to exist - continue - } - for name, meta := range tgts.Signed.Targets { - if _, ok := targets[name]; !ok { - targets[name] = &TargetWithRole{ - Target: Target{Name: name, Hashes: meta.Hashes, Length: meta.Length}, Role: role} - } - } - for _, d := range tgts.Signed.Delegations.Roles { - if !excl[d.Name] { - roles = append(roles, d.Name) - } - } - } -} - // GetTargetByName returns a target given a name. If no roles are passed // it uses the targets role and does a search of the entire delegation // graph, finding the first entry in a breadth first search of the delegations. @@ -502,7 +421,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(false) + _, err := r.Update(false) if err != nil { return nil, err } @@ -510,11 +429,30 @@ func (r *NotaryRepository) GetTargetByName(name string, roles ...string) (*Targe if len(roles) == 0 { roles = append(roles, data.CanonicalTargetsRole) } + var resultMeta data.FileMeta + var resultRoleName string + var foundTarget bool for _, role := range roles { - meta, foundRole := c.TargetMeta(role, name, roles...) - if meta != nil { - return &TargetWithRole{ - Target: Target{Name: name, Hashes: meta.Hashes, Length: meta.Length}, Role: foundRole}, nil + // Define an array of roles to skip for this walk (see IMPORTANT comment above) + skipRoles := utils.StrSliceRemove(roles, role) + + // Define a visitor function to find the specified target + getTargetVisitorFunc := func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} { + if tgt == nil { + return nil + } + // We found the target and validated path compatibility in our walk, + // so we should stop our walk and set the resultMeta and resultRoleName variables + if resultMeta, foundTarget = tgt.Signed.Targets[name]; foundTarget { + resultRoleName = validRole.Name + return tuf.StopWalk{} + } + return nil + } + err = r.tufRepo.WalkTargets(name, role, getTargetVisitorFunc, skipRoles...) + // Check that we didn't error, and that we assigned to our target + if err == nil && foundTarget { + return &TargetWithRole{Target: Target{Name: name, Hashes: resultMeta.Hashes, Length: resultMeta.Length}, Role: resultRoleName}, nil } } return nil, fmt.Errorf("No trust data for %s", name) @@ -532,45 +470,6 @@ func (r *NotaryRepository) GetChangelist() (changelist.Changelist, error) { return cl, nil } -// GetDelegationRoles returns the keys and roles of the repository's delegations -func (r *NotaryRepository) GetDelegationRoles() ([]*data.Role, error) { - // Update state of the repo to latest - if _, err := r.Update(false); err != nil { - return nil, err - } - - // All top level delegations (ex: targets/level1) are stored exclusively in targets.json - targets, ok := r.tufRepo.Targets[data.CanonicalTargetsRole] - if !ok { - return nil, store.ErrMetaNotFound{Resource: data.CanonicalTargetsRole} - } - - allDelegations := targets.Signed.Delegations.Roles - - // make a copy for traversing nested delegations - delegationsList := make([]*data.Role, len(allDelegations)) - copy(delegationsList, allDelegations) - - // Now traverse to lower level delegations (ex: targets/level1/level2) - for len(delegationsList) > 0 { - // Pop off first delegation to traverse - delegation := delegationsList[0] - delegationsList = delegationsList[1:] - - // Get metadata - delegationMeta, ok := r.tufRepo.Targets[delegation.Name] - // If we get an error, don't try to traverse further into this subtree because it doesn't exist or is malformed - if !ok { - continue - } - - // Add nested delegations to return list and exploration list - allDelegations = append(allDelegations, delegationMeta.Signed.Delegations.Roles...) - delegationsList = append(delegationsList, delegationMeta.Signed.Delegations.Roles...) - } - return allDelegations, nil -} - // RoleWithSignatures is a Role with its associated signatures type RoleWithSignatures struct { Signatures []data.Signature @@ -604,7 +503,7 @@ func (r *NotaryRepository) ListRoles() ([]RoleWithSignatures, error) { case data.CanonicalTimestampRole: roleWithSig.Signatures = r.tufRepo.Timestamp.Signatures default: - // If the role isn't a delegation, we should error -- this is only possible if we have invalid keyDB state + // If the role isn't a delegation, we should error -- this is only possible if we have invalid state if !data.IsDelegation(role.Name) { return nil, data.ErrInvalidRole{Role: role.Name, Reason: "invalid role name"} } @@ -705,7 +604,7 @@ func (r *NotaryRepository) Publish() error { r.tufRepo, data.CanonicalSnapshotRole) if err == nil { - // Only update the snapshot if we've sucessfully signed it. + // Only update the snapshot if we've successfully 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 @@ -743,11 +642,10 @@ func (r *NotaryRepository) Publish() error { // 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) + tufRepo := tuf.NewRepo(r.CryptoService) logrus.Debugf("Loading trusted collection.") - rootJSON, err := r.fileStore.GetMeta("root", 0) + rootJSON, err := r.fileStore.GetMeta("root", -1) if err != nil { return err } @@ -760,7 +658,7 @@ func (r *NotaryRepository) bootstrapRepo() error { if err != nil { return err } - targetsJSON, err := r.fileStore.GetMeta("targets", 0) + targetsJSON, err := r.fileStore.GetMeta("targets", -1) if err != nil { return err } @@ -771,7 +669,7 @@ func (r *NotaryRepository) bootstrapRepo() error { } tufRepo.SetTargets("targets", targets) - snapshotJSON, err := r.fileStore.GetMeta("snapshot", 0) + snapshotJSON, err := r.fileStore.GetMeta("snapshot", -1) if err == nil { snapshot := &data.SignedSnapshot{} err = json.Unmarshal(snapshotJSON, snapshot) @@ -854,7 +752,10 @@ func (r *NotaryRepository) Update(forWrite bool) (*tufclient.Client, error) { } err = c.Update() if err != nil { - if notFound, ok := err.(store.ErrMetaNotFound); ok && notFound.Resource == data.CanonicalRootRole { + // notFound.Resource may include a checksum so when the role is root, + // it will be root.json or root..json. Therefore best we can + // do it match a "root." prefix + if notFound, ok := err.(store.ErrMetaNotFound); ok && strings.HasPrefix(notFound.Resource, data.CanonicalRootRole+".") { return nil, r.errRepositoryNotExist() } return nil, err @@ -876,7 +777,7 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl // 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) + rootJSON, cachedRootErr := r.fileStore.GetMeta("root", -1) if cachedRootErr == nil { signedRoot, cachedRootErr = r.validateRoot(rootJSON) @@ -890,7 +791,8 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl // checking for initialization of the repo). // if remote store successfully set up, try and get root from remote - tmpJSON, err := remote.GetMeta("root", maxSize) + // We don't have any local data to determine the size of root, so try the maximum (though it is restricted at 100MB) + tmpJSON, err := remote.GetMeta("root", -1) 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. @@ -912,8 +814,7 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl } } - kdb := keys.NewDB() - r.tufRepo = tuf.NewRepo(kdb, r.CryptoService) + r.tufRepo = tuf.NewRepo(r.CryptoService) if signedRoot == nil { return nil, ErrRepoNotInitialized{} @@ -927,7 +828,6 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl return tufclient.NewClient( r.tufRepo, remote, - kdb, r.fileStore, ), nil } @@ -1020,7 +920,7 @@ func (r *NotaryRepository) DeleteTrustData() error { if err := r.fileStore.RemoveAll(); err != nil { return fmt.Errorf("error clearing TUF repo data: %v", err) } - r.tufRepo = tuf.NewRepo(nil, nil) + r.tufRepo = tuf.NewRepo(nil) // Clear certificates certificates, err := r.CertStore.GetCertificatesByCN(r.gun) if err != nil { diff --git a/vendor/src/github.com/docker/notary/client/delegations.go b/vendor/src/github.com/docker/notary/client/delegations.go new file mode 100644 index 0000000000..c28e3d8469 --- /dev/null +++ b/vendor/src/github.com/docker/notary/client/delegations.go @@ -0,0 +1,294 @@ +package client + +import ( + "encoding/json" + "fmt" + "path/filepath" + + "github.com/Sirupsen/logrus" + "github.com/docker/notary" + "github.com/docker/notary/client/changelist" + "github.com/docker/notary/tuf/data" + "github.com/docker/notary/tuf/store" + "github.com/docker/notary/tuf/utils" +) + +// AddDelegation creates changelist entries to add provided delegation public keys and paths. +// This method composes AddDelegationRoleAndKeys and AddDelegationPaths (each creates one changelist if called). +func (r *NotaryRepository) AddDelegation(name string, delegationKeys []data.PublicKey, paths []string) error { + if len(delegationKeys) > 0 { + err := r.AddDelegationRoleAndKeys(name, delegationKeys) + if err != nil { + return err + } + } + if len(paths) > 0 { + err := r.AddDelegationPaths(name, paths) + if err != nil { + return err + } + } + return nil +} + +// AddDelegationRoleAndKeys creates a changelist entry to add provided delegation public keys. +// This method is the simplest way to create a new delegation, because the delegation must have at least +// one key upon creation to be valid since we will reject the changelist while validating the threshold. +func (r *NotaryRepository) AddDelegationRoleAndKeys(name string, delegationKeys []data.PublicKey) error { + + if !data.IsDelegation(name) { + return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"} + } + + cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) + if err != nil { + return err + } + defer cl.Close() + + logrus.Debugf(`Adding delegation "%s" with threshold %d, and %d keys\n`, + name, notary.MinThreshold, len(delegationKeys)) + + // Defaulting to threshold of 1, since we don't allow for larger thresholds at the moment. + tdJSON, err := json.Marshal(&changelist.TufDelegation{ + NewThreshold: notary.MinThreshold, + AddKeys: data.KeyList(delegationKeys), + }) + if err != nil { + return err + } + + template := newCreateDelegationChange(name, tdJSON) + return addChange(cl, template, name) +} + +// AddDelegationPaths creates a changelist entry to add provided paths to an existing delegation. +// This method cannot create a new delegation itself because the role must meet the key threshold upon creation. +func (r *NotaryRepository) AddDelegationPaths(name string, paths []string) error { + + if !data.IsDelegation(name) { + return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"} + } + + cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) + if err != nil { + return err + } + defer cl.Close() + + logrus.Debugf(`Adding %s paths to delegation %s\n`, paths, name) + + tdJSON, err := json.Marshal(&changelist.TufDelegation{ + AddPaths: paths, + }) + if err != nil { + return err + } + + template := newCreateDelegationChange(name, tdJSON) + return addChange(cl, template, name) +} + +// RemoveDelegationKeysAndPaths creates changelist entries to remove provided delegation key IDs and paths. +// This method composes RemoveDelegationPaths and RemoveDelegationKeys (each creates one changelist if called). +func (r *NotaryRepository) RemoveDelegationKeysAndPaths(name string, keyIDs, paths []string) error { + if len(paths) > 0 { + err := r.RemoveDelegationPaths(name, paths) + if err != nil { + return err + } + } + if len(keyIDs) > 0 { + err := r.RemoveDelegationKeys(name, keyIDs) + if err != nil { + return err + } + } + return nil +} + +// RemoveDelegationRole creates a changelist to remove all paths and keys from a role, and delete the role in its entirety. +func (r *NotaryRepository) RemoveDelegationRole(name string) error { + + if !data.IsDelegation(name) { + return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"} + } + + cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) + if err != nil { + return err + } + defer cl.Close() + + logrus.Debugf(`Removing delegation "%s"\n`, name) + + template := newDeleteDelegationChange(name, nil) + return addChange(cl, template, name) +} + +// RemoveDelegationPaths creates a changelist entry to remove provided paths from an existing delegation. +func (r *NotaryRepository) RemoveDelegationPaths(name string, paths []string) error { + + if !data.IsDelegation(name) { + return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"} + } + + cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) + if err != nil { + return err + } + defer cl.Close() + + logrus.Debugf(`Removing %s paths from delegation "%s"\n`, paths, name) + + tdJSON, err := json.Marshal(&changelist.TufDelegation{ + RemovePaths: paths, + }) + if err != nil { + return err + } + + template := newUpdateDelegationChange(name, tdJSON) + return addChange(cl, template, name) +} + +// RemoveDelegationKeys creates a changelist entry to remove provided keys from an existing delegation. +// When this changelist is applied, if the specified keys are the only keys left in the role, +// the role itself will be deleted in its entirety. +func (r *NotaryRepository) RemoveDelegationKeys(name string, keyIDs []string) error { + + if !data.IsDelegation(name) { + return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"} + } + + cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) + if err != nil { + return err + } + defer cl.Close() + + logrus.Debugf(`Removing %s keys from delegation "%s"\n`, keyIDs, name) + + tdJSON, err := json.Marshal(&changelist.TufDelegation{ + RemoveKeys: keyIDs, + }) + if err != nil { + return err + } + + template := newUpdateDelegationChange(name, tdJSON) + return addChange(cl, template, name) +} + +// ClearDelegationPaths creates a changelist entry to remove all paths from an existing delegation. +func (r *NotaryRepository) ClearDelegationPaths(name string) error { + + if !data.IsDelegation(name) { + return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"} + } + + cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist")) + if err != nil { + return err + } + defer cl.Close() + + logrus.Debugf(`Removing all paths from delegation "%s"\n`, name) + + tdJSON, err := json.Marshal(&changelist.TufDelegation{ + ClearAllPaths: true, + }) + if err != nil { + return err + } + + template := newUpdateDelegationChange(name, tdJSON) + return addChange(cl, template, name) +} + +func newUpdateDelegationChange(name string, content []byte) *changelist.TufChange { + return changelist.NewTufChange( + changelist.ActionUpdate, + name, + changelist.TypeTargetsDelegation, + "", // no path for delegations + content, + ) +} + +func newCreateDelegationChange(name string, content []byte) *changelist.TufChange { + return changelist.NewTufChange( + changelist.ActionCreate, + name, + changelist.TypeTargetsDelegation, + "", // no path for delegations + content, + ) +} + +func newDeleteDelegationChange(name string, content []byte) *changelist.TufChange { + return changelist.NewTufChange( + changelist.ActionDelete, + name, + changelist.TypeTargetsDelegation, + "", // no path for delegations + content, + ) +} + +// GetDelegationRoles returns the keys and roles of the repository's delegations +// Also converts key IDs to canonical key IDs to keep consistent with signing prompts +func (r *NotaryRepository) GetDelegationRoles() ([]*data.Role, error) { + // Update state of the repo to latest + if _, err := r.Update(false); err != nil { + return nil, err + } + + // All top level delegations (ex: targets/level1) are stored exclusively in targets.json + _, ok := r.tufRepo.Targets[data.CanonicalTargetsRole] + if !ok { + return nil, store.ErrMetaNotFound{Resource: data.CanonicalTargetsRole} + } + + // make a copy for traversing nested delegations + allDelegations := []*data.Role{} + + // Define a visitor function to populate the delegations list and translate their key IDs to canonical IDs + delegationCanonicalListVisitor := func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} { + // For the return list, update with a copy that includes canonicalKeyIDs + // These aren't validated by the validRole + canonicalDelegations, err := translateDelegationsToCanonicalIDs(tgt.Signed.Delegations) + if err != nil { + return err + } + allDelegations = append(allDelegations, canonicalDelegations...) + return nil + } + err := r.tufRepo.WalkTargets("", "", delegationCanonicalListVisitor) + if err != nil { + return nil, err + } + return allDelegations, nil +} + +func translateDelegationsToCanonicalIDs(delegationInfo data.Delegations) ([]*data.Role, error) { + canonicalDelegations := make([]*data.Role, len(delegationInfo.Roles)) + copy(canonicalDelegations, delegationInfo.Roles) + delegationKeys := delegationInfo.Keys + for i, delegation := range canonicalDelegations { + canonicalKeyIDs := []string{} + for _, keyID := range delegation.KeyIDs { + pubKey, ok := delegationKeys[keyID] + if !ok { + return nil, fmt.Errorf("Could not translate canonical key IDs for %s", delegation.Name) + } + canonicalKeyID, err := utils.CanonicalKeyID(pubKey) + if err != nil { + return nil, fmt.Errorf("Could not translate canonical key IDs for %s: %v", delegation.Name, err) + } + canonicalKeyIDs = append(canonicalKeyIDs, canonicalKeyID) + } + canonicalDelegations[i].KeyIDs = canonicalKeyIDs + } + return canonicalDelegations, nil +} diff --git a/vendor/src/github.com/docker/notary/client/helpers.go b/vendor/src/github.com/docker/notary/client/helpers.go index a9fd590a9f..46648a11b5 100644 --- a/vendor/src/github.com/docker/notary/client/helpers.go +++ b/vendor/src/github.com/docker/notary/client/helpers.go @@ -12,8 +12,8 @@ import ( "github.com/docker/notary/client/changelist" tuf "github.com/docker/notary/tuf" "github.com/docker/notary/tuf/data" - "github.com/docker/notary/tuf/keys" "github.com/docker/notary/tuf/store" + "github.com/docker/notary/tuf/utils" ) // Use this to initialize remote HTTPStores from the config settings @@ -22,7 +22,6 @@ func getRemoteStore(baseURL, gun string, rt http.RoundTripper) (store.RemoteStor baseURL+"/v2/"+gun+"/_trust/tuf/", "", "json", - "", "key", rt, ) @@ -80,53 +79,51 @@ func changeTargetsDelegation(repo *tuf.Repo, c changelist.Change) error { if err != nil { return err } - r, err := repo.GetDelegation(c.Scope()) - if _, ok := err.(data.ErrNoSuchRole); err != nil && !ok { - // error that wasn't ErrNoSuchRole - return err - } - if err == nil { - // role existed, attempt to merge paths and keys - if err := r.AddPaths(td.AddPaths); err != nil { - return err - } - return repo.UpdateDelegations(r, td.AddKeys) - } - // create brand new role - r, err = td.ToNewRole(c.Scope()) + + // Try to create brand new role or update one + // First add the keys, then the paths. We can only add keys and paths in this scenario + err = repo.UpdateDelegationKeys(c.Scope(), td.AddKeys, []string{}, td.NewThreshold) if err != nil { return err } - return repo.UpdateDelegations(r, td.AddKeys) + return repo.UpdateDelegationPaths(c.Scope(), td.AddPaths, []string{}, false) case changelist.ActionUpdate: td := changelist.TufDelegation{} err := json.Unmarshal(c.Content(), &td) if err != nil { return err } - r, err := repo.GetDelegation(c.Scope()) + delgRole, err := repo.GetDelegationRole(c.Scope()) if err != nil { return err } + + // We need to translate the keys from canonical ID to TUF ID for compatibility + canonicalToTUFID := make(map[string]string) + for tufID, pubKey := range delgRole.Keys { + canonicalID, err := utils.CanonicalKeyID(pubKey) + if err != nil { + return err + } + canonicalToTUFID[canonicalID] = tufID + } + + removeTUFKeyIDs := []string{} + for _, canonID := range td.RemoveKeys { + removeTUFKeyIDs = append(removeTUFKeyIDs, canonicalToTUFID[canonID]) + } + // If we specify the only keys left delete the role, else just delete specified keys - if strings.Join(r.KeyIDs, ";") == strings.Join(td.RemoveKeys, ";") && len(td.AddKeys) == 0 { - r := data.Role{Name: c.Scope()} - return repo.DeleteDelegation(r) + if strings.Join(delgRole.ListKeyIDs(), ";") == strings.Join(removeTUFKeyIDs, ";") && len(td.AddKeys) == 0 { + return repo.DeleteDelegation(c.Scope()) } - // if we aren't deleting and the role exists, merge - if err := r.AddPaths(td.AddPaths); err != nil { + err = repo.UpdateDelegationKeys(c.Scope(), td.AddKeys, removeTUFKeyIDs, td.NewThreshold) + if err != nil { return err } - if err := r.AddPathHashPrefixes(td.AddPathHashPrefixes); err != nil { - return err - } - r.RemoveKeys(td.RemoveKeys) - r.RemovePaths(td.RemovePaths) - r.RemovePathHashPrefixes(td.RemovePathHashPrefixes) - return repo.UpdateDelegations(r, td.AddKeys) + return repo.UpdateDelegationPaths(c.Scope(), td.AddPaths, td.RemovePaths, td.ClearAllPaths) case changelist.ActionDelete: - r := data.Role{Name: c.Scope()} - return repo.DeleteDelegation(r) + return repo.DeleteDelegation(c.Scope()) default: return fmt.Errorf("unsupported action against delegations: %s", c.Action()) } @@ -239,19 +236,6 @@ func getRemoteKey(url, gun, role string, rt http.RoundTripper) (data.PublicKey, 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 - } - kdb.AddKey(key) - if err := kdb.AddRole(theRole); 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 diff --git a/vendor/src/github.com/docker/notary/const.go b/vendor/src/github.com/docker/notary/const.go index a1140c0dc0..566c7f0fbd 100644 --- a/vendor/src/github.com/docker/notary/const.go +++ b/vendor/src/github.com/docker/notary/const.go @@ -1,7 +1,15 @@ package notary +import ( + "time" +) + // application wide constants const ( + // MaxDownloadSize is the maximum size we'll download for metadata if no limit is given + MaxDownloadSize int64 = 100 << 20 + // MaxTimestampSize is the maximum size of timestamp metadata - 1MiB. + MaxTimestampSize int64 = 1 << 20 // MinRSABitSize is the minimum bit size for RSA keys allowed in notary MinRSABitSize = 2048 // MinThreshold requires a minimum of one threshold for roles; currently we do not support a higher threshold @@ -14,4 +22,29 @@ const ( Sha256HexSize = 64 // TrustedCertsDir is the directory, under the notary repo base directory, where trusted certs are stored TrustedCertsDir = "trusted_certificates" + // PrivDir is the directory, under the notary repo base directory, where private keys are stored + PrivDir = "private" + // RootKeysSubdir is the subdirectory under PrivDir where root private keys are stored + RootKeysSubdir = "root_keys" + // NonRootKeysSubdir is the subdirectory under PrivDir where non-root private keys are stored + NonRootKeysSubdir = "tuf_keys" + + // Day is a duration of one day + Day = 24 * time.Hour + Year = 365 * Day + + // NotaryRootExpiry is the duration representing the expiry time of the Root role + NotaryRootExpiry = 10 * Year + NotaryTargetsExpiry = 3 * Year + NotarySnapshotExpiry = 3 * Year + NotaryTimestampExpiry = 14 * Day ) + +// NotaryDefaultExpiries is the construct used to configure the default expiry times of +// the various role files. +var NotaryDefaultExpiries = map[string]time.Duration{ + "root": NotaryRootExpiry, + "targets": NotaryTargetsExpiry, + "snapshot": NotarySnapshotExpiry, + "timestamp": NotaryTimestampExpiry, +} diff --git a/vendor/src/github.com/docker/notary/cryptoservice/import_export.go b/vendor/src/github.com/docker/notary/cryptoservice/import_export.go index f1d454e151..c4b19944a4 100644 --- a/vendor/src/github.com/docker/notary/cryptoservice/import_export.go +++ b/vendor/src/github.com/docker/notary/cryptoservice/import_export.go @@ -11,8 +11,10 @@ import ( "path/filepath" "strings" + "github.com/docker/notary" "github.com/docker/notary/passphrase" "github.com/docker/notary/trustmanager" + "github.com/docker/notary/tuf/data" ) const zipMadeByUNIX = 3 << 8 @@ -31,14 +33,17 @@ var ( ErrNoKeysFoundForGUN = errors.New("no keys found for specified GUN") ) -// ExportRootKey exports the specified root key to an io.Writer in PEM format. +// ExportKey exports the specified private key to an io.Writer in PEM format. // The key's existing encryption is preserved. -func (cs *CryptoService) ExportRootKey(dest io.Writer, keyID string) error { +func (cs *CryptoService) ExportKey(dest io.Writer, keyID, role string) error { var ( pemBytes []byte err error ) + if role != data.CanonicalRootRole { + keyID = filepath.Join(cs.gun, keyID) + } for _, ks := range cs.keyStores { pemBytes, err = ks.ExportKey(keyID) if err != nil { @@ -59,9 +64,9 @@ func (cs *CryptoService) ExportRootKey(dest io.Writer, keyID string) error { return nil } -// ExportRootKeyReencrypt exports the specified root key to an io.Writer in +// ExportKeyReencrypt exports the specified private key to an io.Writer in // PEM format. The key is reencrypted with a new passphrase. -func (cs *CryptoService) ExportRootKeyReencrypt(dest io.Writer, keyID string, newPassphraseRetriever passphrase.Retriever) error { +func (cs *CryptoService) ExportKeyReencrypt(dest io.Writer, keyID string, newPassphraseRetriever passphrase.Retriever) error { privateKey, role, err := cs.GetPrivateKey(keyID) if err != nil { return err @@ -103,14 +108,41 @@ func (cs *CryptoService) ImportRootKey(source io.Reader) error { if err != nil { return err } + return cs.ImportRoleKey(pemBytes, data.CanonicalRootRole, nil) +} - if err = checkRootKeyIsEncrypted(pemBytes); err != nil { - return err +// ImportRoleKey imports a private key in PEM format key from a byte array +// It prompts for the key's passphrase to verify the data and to determine +// the key ID. +func (cs *CryptoService) ImportRoleKey(pemBytes []byte, role string, newPassphraseRetriever passphrase.Retriever) error { + var alias string + var err error + if role == data.CanonicalRootRole { + alias = role + if err = checkRootKeyIsEncrypted(pemBytes); err != nil { + return err + } + } else { + // Parse the private key to get the key ID so that we can import it to the correct location + privKey, err := trustmanager.ParsePEMPrivateKey(pemBytes, "") + if err != nil { + privKey, _, err = trustmanager.GetPasswdDecryptBytes(newPassphraseRetriever, pemBytes, role, string(role)) + if err != nil { + return err + } + } + // Since we're importing a non-root role, we need to pass the path as an alias + alias = filepath.Join(notary.NonRootKeysSubdir, cs.gun, privKey.ID()) + // We also need to ensure that the role is properly set in the PEM headers + pemBytes, err = trustmanager.KeyToPEM(privKey, role) + if err != nil { + return err + } } for _, ks := range cs.keyStores { // don't redeclare err, we want the value carried out of the loop - if err = ks.ImportKey(pemBytes, "root"); err == nil { + if err = ks.ImportKey(pemBytes, alias); err == nil { return nil //bail on the first keystore we import to } } diff --git a/vendor/src/github.com/docker/notary/docker-compose.yml b/vendor/src/github.com/docker/notary/docker-compose.yml index 17b5798fa2..4f8705f384 100644 --- a/vendor/src/github.com/docker/notary/docker-compose.yml +++ b/vendor/src/github.com/docker/notary/docker-compose.yml @@ -1,27 +1,34 @@ -notaryserver: +server: build: . dockerfile: server.Dockerfile links: - - notarymysql - - notarysigner - ports: - - "8080" - - "4443:4443" + - mysql + - signer + - signer:notarysigner environment: - - SERVICE_NAME=notary - command: -config=fixtures/server-config.json -notarysigner: - volumes: - - /dev/bus/usb/003/010:/dev/bus/usb/002/010 - - /var/run/pcscd/pcscd.comm:/var/run/pcscd/pcscd.comm + - SERVICE_NAME=notary_server + ports: + - "8080" + - "4443:4443" + entrypoint: /bin/bash + command: -c "./migrations/migrate.sh && notary-server -config=fixtures/server-config.json" +signer: build: . dockerfile: signer.Dockerfile links: - - notarymysql - command: -config=fixtures/signer-config.json -notarymysql: + - mysql + environment: + - SERVICE_NAME=notary_signer + entrypoint: /bin/bash + command: -c "./migrations/migrate.sh && notary-signer -config=fixtures/signer-config.json" +mysql: volumes: - - notarymysql:/var/lib/mysql - build: ./notarymysql/ + - ./notarymysql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d + - notary_data:/var/lib/mysql + image: mariadb:10.1.10 ports: - "3306:3306" + environment: + - TERM=dumb + - MYSQL_ALLOW_EMPTY_PASSWORD="true" + command: mysqld --innodb_file_per_table diff --git a/vendor/src/github.com/docker/notary/notarymysql/LICENSE b/vendor/src/github.com/docker/notary/notarymysql/LICENSE deleted file mode 100644 index c8476ac066..0000000000 --- a/vendor/src/github.com/docker/notary/notarymysql/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Sameer Naik - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/vendor/src/github.com/docker/notary/server.Dockerfile b/vendor/src/github.com/docker/notary/server.Dockerfile index a2273bc523..693064d7b5 100644 --- a/vendor/src/github.com/docker/notary/server.Dockerfile +++ b/vendor/src/github.com/docker/notary/server.Dockerfile @@ -1,4 +1,5 @@ -FROM golang:1.5.1 +FROM golang:1.5.3 +MAINTAINER David Lawrence "david.lawrence@docker.com" RUN apt-get update && apt-get install -y \ libltdl-dev \ @@ -7,13 +8,20 @@ RUN apt-get update && apt-get install -y \ EXPOSE 4443 +# Install DB migration tool +RUN go get github.com/mattes/migrate + ENV NOTARYPKG github.com/docker/notary ENV GOPATH /go/src/${NOTARYPKG}/Godeps/_workspace:$GOPATH + + +# Copy the local repo to the expected go path COPY . /go/src/github.com/docker/notary WORKDIR /go/src/${NOTARYPKG} +# Install notary-server RUN go install \ -tags pkcs11 \ -ldflags "-w -X ${NOTARYPKG}/version.GitCommit=`git rev-parse --short HEAD` -X ${NOTARYPKG}/version.NotaryVersion=`cat NOTARY_VERSION`" \ diff --git a/vendor/src/github.com/docker/notary/signer.Dockerfile b/vendor/src/github.com/docker/notary/signer.Dockerfile index 3ff8523448..837273bac5 100644 --- a/vendor/src/github.com/docker/notary/signer.Dockerfile +++ b/vendor/src/github.com/docker/notary/signer.Dockerfile @@ -1,29 +1,20 @@ -FROM dockersecurity/golang-softhsm2 -MAINTAINER Diogo Monica "diogo@docker.com" +FROM golang:1.5.3 +MAINTAINER David Lawrence "david.lawrence@docker.com" -# CHANGE-ME: Default values for SoftHSM2 PIN and SOPIN, used to initialize the first token -ENV NOTARY_SIGNER_PIN="1234" -ENV SOPIN="1234" -ENV LIBDIR="/usr/local/lib/softhsm/" -ENV NOTARY_SIGNER_DEFAULT_ALIAS="timestamp_1" -ENV NOTARY_SIGNER_TIMESTAMP_1="testpassword" - -# Install openSC and dependencies RUN apt-get update && apt-get install -y \ libltdl-dev \ - libpcsclite-dev \ - opensc \ - usbutils \ --no-install-recommends \ && rm -rf /var/lib/apt/lists/* -# Initialize the SoftHSM2 token on slod 0, using PIN and SOPIN varaibles -RUN softhsm2-util --init-token --slot 0 --label "test_token" --pin $NOTARY_SIGNER_PIN --so-pin $SOPIN +EXPOSE 4444 + +# Install DB migration tool +RUN go get github.com/mattes/migrate ENV NOTARYPKG github.com/docker/notary ENV GOPATH /go/src/${NOTARYPKG}/Godeps/_workspace:$GOPATH - -EXPOSE 4444 +ENV NOTARY_SIGNER_DEFAULT_ALIAS="timestamp_1" +ENV NOTARY_SIGNER_TIMESTAMP_1="testpassword" # Copy the local repo to the expected go path COPY . /go/src/github.com/docker/notary @@ -36,6 +27,5 @@ RUN go install \ -ldflags "-w -X ${NOTARYPKG}/version.GitCommit=`git rev-parse --short HEAD` -X ${NOTARYPKG}/version.NotaryVersion=`cat NOTARY_VERSION`" \ ${NOTARYPKG}/cmd/notary-signer - ENTRYPOINT [ "notary-signer" ] CMD [ "-config=fixtures/signer-config-local.json" ] diff --git a/vendor/src/github.com/docker/notary/trustmanager/keyfilestore.go b/vendor/src/github.com/docker/notary/trustmanager/keyfilestore.go index 0f9d821327..0a6b8ff4f8 100644 --- a/vendor/src/github.com/docker/notary/trustmanager/keyfilestore.go +++ b/vendor/src/github.com/docker/notary/trustmanager/keyfilestore.go @@ -8,16 +8,11 @@ import ( "sync" "github.com/Sirupsen/logrus" + "github.com/docker/notary" "github.com/docker/notary/passphrase" "github.com/docker/notary/tuf/data" ) -const ( - rootKeysSubdir = "root_keys" - nonRootKeysSubdir = "tuf_keys" - privDir = "private" -) - // KeyFileStore persists and manages private keys on disk type KeyFileStore struct { sync.Mutex @@ -37,7 +32,7 @@ type KeyMemoryStore struct { // NewKeyFileStore returns a new KeyFileStore creating a private directory to // hold the keys. func NewKeyFileStore(baseDir string, passphraseRetriever passphrase.Retriever) (*KeyFileStore, error) { - baseDir = filepath.Join(baseDir, privDir) + baseDir = filepath.Join(baseDir, notary.PrivDir) fileStore, err := NewPrivateSimpleFileStore(baseDir, keyExtension) if err != nil { return nil, err @@ -242,10 +237,10 @@ func listKeys(s LimitedFileStore) map[string]string { for _, f := range s.ListFiles() { // Remove the prefix of the directory from the filename var keyIDFull string - if strings.HasPrefix(f, rootKeysSubdir+"/") { - keyIDFull = strings.TrimPrefix(f, rootKeysSubdir+"/") + if strings.HasPrefix(f, notary.RootKeysSubdir+"/") { + keyIDFull = strings.TrimPrefix(f, notary.RootKeysSubdir+"/") } else { - keyIDFull = strings.TrimPrefix(f, nonRootKeysSubdir+"/") + keyIDFull = strings.TrimPrefix(f, notary.NonRootKeysSubdir+"/") } keyIDFull = strings.TrimSpace(keyIDFull) @@ -302,9 +297,9 @@ func removeKey(s LimitedFileStore, cachedKeys map[string]*cachedKey, name string // Assumes 2 subdirectories, 1 containing root keys and 1 containing tuf keys func getSubdir(alias string) string { if alias == "root" { - return rootKeysSubdir + return notary.RootKeysSubdir } - return nonRootKeysSubdir + return notary.NonRootKeysSubdir } // Given a key ID, gets the bytes and alias belonging to that key if the key @@ -327,7 +322,7 @@ func getRawKey(s LimitedFileStore, name string) ([]byte, string, error) { return keyBytes, role, nil } -// GetPasswdDecryptBytes gets the password to decript the given pem bytes. +// GetPasswdDecryptBytes gets the password to decrypt the given pem bytes. // Returns the password and private key func GetPasswdDecryptBytes(passphraseRetriever passphrase.Retriever, pemBytes []byte, name, alias string) (data.PrivateKey, string, error) { var ( diff --git a/vendor/src/github.com/docker/notary/trustmanager/x509utils.go b/vendor/src/github.com/docker/notary/trustmanager/x509utils.go index f39ca8eb22..601b15b8a9 100644 --- a/vendor/src/github.com/docker/notary/trustmanager/x509utils.go +++ b/vendor/src/github.com/docker/notary/trustmanager/x509utils.go @@ -470,12 +470,17 @@ func KeyToPEM(privKey data.PrivateKey, role string) ([]byte, error) { return nil, err } - block := &pem.Block{ - Type: bt, - Headers: map[string]string{ + headers := map[string]string{} + if role != "" { + headers = map[string]string{ "role": role, - }, - Bytes: privKey.Private(), + } + } + + block := &pem.Block{ + Type: bt, + Headers: headers, + Bytes: privKey.Private(), } return pem.EncodeToMemory(block), nil @@ -509,6 +514,19 @@ func EncryptPrivateKey(key data.PrivateKey, role, passphrase string) ([]byte, er return pem.EncodeToMemory(encryptedPEMBlock), nil } +// ReadRoleFromPEM returns the value from the role PEM header, if it exists +func ReadRoleFromPEM(pemBytes []byte) string { + pemBlock, _ := pem.Decode(pemBytes) + if pemBlock.Headers == nil { + return "" + } + role, ok := pemBlock.Headers["role"] + if !ok { + return "" + } + return role +} + // CertToKey transforms a single input certificate into its corresponding // PublicKey func CertToKey(cert *x509.Certificate) data.PublicKey { diff --git a/vendor/src/github.com/docker/notary/trustmanager/yubikey/yubikeystore.go b/vendor/src/github.com/docker/notary/trustmanager/yubikey/yubikeystore.go index a10048367a..3e292a2e9d 100644 --- a/vendor/src/github.com/docker/notary/trustmanager/yubikey/yubikeystore.go +++ b/vendor/src/github.com/docker/notary/trustmanager/yubikey/yubikeystore.go @@ -765,15 +765,15 @@ func (s *YubiKeyStore) ExportKey(keyID string) ([]byte, error) { // ImportKey imports a root key into a Yubikey func (s *YubiKeyStore) ImportKey(pemBytes []byte, keyPath string) error { logrus.Debugf("Attempting to import: %s key inside of YubiKeyStore", keyPath) + if keyPath != data.CanonicalRootRole { + return fmt.Errorf("yubikey only supports storing root keys") + } privKey, _, err := trustmanager.GetPasswdDecryptBytes( s.passRetriever, pemBytes, "", "imported root") if err != nil { logrus.Debugf("Failed to get and retrieve a key from: %s", keyPath) return err } - if keyPath != data.CanonicalRootRole { - return fmt.Errorf("yubikey only supports storing root keys") - } _, err = s.addKey(privKey.ID(), "root", privKey) return err } diff --git a/vendor/src/github.com/docker/notary/tuf/README.md b/vendor/src/github.com/docker/notary/tuf/README.md index ac8d6d1132..00a342e81e 100644 --- a/vendor/src/github.com/docker/notary/tuf/README.md +++ b/vendor/src/github.com/docker/notary/tuf/README.md @@ -29,7 +29,7 @@ however in attempting to add delegations I found I was making such significant changes that I could not maintain backwards compatibility without the code becoming overly convoluted. -Some features such as pluggable verifiers have alreayd been merged upstream to flynn/go-tuf +Some features such as pluggable verifiers have already been merged upstream to flynn/go-tuf and we are in discussion with [titanous](https://github.com/titanous) about working to merge the 2 implementations. This implementation retains the same 3 Clause BSD license present on 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 0eaa8c87e7..51aededc10 100644 --- a/vendor/src/github.com/docker/notary/tuf/client/client.go +++ b/vendor/src/github.com/docker/notary/tuf/client/client.go @@ -3,38 +3,31 @@ package client import ( "bytes" "crypto/sha256" - "encoding/hex" "encoding/json" "fmt" - "io" "path" - "strings" "github.com/Sirupsen/logrus" + "github.com/docker/notary" tuf "github.com/docker/notary/tuf" "github.com/docker/notary/tuf/data" - "github.com/docker/notary/tuf/keys" "github.com/docker/notary/tuf/signed" "github.com/docker/notary/tuf/store" "github.com/docker/notary/tuf/utils" ) -const maxSize int64 = 5 << 20 - // Client is a usability wrapper around a raw TUF repo type Client struct { local *tuf.Repo remote store.RemoteStore - keysDB *keys.KeyDB cache store.MetadataStore } -// NewClient initialized a Client with the given repo, remote source of content, key database, and cache -func NewClient(local *tuf.Repo, remote store.RemoteStore, keysDB *keys.KeyDB, cache store.MetadataStore) *Client { +// NewClient initialized a Client with the given repo, remote source of content, and cache +func NewClient(local *tuf.Repo, remote store.RemoteStore, cache store.MetadataStore) *Client { return &Client{ local: local, remote: remote, - keysDB: keysDB, cache: cache, } } @@ -131,11 +124,15 @@ func (c Client) checkRoot() error { func (c *Client) downloadRoot() error { logrus.Debug("Downloading Root...") role := data.CanonicalRootRole - size := maxSize + // We can't read an exact size for the root metadata without risking getting stuck in the TUF update cycle + // since it's possible that downloading timestamp/snapshot metadata may fail due to a signature mismatch + var size int64 = -1 var expectedSha256 []byte if c.local.Snapshot != nil { - size = c.local.Snapshot.Signed.Meta[role].Length - expectedSha256 = c.local.Snapshot.Signed.Meta[role].Hashes["sha256"] + if prevRootMeta, ok := c.local.Snapshot.Signed.Meta[role]; ok { + size = prevRootMeta.Length + expectedSha256 = prevRootMeta.Hashes["sha256"] + } } // if we're bootstrapping we may not have a cached root, an @@ -178,6 +175,7 @@ func (c *Client) downloadRoot() error { var s *data.Signed var raw []byte if download { + // use consistent download if we have the checksum. raw, s, err = c.downloadSigned(role, size, expectedSha256) if err != nil { return err @@ -201,34 +199,45 @@ func (c *Client) downloadRoot() error { func (c Client) verifyRoot(role string, s *data.Signed, minVersion int) error { // this will confirm that the root has been signed by the old root role - // as c.keysDB contains the root keys we bootstrapped with. + // with the root keys we bootstrapped with. // Still need to determine if there has been a root key update and // confirm signature with new root key logrus.Debug("verifying root with existing keys") - err := signed.Verify(s, role, minVersion, c.keysDB) + rootRole, err := c.local.GetBaseRole(role) if err != nil { + logrus.Debug("no previous root role loaded") + return err + } + // Verify using the rootRole loaded from the known root.json + if err = signed.Verify(s, rootRole, minVersion); err != nil { logrus.Debug("root did not verify with existing keys") return err } - // This will cause keyDB to get updated, overwriting any keyIDs associated - // with the roles in root.json logrus.Debug("updating known root roles and keys") root, err := data.RootFromSigned(s) if err != nil { logrus.Error(err.Error()) return err } + // replace the existing root.json with the new one (just in memory, we + // have another validation step before we fully accept the new root) err = c.local.SetRoot(root) if err != nil { logrus.Error(err.Error()) return err } - // verify again now that the old keys have been replaced with the new keys. + // Verify the new root again having loaded the rootRole out of this new + // file (verifies self-referential integrity) // TODO(endophage): be more intelligent and only re-verify if we detect // there has been a change in root keys logrus.Debug("verifying root with updated keys") - err = signed.Verify(s, role, minVersion, c.keysDB) + rootRole, err = c.local.GetBaseRole(role) + if err != nil { + logrus.Debug("root role with new keys not loaded") + return err + } + err = signed.Verify(s, rootRole, minVersion) if err != nil { logrus.Debug("root did not verify with new keys") return err @@ -248,11 +257,11 @@ func (c *Client) downloadTimestamp() error { // we're interacting with the repo. This will result in the // version being 0 var ( - saveToCache bool - old *data.Signed - version = 0 + old *data.Signed + ts *data.SignedTimestamp + version = 0 ) - cachedTS, err := c.cache.GetMeta(role, maxSize) + cachedTS, err := c.cache.GetMeta(role, notary.MaxTimestampSize) if err == nil { cached := &data.Signed{} err := json.Unmarshal(cachedTS, cached) @@ -266,49 +275,56 @@ func (c *Client) downloadTimestamp() error { } // unlike root, targets and snapshot, always try and download timestamps // from remote, only using the cache one if we couldn't reach remote. - raw, s, err := c.downloadSigned(role, maxSize, nil) - if err != nil || len(raw) == 0 { - if old == nil { - if err == nil { - // couldn't retrieve data from server and don't have valid - // data in cache. - return store.ErrMetaNotFound{Resource: data.CanonicalTimestampRole} - } - return err + raw, s, err := c.downloadSigned(role, notary.MaxTimestampSize, nil) + if err == nil { + ts, err = c.verifyTimestamp(s, version) + if err == nil { + logrus.Debug("successfully verified downloaded timestamp") + c.cache.SetMeta(role, raw) + c.local.SetTimestamp(ts) + return nil } - logrus.Debug(err.Error()) - logrus.Warn("Error while downloading remote metadata, using cached timestamp - this might not be the latest version available remotely") - s = old - } else { - saveToCache = true } - err = signed.Verify(s, role, version, c.keysDB) - if err != nil { - return err - } - logrus.Debug("successfully verified timestamp") - if saveToCache { - c.cache.SetMeta(role, raw) - } - ts, err := data.TimestampFromSigned(s) + if old == nil { + // couldn't retrieve valid data from server and don't have unmarshallable data in cache. + logrus.Debug("no cached timestamp available") + return err + } + logrus.Debug(err.Error()) + logrus.Warn("Error while downloading remote metadata, using cached timestamp - this might not be the latest version available remotely") + ts, err = c.verifyTimestamp(old, version) if err != nil { return err } + logrus.Debug("successfully verified cached timestamp") c.local.SetTimestamp(ts) return nil } +// verifies that a timestamp is valid, and returned the SignedTimestamp object to add to the tuf repo +func (c *Client) verifyTimestamp(s *data.Signed, minVersion int) (*data.SignedTimestamp, error) { + timestampRole, err := c.local.GetBaseRole(data.CanonicalTimestampRole) + if err != nil { + logrus.Debug("no timestamp role loaded") + return nil, err + } + if err := signed.Verify(s, timestampRole, minVersion); err != nil { + return nil, err + } + return data.TimestampFromSigned(s) +} + // downloadSnapshot is responsible for downloading the snapshot.json func (c *Client) downloadSnapshot() error { logrus.Debug("Downloading Snapshot...") role := data.CanonicalSnapshotRole if c.local.Timestamp == nil { - return ErrMissingMeta{role: "snapshot"} + return tuf.ErrNotLoaded{Role: data.CanonicalTimestampRole} } size := c.local.Timestamp.Signed.Meta[role].Length expectedSha256, ok := c.local.Timestamp.Signed.Meta[role].Hashes["sha256"] if !ok { - return ErrMissingMeta{role: "snapshot"} + return data.ErrMissingMeta{Role: "snapshot"} } var download bool @@ -350,7 +366,12 @@ func (c *Client) downloadSnapshot() error { s = old } - err = signed.Verify(s, role, version, c.keysDB) + snapshotRole, err := c.local.GetBaseRole(role) + if err != nil { + logrus.Debug("no snapshot role loaded") + return err + } + err = signed.Verify(s, snapshotRole, version) if err != nil { return err } @@ -382,18 +403,14 @@ func (c *Client) downloadTargets(role string) error { return err } if c.local.Snapshot == nil { - return ErrMissingMeta{role: role} + return tuf.ErrNotLoaded{Role: data.CanonicalSnapshotRole} } snap := c.local.Snapshot.Signed root := c.local.Root.Signed - r := c.keysDB.GetRole(role) - if r == nil { - return fmt.Errorf("Invalid role: %s", role) - } - keyIDs := r.KeyIDs - s, err := c.getTargetsFile(role, keyIDs, snap.Meta, root.ConsistentSnapshot, r.Threshold) + + s, err := c.getTargetsFile(role, snap.Meta, root.ConsistentSnapshot) if err != nil { - if _, ok := err.(ErrMissingMeta); ok && role != data.CanonicalTargetsRole { + if _, ok := err.(data.ErrMissingMeta); ok && role != data.CanonicalTargetsRole { // if the role meta hasn't been published, // that's ok, continue continue @@ -401,7 +418,7 @@ func (c *Client) downloadTargets(role string) error { logrus.Error("Error getting targets file:", err) return err } - t, err := data.TargetsFromSigned(s) + t, err := data.TargetsFromSigned(s, role) if err != nil { return err } @@ -412,14 +429,19 @@ func (c *Client) downloadTargets(role string) error { // push delegated roles contained in the targets file onto the stack for _, r := range t.Signed.Delegations.Roles { - stack.Push(r.Name) + if path.Dir(r.Name) == role { + // only load children that are direct 1st generation descendants + // of the role we've just downloaded + stack.Push(r.Name) + } } } return nil } func (c *Client) downloadSigned(role string, size int64, expectedSha256 []byte) ([]byte, *data.Signed, error) { - raw, err := c.remote.GetMeta(role, size) + rolePath := utils.ConsistentName(role, expectedSha256) + raw, err := c.remote.GetMeta(rolePath, size) if err != nil { return nil, nil, err } @@ -437,15 +459,15 @@ func (c *Client) downloadSigned(role string, size int64, expectedSha256 []byte) return raw, s, nil } -func (c Client) getTargetsFile(role string, keyIDs []string, snapshotMeta data.Files, consistent bool, threshold int) (*data.Signed, error) { +func (c Client) getTargetsFile(role string, snapshotMeta data.Files, consistent bool) (*data.Signed, error) { // require role exists in snapshots roleMeta, ok := snapshotMeta[role] if !ok { - return nil, ErrMissingMeta{role: role} + return nil, data.ErrMissingMeta{Role: role} } expectedSha256, ok := snapshotMeta[role].Hashes["sha256"] if !ok { - return nil, ErrMissingMeta{role: role} + return nil, data.ErrMissingMeta{Role: role} } // try to get meta file from content addressed cache @@ -464,7 +486,7 @@ func (c Client) getTargetsFile(role string, keyIDs []string, snapshotMeta data.F } err := json.Unmarshal(raw, old) if err == nil { - targ, err := data.TargetsFromSigned(old) + targ, err := data.TargetsFromSigned(old, role) if err == nil { version = targ.Signed.Version } else { @@ -478,11 +500,7 @@ func (c Client) getTargetsFile(role string, keyIDs []string, snapshotMeta data.F size := snapshotMeta[role].Length var s *data.Signed if download { - rolePath, err := c.RoleTargetsPath(role, hex.EncodeToString(expectedSha256), consistent) - if err != nil { - return nil, err - } - raw, s, err = c.downloadSigned(rolePath, size, expectedSha256) + raw, s, err = c.downloadSigned(role, size, expectedSha256) if err != nil { return nil, err } @@ -490,9 +508,22 @@ func (c Client) getTargetsFile(role string, keyIDs []string, snapshotMeta data.F logrus.Debug("using cached ", role) s = old } - - err = signed.Verify(s, role, version, c.keysDB) - if err != nil { + var targetOrDelgRole data.BaseRole + if data.IsDelegation(role) { + delgRole, err := c.local.GetDelegationRole(role) + if err != nil { + logrus.Debugf("no %s delegation role loaded", role) + return nil, err + } + targetOrDelgRole = delgRole.BaseRole + } else { + targetOrDelgRole, err = c.local.GetBaseRole(role) + if err != nil { + logrus.Debugf("no %s role loaded", role) + return nil, err + } + } + if err = signed.Verify(s, targetOrDelgRole, version); err != nil { return nil, err } logrus.Debugf("successfully verified %s", role) @@ -505,73 +536,3 @@ func (c Client) getTargetsFile(role string, keyIDs []string, snapshotMeta data.F } return s, nil } - -// RoleTargetsPath generates the appropriate HTTP URL for the targets file, -// based on whether the repo is marked as consistent. -func (c Client) RoleTargetsPath(role string, hashSha256 string, consistent bool) (string, error) { - if consistent { - // Use path instead of filepath since we refer to the TUF role directly instead of its target files - dir := path.Dir(role) - if strings.Contains(role, "/") { - lastSlashIdx := strings.LastIndex(role, "/") - role = role[lastSlashIdx+1:] - } - role = path.Join( - dir, - fmt.Sprintf("%s.%s.json", hashSha256, role), - ) - } - return role, nil -} - -// TargetMeta ensures the repo is up to date. It assumes downloadTargets -// has already downloaded all delegated roles -func (c Client) TargetMeta(role, path string, excludeRoles ...string) (*data.FileMeta, string) { - excl := make(map[string]bool) - for _, r := range excludeRoles { - excl[r] = true - } - - pathDigest := sha256.Sum256([]byte(path)) - pathHex := hex.EncodeToString(pathDigest[:]) - - // FIFO list of targets delegations to inspect for target - roles := []string{role} - var ( - meta *data.FileMeta - curr string - ) - for len(roles) > 0 { - // have to do these lines here because of order of execution in for statement - curr = roles[0] - roles = roles[1:] - - meta = c.local.TargetMeta(curr, path) - if meta != nil { - // we found the target! - return meta, curr - } - delegations := c.local.TargetDelegations(curr, path, pathHex) - for _, d := range delegations { - if !excl[d.Name] { - roles = append(roles, d.Name) - } - } - } - return meta, "" -} - -// DownloadTarget downloads the target to dst from the remote -func (c Client) DownloadTarget(dst io.Writer, path string, meta *data.FileMeta) error { - reader, err := c.remote.GetTarget(path) - if err != nil { - return err - } - defer reader.Close() - r := io.TeeReader( - io.LimitReader(reader, meta.Length), - dst, - ) - err = utils.ValidateTarget(r, meta) - return err -} diff --git a/vendor/src/github.com/docker/notary/tuf/client/errors.go b/vendor/src/github.com/docker/notary/tuf/client/errors.go index 037b3df00b..ad0555127e 100644 --- a/vendor/src/github.com/docker/notary/tuf/client/errors.go +++ b/vendor/src/github.com/docker/notary/tuf/client/errors.go @@ -13,15 +13,6 @@ func (e ErrChecksumMismatch) Error() string { return fmt.Sprintf("tuf: checksum for %s did not match", e.role) } -// ErrMissingMeta - couldn't find the FileMeta object for a role or target -type ErrMissingMeta struct { - role string -} - -func (e ErrMissingMeta) Error() string { - return fmt.Sprintf("tuf: sha256 checksum required for %s", e.role) -} - // ErrCorruptedCache - local data is incorrect type ErrCorruptedCache struct { file string diff --git a/vendor/src/github.com/docker/notary/tuf/data/errors.go b/vendor/src/github.com/docker/notary/tuf/data/errors.go new file mode 100644 index 0000000000..7ff5814c8e --- /dev/null +++ b/vendor/src/github.com/docker/notary/tuf/data/errors.go @@ -0,0 +1,22 @@ +package data + +import "fmt" + +// ErrInvalidMetadata is the error to be returned when metadata is invalid +type ErrInvalidMetadata struct { + role string + msg string +} + +func (e ErrInvalidMetadata) Error() string { + return fmt.Sprintf("%s type metadata invalid: %s", e.role, e.msg) +} + +// ErrMissingMeta - couldn't find the FileMeta object for a role or target +type ErrMissingMeta struct { + Role string +} + +func (e ErrMissingMeta) Error() string { + return fmt.Sprintf("tuf: sha256 checksum required for %s", e.Role) +} diff --git a/vendor/src/github.com/docker/notary/tuf/data/keys.go b/vendor/src/github.com/docker/notary/tuf/data/keys.go index 9f94d5552f..25df598c16 100644 --- a/vendor/src/github.com/docker/notary/tuf/data/keys.go +++ b/vendor/src/github.com/docker/notary/tuf/data/keys.go @@ -46,7 +46,7 @@ type Keys map[string]PublicKey // UnmarshalJSON implements the json.Unmarshaller interface func (ks *Keys) UnmarshalJSON(data []byte) error { - parsed := make(map[string]tufKey) + parsed := make(map[string]TUFKey) err := json.Unmarshal(data, &parsed) if err != nil { return err @@ -64,7 +64,7 @@ type KeyList []PublicKey // UnmarshalJSON implements the json.Unmarshaller interface func (ks *KeyList) UnmarshalJSON(data []byte) error { - parsed := make([]tufKey, 0, 1) + parsed := make([]TUFKey, 0, 1) err := json.Unmarshal(data, &parsed) if err != nil { return err @@ -86,64 +86,64 @@ func (ks KeyList) IDs() []string { return keyIDs } -func typedPublicKey(tk tufKey) PublicKey { +func typedPublicKey(tk TUFKey) PublicKey { switch tk.Algorithm() { case ECDSAKey: - return &ECDSAPublicKey{tufKey: tk} + return &ECDSAPublicKey{TUFKey: tk} case ECDSAx509Key: - return &ECDSAx509PublicKey{tufKey: tk} + return &ECDSAx509PublicKey{TUFKey: tk} case RSAKey: - return &RSAPublicKey{tufKey: tk} + return &RSAPublicKey{TUFKey: tk} case RSAx509Key: - return &RSAx509PublicKey{tufKey: tk} + return &RSAx509PublicKey{TUFKey: tk} case ED25519Key: - return &ED25519PublicKey{tufKey: tk} + return &ED25519PublicKey{TUFKey: tk} } - return &UnknownPublicKey{tufKey: tk} + return &UnknownPublicKey{TUFKey: tk} } -func typedPrivateKey(tk tufKey) (PrivateKey, error) { +func typedPrivateKey(tk TUFKey) (PrivateKey, error) { private := tk.Value.Private tk.Value.Private = nil switch tk.Algorithm() { case ECDSAKey: return NewECDSAPrivateKey( &ECDSAPublicKey{ - tufKey: tk, + TUFKey: tk, }, private, ) case ECDSAx509Key: return NewECDSAPrivateKey( &ECDSAx509PublicKey{ - tufKey: tk, + TUFKey: tk, }, private, ) case RSAKey: return NewRSAPrivateKey( &RSAPublicKey{ - tufKey: tk, + TUFKey: tk, }, private, ) case RSAx509Key: return NewRSAPrivateKey( &RSAx509PublicKey{ - tufKey: tk, + TUFKey: tk, }, private, ) case ED25519Key: return NewED25519PrivateKey( ED25519PublicKey{ - tufKey: tk, + TUFKey: tk, }, private, ) } return &UnknownPrivateKey{ - tufKey: tk, + TUFKey: tk, privateKey: privateKey{private: private}, }, nil } @@ -151,7 +151,7 @@ func typedPrivateKey(tk tufKey) (PrivateKey, error) { // NewPublicKey creates a new, correctly typed PublicKey, using the // UnknownPublicKey catchall for unsupported ciphers func NewPublicKey(alg string, public []byte) PublicKey { - tk := tufKey{ + tk := TUFKey{ Type: alg, Value: KeyPair{ Public: public, @@ -163,7 +163,7 @@ func NewPublicKey(alg string, public []byte) PublicKey { // NewPrivateKey creates a new, correctly typed PrivateKey, using the // UnknownPrivateKey catchall for unsupported ciphers func NewPrivateKey(pubKey PublicKey, private []byte) (PrivateKey, error) { - tk := tufKey{ + tk := TUFKey{ Type: pubKey.Algorithm(), Value: KeyPair{ Public: pubKey.Public(), @@ -175,7 +175,7 @@ func NewPrivateKey(pubKey PublicKey, private []byte) (PrivateKey, error) { // UnmarshalPublicKey is used to parse individual public keys in JSON func UnmarshalPublicKey(data []byte) (PublicKey, error) { - var parsed tufKey + var parsed TUFKey err := json.Unmarshal(data, &parsed) if err != nil { return nil, err @@ -185,7 +185,7 @@ func UnmarshalPublicKey(data []byte) (PublicKey, error) { // UnmarshalPrivateKey is used to parse individual private keys in JSON func UnmarshalPrivateKey(data []byte) (PrivateKey, error) { - var parsed tufKey + var parsed TUFKey err := json.Unmarshal(data, &parsed) if err != nil { return nil, err @@ -193,26 +193,26 @@ func UnmarshalPrivateKey(data []byte) (PrivateKey, error) { return typedPrivateKey(parsed) } -// tufKey is the structure used for both public and private keys in TUF. +// TUFKey is the structure used for both public and private keys in TUF. // Normally it would make sense to use a different structures for public and // private keys, but that would change the key ID algorithm (since the canonical // JSON would be different). This structure should normally be accessed through // the PublicKey or PrivateKey interfaces. -type tufKey struct { +type TUFKey struct { id string Type string `json:"keytype"` Value KeyPair `json:"keyval"` } // Algorithm returns the algorithm of the key -func (k tufKey) Algorithm() string { +func (k TUFKey) Algorithm() string { return k.Type } // ID efficiently generates if necessary, and caches the ID of the key -func (k *tufKey) ID() string { +func (k *TUFKey) ID() string { if k.id == "" { - pubK := tufKey{ + pubK := TUFKey{ Type: k.Algorithm(), Value: KeyPair{ Public: k.Public(), @@ -230,7 +230,7 @@ func (k *tufKey) ID() string { } // Public returns the public bytes -func (k tufKey) Public() []byte { +func (k TUFKey) Public() []byte { return k.Value.Public } @@ -239,42 +239,42 @@ func (k tufKey) Public() []byte { // ECDSAPublicKey represents an ECDSA key using a raw serialization // of the public key type ECDSAPublicKey struct { - tufKey + TUFKey } // ECDSAx509PublicKey represents an ECDSA key using an x509 cert // as the serialized format of the public key type ECDSAx509PublicKey struct { - tufKey + TUFKey } // RSAPublicKey represents an RSA key using a raw serialization // of the public key type RSAPublicKey struct { - tufKey + TUFKey } // RSAx509PublicKey represents an RSA key using an x509 cert // as the serialized format of the public key type RSAx509PublicKey struct { - tufKey + TUFKey } // ED25519PublicKey represents an ED25519 key using a raw serialization // of the public key type ED25519PublicKey struct { - tufKey + TUFKey } // UnknownPublicKey is a catchall for key types that are not supported type UnknownPublicKey struct { - tufKey + TUFKey } // NewECDSAPublicKey initializes a new public key with the ECDSAKey type func NewECDSAPublicKey(public []byte) *ECDSAPublicKey { return &ECDSAPublicKey{ - tufKey: tufKey{ + TUFKey: TUFKey{ Type: ECDSAKey, Value: KeyPair{ Public: public, @@ -287,7 +287,7 @@ func NewECDSAPublicKey(public []byte) *ECDSAPublicKey { // NewECDSAx509PublicKey initializes a new public key with the ECDSAx509Key type func NewECDSAx509PublicKey(public []byte) *ECDSAx509PublicKey { return &ECDSAx509PublicKey{ - tufKey: tufKey{ + TUFKey: TUFKey{ Type: ECDSAx509Key, Value: KeyPair{ Public: public, @@ -300,7 +300,7 @@ func NewECDSAx509PublicKey(public []byte) *ECDSAx509PublicKey { // NewRSAPublicKey initializes a new public key with the RSA type func NewRSAPublicKey(public []byte) *RSAPublicKey { return &RSAPublicKey{ - tufKey: tufKey{ + TUFKey: TUFKey{ Type: RSAKey, Value: KeyPair{ Public: public, @@ -313,7 +313,7 @@ func NewRSAPublicKey(public []byte) *RSAPublicKey { // NewRSAx509PublicKey initializes a new public key with the RSAx509Key type func NewRSAx509PublicKey(public []byte) *RSAx509PublicKey { return &RSAx509PublicKey{ - tufKey: tufKey{ + TUFKey: TUFKey{ Type: RSAx509Key, Value: KeyPair{ Public: public, @@ -326,7 +326,7 @@ func NewRSAx509PublicKey(public []byte) *RSAx509PublicKey { // NewED25519PublicKey initializes a new public key with the ED25519Key type func NewED25519PublicKey(public []byte) *ED25519PublicKey { return &ED25519PublicKey{ - tufKey: tufKey{ + TUFKey: TUFKey{ Type: ED25519Key, Value: KeyPair{ Public: public, @@ -367,7 +367,7 @@ type ED25519PrivateKey struct { // UnknownPrivateKey is a catchall for unsupported key types type UnknownPrivateKey struct { - tufKey + TUFKey privateKey } @@ -515,10 +515,10 @@ func (k UnknownPrivateKey) SignatureAlgorithm() SigAlgorithm { return "" } -// PublicKeyFromPrivate returns a new tufKey based on a private key, with +// PublicKeyFromPrivate returns a new TUFKey based on a private key, with // the private key bytes guaranteed to be nil. func PublicKeyFromPrivate(pk PrivateKey) PublicKey { - return typedPublicKey(tufKey{ + return typedPublicKey(TUFKey{ Type: pk.Algorithm(), Value: KeyPair{ Public: pk.Public(), diff --git a/vendor/src/github.com/docker/notary/tuf/data/roles.go b/vendor/src/github.com/docker/notary/tuf/data/roles.go index a505c92304..b1a2988bd0 100644 --- a/vendor/src/github.com/docker/notary/tuf/data/roles.go +++ b/vendor/src/github.com/docker/notary/tuf/data/roles.go @@ -2,10 +2,11 @@ package data import ( "fmt" - "github.com/Sirupsen/logrus" "path" "regexp" "strings" + + "github.com/Sirupsen/logrus" ) // Canonical base role names @@ -85,32 +86,139 @@ func IsDelegation(role string) bool { isClean } +// BaseRole is an internal representation of a root/targets/snapshot/timestamp role, with its public keys included +type BaseRole struct { + Keys map[string]PublicKey + Name string + Threshold int +} + +// NewBaseRole creates a new BaseRole object with the provided parameters +func NewBaseRole(name string, threshold int, keys ...PublicKey) BaseRole { + r := BaseRole{ + Name: name, + Threshold: threshold, + Keys: make(map[string]PublicKey), + } + for _, k := range keys { + r.Keys[k.ID()] = k + } + return r +} + +// ListKeys retrieves the public keys valid for this role +func (b BaseRole) ListKeys() KeyList { + return listKeys(b.Keys) +} + +// ListKeyIDs retrieves the list of key IDs valid for this role +func (b BaseRole) ListKeyIDs() []string { + return listKeyIDs(b.Keys) +} + +// DelegationRole is an internal representation of a delegation role, with its public keys included +type DelegationRole struct { + BaseRole + Paths []string +} + +func listKeys(keyMap map[string]PublicKey) KeyList { + keys := KeyList{} + for _, key := range keyMap { + keys = append(keys, key) + } + return keys +} + +func listKeyIDs(keyMap map[string]PublicKey) []string { + keyIDs := []string{} + for id := range keyMap { + keyIDs = append(keyIDs, id) + } + return keyIDs +} + +// Restrict restricts the paths and path hash prefixes for the passed in delegation role, +// returning a copy of the role with validated paths as if it was a direct child +func (d DelegationRole) Restrict(child DelegationRole) (DelegationRole, error) { + if !d.IsParentOf(child) { + return DelegationRole{}, fmt.Errorf("%s is not a parent of %s", d.Name, child.Name) + } + return DelegationRole{ + BaseRole: BaseRole{ + Keys: child.Keys, + Name: child.Name, + Threshold: child.Threshold, + }, + Paths: RestrictDelegationPathPrefixes(d.Paths, child.Paths), + }, nil +} + +// IsParentOf returns whether the passed in delegation role is the direct child of this role, +// determined by delegation name. +// Ex: targets/a is a direct parent of targets/a/b, but targets/a is not a direct parent of targets/a/b/c +func (d DelegationRole) IsParentOf(child DelegationRole) bool { + return path.Dir(child.Name) == d.Name +} + +// CheckPaths checks if a given path is valid for the role +func (d DelegationRole) CheckPaths(path string) bool { + return checkPaths(path, d.Paths) +} + +func checkPaths(path string, permitted []string) bool { + for _, p := range permitted { + if strings.HasPrefix(path, p) { + return true + } + } + return false +} + +// RestrictDelegationPathPrefixes returns the list of valid delegationPaths that are prefixed by parentPaths +func RestrictDelegationPathPrefixes(parentPaths, delegationPaths []string) []string { + validPaths := []string{} + if len(delegationPaths) == 0 { + return validPaths + } + + // Validate each individual delegation path + for _, delgPath := range delegationPaths { + isPrefixed := false + for _, parentPath := range parentPaths { + if strings.HasPrefix(delgPath, parentPath) { + isPrefixed = true + break + } + } + // If the delegation path did not match prefix against any parent path, it is not valid + if isPrefixed { + validPaths = append(validPaths, delgPath) + } + } + return validPaths +} + // RootRole is a cut down role as it appears in the root.json +// Eventually should only be used for immediately before and after serialization/deserialization type RootRole struct { KeyIDs []string `json:"keyids"` Threshold int `json:"threshold"` } // Role is a more verbose role as they appear in targets delegations +// Eventually should only be used for immediately before and after serialization/deserialization type Role struct { RootRole - Name string `json:"name"` - Paths []string `json:"paths,omitempty"` - PathHashPrefixes []string `json:"path_hash_prefixes,omitempty"` - Email string `json:"email,omitempty"` + Name string `json:"name"` + Paths []string `json:"paths,omitempty"` } // NewRole creates a new Role object from the given parameters -func NewRole(name string, threshold int, keyIDs, paths, pathHashPrefixes []string) (*Role, error) { - if len(paths) > 0 && len(pathHashPrefixes) > 0 { - return nil, ErrInvalidRole{ - Role: name, - Reason: "roles may not have both Paths and PathHashPrefixes", - } - } +func NewRole(name string, threshold int, keyIDs, paths []string) (*Role, error) { if IsDelegation(name) { - if len(paths) == 0 && len(pathHashPrefixes) == 0 { - logrus.Debugf("role %s with no Paths and no PathHashPrefixes will never be able to publish content until one or more are added", name) + if len(paths) == 0 { + logrus.Debugf("role %s with no Paths will never be able to publish content until one or more are added", name) } } if threshold < 1 { @@ -124,52 +232,15 @@ func NewRole(name string, threshold int, keyIDs, paths, pathHashPrefixes []strin KeyIDs: keyIDs, Threshold: threshold, }, - Name: name, - Paths: paths, - PathHashPrefixes: pathHashPrefixes, + Name: name, + Paths: paths, }, nil } -// IsValid checks if the role has defined both paths and path hash prefixes, -// having both is invalid -func (r Role) IsValid() bool { - return !(len(r.Paths) > 0 && len(r.PathHashPrefixes) > 0) -} - -// ValidKey checks if the given id is a recognized signing key for the role -func (r Role) ValidKey(id string) bool { - for _, key := range r.KeyIDs { - if key == id { - return true - } - } - return false -} - // CheckPaths checks if a given path is valid for the role func (r Role) CheckPaths(path string) bool { - for _, p := range r.Paths { - if strings.HasPrefix(path, p) { - return true - } - } - return false -} - -// CheckPrefixes checks if a given hash matches the prefixes for the role -func (r Role) CheckPrefixes(hash string) bool { - for _, p := range r.PathHashPrefixes { - if strings.HasPrefix(hash, p) { - return true - } - } - return false -} - -// IsDelegation checks if the role is a delegation or a root role -func (r Role) IsDelegation() bool { - return IsDelegation(r.Name) + return checkPaths(path, r.Paths) } // AddKeys merges the ids into the current list of role key ids @@ -182,25 +253,10 @@ func (r *Role) AddPaths(paths []string) error { if len(paths) == 0 { return nil } - if len(r.PathHashPrefixes) > 0 { - return ErrInvalidRole{Role: r.Name, Reason: "attempted to add paths to role that already has hash prefixes"} - } r.Paths = mergeStrSlices(r.Paths, paths) return nil } -// AddPathHashPrefixes merges the prefixes into the list of role path hash prefixes -func (r *Role) AddPathHashPrefixes(prefixes []string) error { - if len(prefixes) == 0 { - return nil - } - if len(r.Paths) > 0 { - return ErrInvalidRole{Role: r.Name, Reason: "attempted to add hash prefixes to role that already has paths"} - } - r.PathHashPrefixes = mergeStrSlices(r.PathHashPrefixes, prefixes) - return nil -} - // RemoveKeys removes the ids from the current list of key ids func (r *Role) RemoveKeys(ids []string) { r.KeyIDs = subtractStrSlices(r.KeyIDs, ids) @@ -211,11 +267,6 @@ func (r *Role) RemovePaths(paths []string) { r.Paths = subtractStrSlices(r.Paths, paths) } -// RemovePathHashPrefixes removes the prefixes from the current list of path hash prefixes -func (r *Role) RemovePathHashPrefixes(prefixes []string) { - r.PathHashPrefixes = subtractStrSlices(r.PathHashPrefixes, prefixes) -} - func mergeStrSlices(orig, new []string) []string { have := make(map[string]bool) for _, e := range orig { 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 bd479206fd..3a9a1b1dec 100644 --- a/vendor/src/github.com/docker/notary/tuf/data/root.go +++ b/vendor/src/github.com/docker/notary/tuf/data/root.go @@ -1,6 +1,7 @@ package data import ( + "fmt" "time" "github.com/docker/go/canonical/json" @@ -23,14 +24,57 @@ type Root struct { ConsistentSnapshot bool `json:"consistent_snapshot"` } +// isValidRootStructure returns an error, or nil, depending on whether the content of the struct +// is valid for root metadata. This does not check signatures or expiry, just that +// the metadata content is valid. +func isValidRootStructure(r Root) error { + expectedType := TUFTypes[CanonicalRootRole] + if r.Type != expectedType { + return ErrInvalidMetadata{ + role: CanonicalRootRole, msg: fmt.Sprintf("expected type %s, not %s", expectedType, r.Type)} + } + + // all the base roles MUST appear in the root.json - other roles are allowed, + // but other than the mirror role (not currently supported) are out of spec + for _, roleName := range BaseRoles { + roleObj, ok := r.Roles[roleName] + if !ok || roleObj == nil { + return ErrInvalidMetadata{ + role: CanonicalRootRole, msg: fmt.Sprintf("missing %s role specification", roleName)} + } + if err := isValidRootRoleStructure(CanonicalRootRole, roleName, *roleObj, r.Keys); err != nil { + return err + } + } + return nil +} + +func isValidRootRoleStructure(metaContainingRole, rootRoleName string, r RootRole, validKeys Keys) error { + if r.Threshold < 1 { + return ErrInvalidMetadata{ + role: metaContainingRole, + msg: fmt.Sprintf("invalid threshold specified for %s: %v ", rootRoleName, r.Threshold), + } + } + for _, keyID := range r.KeyIDs { + if _, ok := validKeys[keyID]; !ok { + return ErrInvalidMetadata{ + role: metaContainingRole, + msg: fmt.Sprintf("key ID %s specified in %s without corresponding key", keyID, rootRoleName), + } + } + } + return nil +} + // NewRoot initializes a new SignedRoot with a set of keys, roles, and the consistent flag func NewRoot(keys map[string]PublicKey, roles map[string]*RootRole, consistent bool) (*SignedRoot, error) { signedRoot := &SignedRoot{ Signatures: make([]Signature, 0), Signed: Root{ - Type: TUFTypes["root"], + Type: TUFTypes[CanonicalRootRole], Version: 0, - Expires: DefaultExpires("root"), + Expires: DefaultExpires(CanonicalRootRole), Keys: keys, Roles: roles, ConsistentSnapshot: consistent, @@ -41,6 +85,34 @@ func NewRoot(keys map[string]PublicKey, roles map[string]*RootRole, consistent b return signedRoot, nil } +// BuildBaseRole returns a copy of a BaseRole using the information in this SignedRoot for the specified role name. +// Will error for invalid role name or key metadata within this SignedRoot +func (r SignedRoot) BuildBaseRole(roleName string) (BaseRole, error) { + roleData, ok := r.Signed.Roles[roleName] + if !ok { + return BaseRole{}, ErrInvalidRole{Role: roleName, Reason: "role not found in root file"} + } + // Get all public keys for the base role from TUF metadata + keyIDs := roleData.KeyIDs + pubKeys := make(map[string]PublicKey) + for _, keyID := range keyIDs { + pubKey, ok := r.Signed.Keys[keyID] + if !ok { + return BaseRole{}, ErrInvalidRole{ + Role: roleName, + Reason: fmt.Sprintf("key with ID %s was not found in root metadata", keyID), + } + } + pubKeys[keyID] = pubKey + } + + return BaseRole{ + Name: roleName, + Keys: pubKeys, + Threshold: roleData.Threshold, + }, nil +} + // ToSigned partially serializes a SignedRoot for further signing func (r SignedRoot) ToSigned() (*Signed, error) { s, err := defaultSerializer.MarshalCanonical(r.Signed) @@ -70,11 +142,14 @@ func (r SignedRoot) MarshalJSON() ([]byte, error) { return defaultSerializer.Marshal(signed) } -// RootFromSigned fully unpacks a Signed object into a SignedRoot +// RootFromSigned fully unpacks a Signed object into a SignedRoot and ensures +// that it is a valid SignedRoot func RootFromSigned(s *Signed) (*SignedRoot, error) { r := Root{} - err := json.Unmarshal(s.Signed, &r) - if err != nil { + if err := defaultSerializer.Unmarshal(s.Signed, &r); err != nil { + return nil, err + } + if err := isValidRootStructure(r); err != nil { return nil, err } sigs := make([]Signature, len(s.Signatures)) diff --git a/vendor/src/github.com/docker/notary/tuf/data/snapshot.go b/vendor/src/github.com/docker/notary/tuf/data/snapshot.go index f13951ca83..9637dfff86 100644 --- a/vendor/src/github.com/docker/notary/tuf/data/snapshot.go +++ b/vendor/src/github.com/docker/notary/tuf/data/snapshot.go @@ -2,6 +2,8 @@ package data import ( "bytes" + "crypto/sha256" + "fmt" "time" "github.com/Sirupsen/logrus" @@ -23,6 +25,30 @@ type Snapshot struct { Meta Files `json:"meta"` } +// isValidSnapshotStructure returns an error, or nil, depending on whether the content of the +// struct is valid for snapshot metadata. This does not check signatures or expiry, just that +// the metadata content is valid. +func isValidSnapshotStructure(s Snapshot) error { + expectedType := TUFTypes[CanonicalSnapshotRole] + if s.Type != expectedType { + return ErrInvalidMetadata{ + role: CanonicalSnapshotRole, msg: fmt.Sprintf("expected type %s, not %s", expectedType, s.Type)} + } + + for _, role := range []string{CanonicalRootRole, CanonicalTargetsRole} { + // Meta is a map of FileMeta, so if the role isn't in the map it returns + // an empty FileMeta, which has an empty map, and you can check on keys + // from an empty map. + if checksum, ok := s.Meta[role].Hashes["sha256"]; !ok || len(checksum) != sha256.Size { + return ErrInvalidMetadata{ + role: CanonicalSnapshotRole, + msg: fmt.Sprintf("missing or invalid %s sha256 checksum information", role), + } + } + } + return nil +} + // NewSnapshot initilizes a SignedSnapshot with a given top level root // and targets objects func NewSnapshot(root *Signed, targets *Signed) (*SignedSnapshot, error) { @@ -64,8 +90,8 @@ func (sp *SignedSnapshot) hashForRole(role string) []byte { } // ToSigned partially serializes a SignedSnapshot for further signing -func (sp SignedSnapshot) ToSigned() (*Signed, error) { - s, err := json.MarshalCanonical(sp.Signed) +func (sp *SignedSnapshot) ToSigned() (*Signed, error) { + s, err := defaultSerializer.MarshalCanonical(sp.Signed) if err != nil { return nil, err } @@ -88,6 +114,15 @@ func (sp *SignedSnapshot) AddMeta(role string, meta FileMeta) { sp.Dirty = true } +// GetMeta gets the metadata for a particular role, returning an error if it's +// not found +func (sp *SignedSnapshot) GetMeta(role string) (*FileMeta, error) { + if meta, ok := sp.Signed.Meta[role]; ok { + return &meta, nil + } + return nil, ErrMissingMeta{Role: role} +} + // DeleteMeta removes a role from the snapshot. If the role doesn't // exist in the snapshot, it's a noop. func (sp *SignedSnapshot) DeleteMeta(role string) { @@ -97,11 +132,22 @@ func (sp *SignedSnapshot) DeleteMeta(role string) { } } +// MarshalJSON returns the serialized form of SignedSnapshot as bytes +func (sp *SignedSnapshot) MarshalJSON() ([]byte, error) { + signed, err := sp.ToSigned() + if err != nil { + return nil, err + } + return defaultSerializer.Marshal(signed) +} + // SnapshotFromSigned fully unpacks a Signed object into a SignedSnapshot func SnapshotFromSigned(s *Signed) (*SignedSnapshot, error) { sp := Snapshot{} - err := json.Unmarshal(s.Signed, &sp) - if err != nil { + if err := defaultSerializer.Unmarshal(s.Signed, &sp); err != nil { + return nil, err + } + if err := isValidSnapshotStructure(sp); err != nil { return nil, err } sigs := make([]Signature, len(s.Signatures)) diff --git a/vendor/src/github.com/docker/notary/tuf/data/targets.go b/vendor/src/github.com/docker/notary/tuf/data/targets.go index a538d6afa5..fce4d177e0 100644 --- a/vendor/src/github.com/docker/notary/tuf/data/targets.go +++ b/vendor/src/github.com/docker/notary/tuf/data/targets.go @@ -1,9 +1,9 @@ package data import ( - "crypto/sha256" - "encoding/hex" "errors" + "fmt" + "path" "github.com/docker/go/canonical/json" ) @@ -23,6 +23,33 @@ type Targets struct { Delegations Delegations `json:"delegations,omitempty"` } +// isValidTargetsStructure returns an error, or nil, depending on whether the content of the struct +// is valid for targets metadata. This does not check signatures or expiry, just that +// the metadata content is valid. +func isValidTargetsStructure(t Targets, roleName string) error { + if roleName != CanonicalTargetsRole && !IsDelegation(roleName) { + return ErrInvalidRole{Role: roleName} + } + + // even if it's a delegated role, the metadata type is "Targets" + expectedType := TUFTypes[CanonicalTargetsRole] + if t.Type != expectedType { + return ErrInvalidMetadata{ + role: roleName, msg: fmt.Sprintf("expected type %s, not %s", expectedType, t.Type)} + } + + for _, roleObj := range t.Delegations.Roles { + if !IsDelegation(roleObj.Name) || path.Dir(roleObj.Name) != roleName { + return ErrInvalidMetadata{ + role: roleName, msg: fmt.Sprintf("delegation role %s invalid", roleObj.Name)} + } + if err := isValidRootRoleStructure(roleName, roleObj.Name, roleObj.RootRole, t.Delegations.Keys); err != nil { + return err + } + } + return nil +} + // NewTargets intiializes a new empty SignedTargets object func NewTargets() *SignedTargets { return &SignedTargets{ @@ -51,30 +78,58 @@ func (t SignedTargets) GetMeta(path string) *FileMeta { return nil } -// GetDelegations filters the roles and associated keys that may be -// the signers for the given target path. If no appropriate roles -// can be found, it will simply return nil for the return values. -// The returned slice of Role will have order maintained relative -// to the role slice on Delegations per TUF spec proposal on using -// order to determine priority. -func (t SignedTargets) GetDelegations(path string) []*Role { - var roles []*Role - pathHashBytes := sha256.Sum256([]byte(path)) - pathHash := hex.EncodeToString(pathHashBytes[:]) - for _, r := range t.Signed.Delegations.Roles { - if !r.IsValid() { - // Role has both Paths and PathHashPrefixes. +// GetValidDelegations filters the delegation roles specified in the signed targets, and +// only returns roles that are direct children and restricts their paths +func (t SignedTargets) GetValidDelegations(parent DelegationRole) []DelegationRole { + roles := t.buildDelegationRoles() + result := []DelegationRole{} + for _, r := range roles { + validRole, err := parent.Restrict(r) + if err != nil { continue } - if r.CheckPaths(path) { - roles = append(roles, r) + result = append(result, validRole) + } + return result +} + +// BuildDelegationRole returns a copy of a DelegationRole using the information in this SignedTargets for the specified role name. +// Will error for invalid role name or key metadata within this SignedTargets. Path data is not validated. +func (t *SignedTargets) BuildDelegationRole(roleName string) (DelegationRole, error) { + for _, role := range t.Signed.Delegations.Roles { + if role.Name == roleName { + pubKeys := make(map[string]PublicKey) + for _, keyID := range role.KeyIDs { + pubKey, ok := t.Signed.Delegations.Keys[keyID] + if !ok { + // Couldn't retrieve all keys, so stop walking and return invalid role + return DelegationRole{}, ErrInvalidRole{Role: roleName, Reason: "delegation does not exist with all specified keys"} + } + pubKeys[keyID] = pubKey + } + return DelegationRole{ + BaseRole: BaseRole{ + Name: role.Name, + Keys: pubKeys, + Threshold: role.Threshold, + }, + Paths: role.Paths, + }, nil + } + } + return DelegationRole{}, ErrNoSuchRole{Role: roleName} +} + +// helper function to create DelegationRole structures from all delegations in a SignedTargets, +// these delegations are read directly from the SignedTargets and not modified or validated +func (t SignedTargets) buildDelegationRoles() []DelegationRole { + var roles []DelegationRole + for _, roleData := range t.Signed.Delegations.Roles { + delgRole, err := t.BuildDelegationRole(roleData.Name) + if err != nil { continue } - if r.CheckPrefixes(pathHash) { - roles = append(roles, r) - continue - } - //keysDB.AddRole(r) + roles = append(roles, delgRole) } return roles } @@ -93,8 +148,8 @@ func (t *SignedTargets) AddDelegation(role *Role, keys []*PublicKey) error { } // ToSigned partially serializes a SignedTargets for further signing -func (t SignedTargets) ToSigned() (*Signed, error) { - s, err := json.MarshalCanonical(t.Signed) +func (t *SignedTargets) ToSigned() (*Signed, error) { + s, err := defaultSerializer.MarshalCanonical(t.Signed) if err != nil { return nil, err } @@ -111,13 +166,25 @@ func (t SignedTargets) ToSigned() (*Signed, error) { }, nil } -// TargetsFromSigned fully unpacks a Signed object into a SignedTargets -func TargetsFromSigned(s *Signed) (*SignedTargets, error) { - t := Targets{} - err := json.Unmarshal(s.Signed, &t) +// MarshalJSON returns the serialized form of SignedTargets as bytes +func (t *SignedTargets) MarshalJSON() ([]byte, error) { + signed, err := t.ToSigned() if err != nil { return nil, err } + return defaultSerializer.Marshal(signed) +} + +// TargetsFromSigned fully unpacks a Signed object into a SignedTargets, given +// a role name (so it can validate the SignedTargets object) +func TargetsFromSigned(s *Signed, roleName string) (*SignedTargets, error) { + t := Targets{} + if err := defaultSerializer.Unmarshal(s.Signed, &t); err != nil { + return nil, err + } + if err := isValidTargetsStructure(t, roleName); err != nil { + return nil, err + } sigs := make([]Signature, len(s.Signatures)) copy(sigs, s.Signatures) return &SignedTargets{ diff --git a/vendor/src/github.com/docker/notary/tuf/data/timestamp.go b/vendor/src/github.com/docker/notary/tuf/data/timestamp.go index f68252ca5b..bc961f7a63 100644 --- a/vendor/src/github.com/docker/notary/tuf/data/timestamp.go +++ b/vendor/src/github.com/docker/notary/tuf/data/timestamp.go @@ -2,6 +2,8 @@ package data import ( "bytes" + "crypto/sha256" + "fmt" "time" "github.com/docker/go/canonical/json" @@ -22,6 +24,26 @@ type Timestamp struct { Meta Files `json:"meta"` } +// isValidTimestampStructure returns an error, or nil, depending on whether the content of the struct +// is valid for timestamp metadata. This does not check signatures or expiry, just that +// the metadata content is valid. +func isValidTimestampStructure(t Timestamp) error { + expectedType := TUFTypes[CanonicalTimestampRole] + if t.Type != expectedType { + return ErrInvalidMetadata{ + role: CanonicalTimestampRole, msg: fmt.Sprintf("expected type %s, not %s", expectedType, t.Type)} + } + + // Meta is a map of FileMeta, so if the role isn't in the map it returns + // an empty FileMeta, which has an empty map, and you can check on keys + // from an empty map. + if cs, ok := t.Meta[CanonicalSnapshotRole].Hashes["sha256"]; !ok || len(cs) != sha256.Size { + return ErrInvalidMetadata{ + role: CanonicalTimestampRole, msg: "missing or invalid snapshot sha256 checksum information"} + } + return nil +} + // NewTimestamp initializes a timestamp with an existing snapshot func NewTimestamp(snapshot *Signed) (*SignedTimestamp, error) { snapshotJSON, err := json.Marshal(snapshot) @@ -47,8 +69,8 @@ func NewTimestamp(snapshot *Signed) (*SignedTimestamp, error) { // ToSigned partially serializes a SignedTimestamp such that it can // be signed -func (ts SignedTimestamp) ToSigned() (*Signed, error) { - s, err := json.MarshalCanonical(ts.Signed) +func (ts *SignedTimestamp) ToSigned() (*Signed, error) { + s, err := defaultSerializer.MarshalCanonical(ts.Signed) if err != nil { return nil, err } @@ -65,12 +87,33 @@ func (ts SignedTimestamp) ToSigned() (*Signed, error) { }, nil } +// GetSnapshot gets the expected snapshot metadata hashes in the timestamp metadata, +// or nil if it doesn't exist +func (ts *SignedTimestamp) GetSnapshot() (*FileMeta, error) { + snapshotExpected, ok := ts.Signed.Meta[CanonicalSnapshotRole] + if !ok { + return nil, ErrMissingMeta{Role: CanonicalSnapshotRole} + } + return &snapshotExpected, nil +} + +// MarshalJSON returns the serialized form of SignedTimestamp as bytes +func (ts *SignedTimestamp) MarshalJSON() ([]byte, error) { + signed, err := ts.ToSigned() + if err != nil { + return nil, err + } + return defaultSerializer.Marshal(signed) +} + // TimestampFromSigned parsed a Signed object into a fully unpacked // SignedTimestamp func TimestampFromSigned(s *Signed) (*SignedTimestamp, error) { ts := Timestamp{} - err := json.Unmarshal(s.Signed, &ts) - if err != nil { + if err := defaultSerializer.Unmarshal(s.Signed, &ts); err != nil { + return nil, err + } + if err := isValidTimestampStructure(ts); err != nil { return nil, err } sigs := make([]Signature, len(s.Signatures)) diff --git a/vendor/src/github.com/docker/notary/tuf/data/types.go b/vendor/src/github.com/docker/notary/tuf/data/types.go index 6459b8e664..4de55c4e1c 100644 --- a/vendor/src/github.com/docker/notary/tuf/data/types.go +++ b/vendor/src/github.com/docker/notary/tuf/data/types.go @@ -12,6 +12,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/go/canonical/json" + "github.com/docker/notary" ) // SigAlgorithm for types of signatures @@ -171,16 +172,16 @@ func NewDelegations() *Delegations { } } -// defines number of days in which something should expire -var defaultExpiryTimes = map[string]int{ - CanonicalRootRole: 365, - CanonicalTargetsRole: 90, - CanonicalSnapshotRole: 7, - CanonicalTimestampRole: 1, +// These values are recommended TUF expiry times. +var defaultExpiryTimes = map[string]time.Duration{ + CanonicalRootRole: notary.Year, + CanonicalTargetsRole: 90 * notary.Day, + CanonicalSnapshotRole: 7 * notary.Day, + CanonicalTimestampRole: notary.Day, } // SetDefaultExpiryTimes allows one to change the default expiries. -func SetDefaultExpiryTimes(times map[string]int) { +func SetDefaultExpiryTimes(times map[string]time.Duration) { for key, value := range times { if _, ok := defaultExpiryTimes[key]; !ok { logrus.Errorf("Attempted to set default expiry for an unknown role: %s", key) @@ -192,10 +193,10 @@ func SetDefaultExpiryTimes(times map[string]int) { // DefaultExpires gets the default expiry time for the given role func DefaultExpires(role string) time.Time { - var t time.Time - if t, ok := defaultExpiryTimes[role]; ok { - return time.Now().AddDate(0, 0, t) + if d, ok := defaultExpiryTimes[role]; ok { + return time.Now().Add(d) } + var t time.Time return t.UTC().Round(time.Second) } diff --git a/vendor/src/github.com/docker/notary/tuf/keys/db.go b/vendor/src/github.com/docker/notary/tuf/keys/db.go deleted file mode 100644 index 92d2ef863e..0000000000 --- a/vendor/src/github.com/docker/notary/tuf/keys/db.go +++ /dev/null @@ -1,78 +0,0 @@ -package keys - -import ( - "errors" - - "github.com/docker/notary/tuf/data" -) - -// Various basic key database errors -var ( - ErrWrongType = errors.New("tuf: invalid key type") - ErrExists = errors.New("tuf: key already in db") - ErrWrongID = errors.New("tuf: key id mismatch") - ErrInvalidKey = errors.New("tuf: invalid key") - ErrInvalidKeyID = errors.New("tuf: invalid key id") - ErrInvalidThreshold = errors.New("tuf: invalid role threshold") -) - -// KeyDB is an in memory database of public keys and role associations. -// It is populated when parsing TUF files and used during signature -// verification to look up the keys for a given role -type KeyDB struct { - roles map[string]*data.Role - keys map[string]data.PublicKey -} - -// NewDB initializes an empty KeyDB -func NewDB() *KeyDB { - return &KeyDB{ - roles: make(map[string]*data.Role), - keys: make(map[string]data.PublicKey), - } -} - -// AddKey adds a public key to the database -func (db *KeyDB) AddKey(k data.PublicKey) { - db.keys[k.ID()] = k -} - -// AddRole adds a role to the database. Any keys associated with the -// role must have already been added. -func (db *KeyDB) AddRole(r *data.Role) error { - if !data.ValidRole(r.Name) { - return data.ErrInvalidRole{Role: r.Name} - } - if r.Threshold < 1 { - return ErrInvalidThreshold - } - - // validate all key ids are in the keys maps - for _, id := range r.KeyIDs { - if _, ok := db.keys[id]; !ok { - return ErrInvalidKeyID - } - } - - db.roles[r.Name] = r - return nil -} - -// GetAllRoles gets all roles from the database -func (db *KeyDB) GetAllRoles() []*data.Role { - roles := []*data.Role{} - for _, role := range db.roles { - roles = append(roles, role) - } - return roles -} - -// GetKey pulls a key out of the database by its ID -func (db *KeyDB) GetKey(id string) data.PublicKey { - return db.keys[id] -} - -// GetRole retrieves a role based on its name -func (db *KeyDB) GetRole(name string) *data.Role { - return db.roles[name] -} diff --git a/vendor/src/github.com/docker/notary/tuf/signed/verify.go b/vendor/src/github.com/docker/notary/tuf/signed/verify.go index 9548e4e53d..4869b17074 100644 --- a/vendor/src/github.com/docker/notary/tuf/signed/verify.go +++ b/vendor/src/github.com/docker/notary/tuf/signed/verify.go @@ -8,7 +8,6 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/go/canonical/json" "github.com/docker/notary/tuf/data" - "github.com/docker/notary/tuf/keys" ) // Various basic signing errors @@ -57,18 +56,18 @@ func VerifyRoot(s *data.Signed, minVersion int, keys map[string]data.PublicKey) continue } // threshold of 1 so return on first success - return verifyMeta(s, "root", minVersion) + return verifyMeta(s, data.CanonicalRootRole, minVersion) } return ErrRoleThreshold{} } // Verify checks the signatures and metadata (expiry, version) for the signed role // data -func Verify(s *data.Signed, role string, minVersion int, db *keys.KeyDB) error { - if err := verifyMeta(s, role, minVersion); err != nil { +func Verify(s *data.Signed, role data.BaseRole, minVersion int) error { + if err := verifyMeta(s, role.Name, minVersion); err != nil { return err } - return VerifySignatures(s, role, db) + return VerifySignatures(s, role) } func verifyMeta(s *data.Signed, role string, minVersion int) error { @@ -96,21 +95,18 @@ func IsExpired(t time.Time) bool { } // VerifySignatures checks the we have sufficient valid signatures for the given role -func VerifySignatures(s *data.Signed, role string, db *keys.KeyDB) error { +func VerifySignatures(s *data.Signed, roleData data.BaseRole) error { if len(s.Signatures) == 0 { return ErrNoSignatures } - roleData := db.GetRole(role) - if roleData == nil { - return ErrUnknownRole - } - if roleData.Threshold < 1 { return ErrRoleThreshold{} } - logrus.Debugf("%s role has key IDs: %s", role, strings.Join(roleData.KeyIDs, ",")) + logrus.Debugf("%s role has key IDs: %s", roleData.Name, strings.Join(roleData.ListKeyIDs(), ",")) + // remarshal the signed part so we can verify the signature, since the signature has + // to be of a canonically marshalled signed object var decoded map[string]interface{} if err := json.Unmarshal(s.Signed, &decoded); err != nil { return err @@ -123,12 +119,8 @@ func VerifySignatures(s *data.Signed, role string, db *keys.KeyDB) error { valid := make(map[string]struct{}) for _, sig := range s.Signatures { logrus.Debug("verifying signature for key ID: ", sig.KeyID) - if !roleData.ValidKey(sig.KeyID) { - logrus.Debugf("continuing b/c keyid was invalid: %s for roledata %s\n", sig.KeyID, roleData) - continue - } - key := db.GetKey(sig.KeyID) - if key == nil { + key, ok := roleData.Keys[sig.KeyID] + if !ok { logrus.Debugf("continuing b/c keyid lookup was nil: %s\n", sig.KeyID) continue } @@ -153,28 +145,3 @@ func VerifySignatures(s *data.Signed, role string, db *keys.KeyDB) error { return nil } - -// Unmarshal unmarshals and verifys the raw bytes for a given role's metadata -func Unmarshal(b []byte, v interface{}, role string, minVersion int, db *keys.KeyDB) error { - s := &data.Signed{} - if err := json.Unmarshal(b, s); err != nil { - return err - } - if err := Verify(s, role, minVersion, db); err != nil { - return err - } - return json.Unmarshal(s.Signed, v) -} - -// UnmarshalTrusted unmarshals and verifies signatures only, not metadata, for a -// given role's metadata -func UnmarshalTrusted(b []byte, v interface{}, role string, db *keys.KeyDB) error { - s := &data.Signed{} - if err := json.Unmarshal(b, s); err != nil { - return err - } - if err := VerifySignatures(s, role, db); err != nil { - return err - } - return json.Unmarshal(s.Signed, v) -} diff --git a/vendor/src/github.com/docker/notary/tuf/store/filestore.go b/vendor/src/github.com/docker/notary/tuf/store/filestore.go index 52e7c8f289..44401707c4 100644 --- a/vendor/src/github.com/docker/notary/tuf/store/filestore.go +++ b/vendor/src/github.com/docker/notary/tuf/store/filestore.go @@ -2,6 +2,7 @@ package store import ( "fmt" + "github.com/docker/notary" "io/ioutil" "os" "path" @@ -9,25 +10,19 @@ import ( ) // NewFilesystemStore creates a new store in a directory tree -func NewFilesystemStore(baseDir, metaSubDir, metaExtension, targetsSubDir string) (*FilesystemStore, error) { +func NewFilesystemStore(baseDir, metaSubDir, metaExtension string) (*FilesystemStore, error) { metaDir := path.Join(baseDir, metaSubDir) - targetsDir := path.Join(baseDir, targetsSubDir) // Make sure we can create the necessary dirs and they are writable err := os.MkdirAll(metaDir, 0700) if err != nil { return nil, err } - err = os.MkdirAll(targetsDir, 0700) - if err != nil { - return nil, err - } return &FilesystemStore{ baseDir: baseDir, metaDir: metaDir, metaExtension: metaExtension, - targetsDir: targetsDir, }, nil } @@ -36,7 +31,6 @@ type FilesystemStore struct { baseDir string metaDir string metaExtension string - targetsDir string } func (f *FilesystemStore) getPath(name string) string { @@ -44,7 +38,8 @@ func (f *FilesystemStore) getPath(name string) string { return filepath.Join(f.metaDir, fileName) } -// GetMeta returns the meta for the given name (a role) +// GetMeta returns the meta for the given name (a role) up to size bytes +// If size is -1, this corresponds to "infinite," but we cut off at 100MB func (f *FilesystemStore) GetMeta(name string, size int64) ([]byte, error) { meta, err := ioutil.ReadFile(f.getPath(name)) if err != nil { @@ -53,7 +48,14 @@ func (f *FilesystemStore) GetMeta(name string, size int64) ([]byte, error) { } return nil, err } - return meta, nil + if size == -1 { + size = notary.MaxDownloadSize + } + // Only return up to size bytes + if int64(len(meta)) < size { + return meta, nil + } + return meta[:size], nil } // SetMultiMeta sets the metadata for multiple roles in one operation 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 7444a311b9..8b0d850114 100644 --- a/vendor/src/github.com/docker/notary/tuf/store/httpstore.go +++ b/vendor/src/github.com/docker/notary/tuf/store/httpstore.go @@ -23,6 +23,7 @@ import ( "path" "github.com/Sirupsen/logrus" + "github.com/docker/notary" "github.com/docker/notary/tuf/validation" ) @@ -33,6 +34,9 @@ type ErrServerUnavailable struct { } func (err ErrServerUnavailable) Error() string { + if err.code == 401 { + return fmt.Sprintf("you are not authorized to perform this operation: server returned 401.") + } return fmt.Sprintf("unable to reach trust server at this time: %d.", err.code) } @@ -71,13 +75,12 @@ type HTTPStore struct { baseURL url.URL metaPrefix string metaExtension string - targetsPrefix string keyExtension string roundTrip http.RoundTripper } // NewHTTPStore initializes a new store against a URL and a number of configuration options -func NewHTTPStore(baseURL, metaPrefix, metaExtension, targetsPrefix, keyExtension string, roundTrip http.RoundTripper) (RemoteStore, error) { +func NewHTTPStore(baseURL, metaPrefix, metaExtension, keyExtension string, roundTrip http.RoundTripper) (RemoteStore, error) { base, err := url.Parse(baseURL) if err != nil { return nil, err @@ -92,7 +95,6 @@ func NewHTTPStore(baseURL, metaPrefix, metaExtension, targetsPrefix, keyExtensio baseURL: *base, metaPrefix: metaPrefix, metaExtension: metaExtension, - targetsPrefix: targetsPrefix, keyExtension: keyExtension, roundTrip: roundTrip, }, nil @@ -137,6 +139,7 @@ func translateStatusToError(resp *http.Response, resource string) error { // GetMeta downloads the named meta file with the given size. A short body // is acceptable because in the case of timestamp.json, the size is a cap, // not an exact length. +// If size is -1, this corresponds to "infinite," but we cut off at 100MB func (s HTTPStore) GetMeta(name string, size int64) ([]byte, error) { url, err := s.buildMetaURL(name) if err != nil { @@ -155,6 +158,9 @@ func (s HTTPStore) GetMeta(name string, size int64) ([]byte, error) { logrus.Debugf("received HTTP status %d when requesting %s.", resp.StatusCode, name) return nil, err } + if size == -1 { + size = notary.MaxDownloadSize + } if resp.ContentLength > size { return nil, ErrMaliciousServer{} } @@ -250,11 +256,6 @@ func (s HTTPStore) buildMetaURL(name string) (*url.URL, error) { return s.buildURL(uri) } -func (s HTTPStore) buildTargetsURL(name string) (*url.URL, error) { - uri := path.Join(s.targetsPrefix, name) - return s.buildURL(uri) -} - func (s HTTPStore) buildKeyURL(name string) (*url.URL, error) { filename := fmt.Sprintf("%s.%s", name, s.keyExtension) uri := path.Join(s.metaPrefix, filename) @@ -269,29 +270,6 @@ func (s HTTPStore) buildURL(uri string) (*url.URL, error) { return s.baseURL.ResolveReference(sub), nil } -// GetTarget returns a reader for the desired target or an error. -// N.B. The caller is responsible for closing the reader. -func (s HTTPStore) GetTarget(path string) (io.ReadCloser, error) { - url, err := s.buildTargetsURL(path) - if err != nil { - return nil, err - } - logrus.Debug("Attempting to download target: ", url.String()) - req, err := http.NewRequest("GET", url.String(), nil) - if err != nil { - return nil, err - } - resp, err := s.roundTrip.RoundTrip(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - if err := translateStatusToError(resp, path); err != nil { - return nil, err - } - return resp.Body, nil -} - // GetKey retrieves a public key from the remote server func (s HTTPStore) GetKey(role string) ([]byte, error) { url, err := s.buildKeyURL(role) diff --git a/vendor/src/github.com/docker/notary/tuf/store/interfaces.go b/vendor/src/github.com/docker/notary/tuf/store/interfaces.go index 6d73da8a96..dd307168bf 100644 --- a/vendor/src/github.com/docker/notary/tuf/store/interfaces.go +++ b/vendor/src/github.com/docker/notary/tuf/store/interfaces.go @@ -1,13 +1,5 @@ package store -import ( - "io" - - "github.com/docker/notary/tuf/data" -) - -type targetsWalkFunc func(path string, meta data.FileMeta) error - // MetadataStore must be implemented by anything that intends to interact // with a store of TUF files type MetadataStore interface { @@ -23,17 +15,9 @@ type PublicKeyStore interface { GetKey(role string) ([]byte, error) } -// TargetStore represents a collection of targets that can be walked similarly -// to walking a directory, passing a callback that receives the path and meta -// for each target -type TargetStore interface { - WalkStagedTargets(paths []string, targetsFn targetsWalkFunc) error -} - // LocalStore represents a local TUF sture type LocalStore interface { MetadataStore - TargetStore } // RemoteStore is similar to LocalStore with the added expectation that it should @@ -41,5 +25,4 @@ type LocalStore interface { type RemoteStore interface { MetadataStore PublicKeyStore - GetTarget(path string) (io.ReadCloser, error) } diff --git a/vendor/src/github.com/docker/notary/tuf/store/memorystore.go b/vendor/src/github.com/docker/notary/tuf/store/memorystore.go index 6072a8c446..493bb6f0b5 100644 --- a/vendor/src/github.com/docker/notary/tuf/store/memorystore.go +++ b/vendor/src/github.com/docker/notary/tuf/store/memorystore.go @@ -1,38 +1,59 @@ package store import ( - "bytes" + "crypto/sha256" "fmt" - "io" + "github.com/docker/notary" "github.com/docker/notary/tuf/data" "github.com/docker/notary/tuf/utils" ) // NewMemoryStore returns a MetadataStore that operates entirely in memory. // Very useful for testing -func NewMemoryStore(meta map[string][]byte, files map[string][]byte) RemoteStore { +func NewMemoryStore(meta map[string][]byte) *MemoryStore { + var consistent = make(map[string][]byte) if meta == nil { meta = make(map[string][]byte) + } else { + // add all seed meta to consistent + for name, data := range meta { + checksum := sha256.Sum256(data) + path := utils.ConsistentName(name, checksum[:]) + consistent[path] = data + } } - if files == nil { - files = make(map[string][]byte) - } - return &memoryStore{ - meta: meta, - files: files, - keys: make(map[string][]data.PrivateKey), + return &MemoryStore{ + meta: meta, + consistent: consistent, + keys: make(map[string][]data.PrivateKey), } } -type memoryStore struct { - meta map[string][]byte - files map[string][]byte - keys map[string][]data.PrivateKey +// MemoryStore implements a mock RemoteStore entirely in memory. +// For testing purposes only. +type MemoryStore struct { + meta map[string][]byte + consistent map[string][]byte + keys map[string][]data.PrivateKey } -func (m *memoryStore) GetMeta(name string, size int64) ([]byte, error) { +// GetMeta returns up to size bytes of data references by name. +// If size is -1, this corresponds to "infinite," but we cut off at 100MB +// as we will always know the size for everything but a timestamp and +// sometimes a root, neither of which should be exceptionally large +func (m *MemoryStore) GetMeta(name string, size int64) ([]byte, error) { d, ok := m.meta[name] + if ok { + if size == -1 { + size = notary.MaxDownloadSize + } + if int64(len(d)) < size { + return d, nil + } + return d[:size], nil + } + d, ok = m.consistent[name] if ok { if int64(len(d)) < size { return d, nil @@ -42,12 +63,19 @@ func (m *memoryStore) GetMeta(name string, size int64) ([]byte, error) { return nil, ErrMetaNotFound{Resource: name} } -func (m *memoryStore) SetMeta(name string, meta []byte) error { +// SetMeta sets the metadata value for the given name +func (m *MemoryStore) SetMeta(name string, meta []byte) error { m.meta[name] = meta + + checksum := sha256.Sum256(meta) + path := utils.ConsistentName(name, checksum[:]) + m.consistent[path] = meta return nil } -func (m *memoryStore) SetMultiMeta(metas map[string][]byte) error { +// SetMultiMeta sets multiple pieces of metadata for multiple names +// in a single operation. +func (m *MemoryStore) SetMultiMeta(metas map[string][]byte) error { for role, blob := range metas { m.SetMeta(role, blob) } @@ -56,57 +84,23 @@ func (m *memoryStore) SetMultiMeta(metas map[string][]byte) error { // RemoveMeta removes the metadata for a single role - if the metadata doesn't // exist, no error is returned -func (m *memoryStore) RemoveMeta(name string) error { - delete(m.meta, name) - return nil -} - -func (m *memoryStore) GetTarget(path string) (io.ReadCloser, error) { - return &utils.NoopCloser{Reader: bytes.NewReader(m.files[path])}, nil -} - -func (m *memoryStore) WalkStagedTargets(paths []string, targetsFn targetsWalkFunc) error { - if len(paths) == 0 { - for path, dat := range m.files { - meta, err := data.NewFileMeta(bytes.NewReader(dat), "sha256") - if err != nil { - return err - } - if err = targetsFn(path, meta); err != nil { - return err - } - } - return nil - } - - for _, path := range paths { - dat, ok := m.files[path] - if !ok { - return ErrMetaNotFound{Resource: path} - } - meta, err := data.NewFileMeta(bytes.NewReader(dat), "sha256") - if err != nil { - return err - } - if err = targetsFn(path, meta); err != nil { - return err - } +func (m *MemoryStore) RemoveMeta(name string) error { + if meta, ok := m.meta[name]; ok { + checksum := sha256.Sum256(meta) + path := utils.ConsistentName(name, checksum[:]) + delete(m.meta, name) + delete(m.consistent, path) } return nil } -func (m *memoryStore) Commit(map[string][]byte, bool, map[string]data.Hashes) error { - return nil +// GetKey returns the public key for the given role +func (m *MemoryStore) GetKey(role string) ([]byte, error) { + return nil, fmt.Errorf("GetKey is not implemented for the MemoryStore") } -func (m *memoryStore) GetKey(role string) ([]byte, error) { - return nil, fmt.Errorf("GetKey is not implemented for the memoryStore") -} - -// Clear this existing memory store by setting this store as new empty one -func (m *memoryStore) RemoveAll() error { - m.meta = make(map[string][]byte) - m.files = make(map[string][]byte) - m.keys = make(map[string][]data.PrivateKey) +// RemoveAll clears the existing memory store by setting this store as new empty one +func (m *MemoryStore) RemoveAll() error { + *m = *NewMemoryStore(nil) return nil } diff --git a/vendor/src/github.com/docker/notary/tuf/tuf.go b/vendor/src/github.com/docker/notary/tuf/tuf.go index 96ab7da1d6..0a1912a5f5 100644 --- a/vendor/src/github.com/docker/notary/tuf/tuf.go +++ b/vendor/src/github.com/docker/notary/tuf/tuf.go @@ -3,8 +3,6 @@ package tuf import ( "bytes" - "crypto/sha256" - "encoding/hex" "encoding/json" "fmt" "path" @@ -12,8 +10,8 @@ import ( "time" "github.com/Sirupsen/logrus" + "github.com/docker/notary" "github.com/docker/notary/tuf/data" - "github.com/docker/notary/tuf/keys" "github.com/docker/notary/tuf/signed" "github.com/docker/notary/tuf/utils" ) @@ -40,15 +38,19 @@ func (e ErrLocalRootExpired) Error() string { } // ErrNotLoaded - attempted to access data that has not been loaded into -// the repo +// the repo. This means specifically that the relevant JSON file has not +// been loaded. type ErrNotLoaded struct { - role string + Role string } func (err ErrNotLoaded) Error() string { - return fmt.Sprintf("%s role has not been loaded", err.role) + return fmt.Sprintf("%s role has not been loaded", err.Role) } +// StopWalk - used by visitor functions to signal WalkTargets to stop walking +type StopWalk struct{} + // Repo is an in memory representation of the TUF Repo. // It operates at the data.Signed level, accepting and producing // data.Signed objects. Users of a Repo are responsible for @@ -59,16 +61,15 @@ type Repo struct { Targets map[string]*data.SignedTargets Snapshot *data.SignedSnapshot Timestamp *data.SignedTimestamp - keysDB *keys.KeyDB cryptoService signed.CryptoService } -// NewRepo initializes a Repo instance with a keysDB and a signer. -// If the Repo will only be used for reading, the signer should be nil. -func NewRepo(keysDB *keys.KeyDB, cryptoService signed.CryptoService) *Repo { +// NewRepo initializes a Repo instance with a CryptoService. +// If the Repo will only be used for reading, the CryptoService +// can be nil. +func NewRepo(cryptoService signed.CryptoService) *Repo { repo := &Repo{ Targets: make(map[string]*data.SignedTargets), - keysDB: keysDB, cryptoService: cryptoService, } return repo @@ -77,27 +78,15 @@ func NewRepo(keysDB *keys.KeyDB, cryptoService signed.CryptoService) *Repo { // AddBaseKeys is used to add keys to the role in root.json func (tr *Repo) AddBaseKeys(role string, keys ...data.PublicKey) error { if tr.Root == nil { - return ErrNotLoaded{role: "root"} + return ErrNotLoaded{Role: data.CanonicalRootRole} } ids := []string{} for _, k := range keys { // Store only the public portion tr.Root.Signed.Keys[k.ID()] = k - tr.keysDB.AddKey(k) tr.Root.Signed.Roles[role].KeyIDs = append(tr.Root.Signed.Roles[role].KeyIDs, k.ID()) ids = append(ids, k.ID()) } - r, err := data.NewRole( - role, - tr.Root.Signed.Roles[role].Threshold, - ids, - nil, - nil, - ) - if err != nil { - return err - } - tr.keysDB.AddRole(r) tr.Root.Dirty = true // also, whichever role was switched out needs to be re-signed @@ -121,8 +110,11 @@ func (tr *Repo) AddBaseKeys(role string, keys ...data.PublicKey) error { // ReplaceBaseKeys is used to replace all keys for the given role with the new keys func (tr *Repo) ReplaceBaseKeys(role string, keys ...data.PublicKey) error { - r := tr.keysDB.GetRole(role) - err := tr.RemoveBaseKeys(role, r.KeyIDs...) + r, err := tr.GetBaseRole(role) + if err != nil { + return err + } + err = tr.RemoveBaseKeys(role, r.ListKeyIDs()...) if err != nil { return err } @@ -132,7 +124,7 @@ func (tr *Repo) ReplaceBaseKeys(role string, keys ...data.PublicKey) error { // RemoveBaseKeys is used to remove keys from the roles in root.json func (tr *Repo) RemoveBaseKeys(role string, keyIDs ...string) error { if tr.Root == nil { - return ErrNotLoaded{role: "root"} + return ErrNotLoaded{Role: data.CanonicalRootRole} } var keep []string toDelete := make(map[string]struct{}) @@ -173,117 +165,253 @@ func (tr *Repo) RemoveBaseKeys(role string, keyIDs ...string) error { return nil } +// GetBaseRole gets a base role from this repo's metadata +func (tr *Repo) GetBaseRole(name string) (data.BaseRole, error) { + if !data.ValidRole(name) { + return data.BaseRole{}, data.ErrInvalidRole{Role: name, Reason: "invalid base role name"} + } + if tr.Root == nil { + return data.BaseRole{}, ErrNotLoaded{data.CanonicalRootRole} + } + // Find the role data public keys for the base role from TUF metadata + baseRole, err := tr.Root.BuildBaseRole(name) + if err != nil { + return data.BaseRole{}, err + } + + return baseRole, nil +} + +// GetDelegationRole gets a delegation role from this repo's metadata, walking from the targets role down to the delegation itself +func (tr *Repo) GetDelegationRole(name string) (data.DelegationRole, error) { + if !data.IsDelegation(name) { + return data.DelegationRole{}, data.ErrInvalidRole{Role: name, Reason: "invalid delegation name"} + } + if tr.Root == nil { + return data.DelegationRole{}, ErrNotLoaded{data.CanonicalRootRole} + } + _, ok := tr.Root.Signed.Roles[data.CanonicalTargetsRole] + if !ok { + return data.DelegationRole{}, ErrNotLoaded{data.CanonicalTargetsRole} + } + // Traverse target metadata, down to delegation itself + // Get all public keys for the base role from TUF metadata + _, ok = tr.Targets[data.CanonicalTargetsRole] + if !ok { + return data.DelegationRole{}, ErrNotLoaded{data.CanonicalTargetsRole} + } + + // Start with top level roles in targets. Walk the chain of ancestors + // until finding the desired role, or we run out of targets files to search. + var foundRole *data.DelegationRole + buildDelegationRoleVisitor := func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} { + // Try to find the delegation and build a DelegationRole structure + for _, role := range tgt.Signed.Delegations.Roles { + if role.Name == name { + delgRole, err := tgt.BuildDelegationRole(name) + if err != nil { + return err + } + foundRole = &delgRole + return StopWalk{} + } + } + return nil + } + + // Walk to the parent of this delegation, since that is where its role metadata exists + err := tr.WalkTargets("", path.Dir(name), buildDelegationRoleVisitor) + if err != nil { + return data.DelegationRole{}, err + } + + // We never found the delegation. In the context of this repo it is considered + // invalid. N.B. it may be that it existed at one point but an ancestor has since + // been modified/removed. + if foundRole == nil { + return data.DelegationRole{}, data.ErrInvalidRole{Role: name, Reason: "delegation does not exist"} + } + + return *foundRole, nil +} + // GetAllLoadedRoles returns a list of all role entries loaded in this TUF repo, could be empty func (tr *Repo) GetAllLoadedRoles() []*data.Role { - return tr.keysDB.GetAllRoles() + var res []*data.Role + if tr.Root == nil { + // if root isn't loaded, we should consider we have no loaded roles because we can't + // trust any other state that might be present + return res + } + for name, rr := range tr.Root.Signed.Roles { + res = append(res, &data.Role{ + RootRole: *rr, + Name: name, + }) + } + for _, delegate := range tr.Targets { + for _, r := range delegate.Signed.Delegations.Roles { + res = append(res, r) + } + } + return res } -// GetDelegation finds the role entry representing the provided -// role name or ErrInvalidRole -func (tr *Repo) GetDelegation(role string) (*data.Role, error) { - r := data.Role{Name: role} - if !r.IsDelegation() { - return nil, data.ErrInvalidRole{Role: role, Reason: "not a valid delegated role"} +// Walk to parent, and either create or update this delegation. We can only create a new delegation if we're given keys +// Ensure all updates are valid, by checking against parent ancestor paths and ensuring the keys meet the role threshold. +func delegationUpdateVisitor(roleName string, addKeys data.KeyList, removeKeys, addPaths, removePaths []string, clearAllPaths bool, newThreshold int) walkVisitorFunc { + return func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} { + var err error + // Validate the changes underneath this restricted validRole for adding paths, reject invalid path additions + if len(addPaths) != len(data.RestrictDelegationPathPrefixes(validRole.Paths, addPaths)) { + return data.ErrInvalidRole{Role: roleName, Reason: "invalid paths to add to role"} + } + // Try to find the delegation and amend it using our changelist + var delgRole *data.Role + for _, role := range tgt.Signed.Delegations.Roles { + if role.Name == roleName { + // Make a copy and operate on this role until we validate the changes + keyIDCopy := make([]string, len(role.KeyIDs)) + copy(keyIDCopy, role.KeyIDs) + pathsCopy := make([]string, len(role.Paths)) + copy(pathsCopy, role.Paths) + delgRole = &data.Role{ + RootRole: data.RootRole{ + KeyIDs: keyIDCopy, + Threshold: role.Threshold, + }, + Name: role.Name, + Paths: pathsCopy, + } + delgRole.RemovePaths(removePaths) + if clearAllPaths { + delgRole.Paths = []string{} + } + delgRole.AddPaths(addPaths) + delgRole.RemoveKeys(removeKeys) + break + } + } + // We didn't find the role earlier, so create it only if we have keys to add + if delgRole == nil { + if len(addKeys) > 0 { + delgRole, err = data.NewRole(roleName, newThreshold, addKeys.IDs(), addPaths) + if err != nil { + return err + } + } else { + // If we can't find the role and didn't specify keys to add, this is an error + return data.ErrInvalidRole{Role: roleName, Reason: "cannot create new delegation without keys"} + } + } + // Add the key IDs to the role and the keys themselves to the parent + for _, k := range addKeys { + if !utils.StrSliceContains(delgRole.KeyIDs, k.ID()) { + delgRole.KeyIDs = append(delgRole.KeyIDs, k.ID()) + } + } + // Make sure we have a valid role still + if len(delgRole.KeyIDs) < delgRole.Threshold { + return data.ErrInvalidRole{Role: roleName, Reason: "insufficient keys to meet threshold"} + } + // NOTE: this closure CANNOT error after this point, as we've committed to editing the SignedTargets metadata in the repo object. + // Any errors related to updating this delegation must occur before this point. + // If all of our changes were valid, we should edit the actual SignedTargets to match our copy + for _, k := range addKeys { + tgt.Signed.Delegations.Keys[k.ID()] = k + } + foundAt := utils.FindRoleIndex(tgt.Signed.Delegations.Roles, delgRole.Name) + if foundAt < 0 { + tgt.Signed.Delegations.Roles = append(tgt.Signed.Delegations.Roles, delgRole) + } else { + tgt.Signed.Delegations.Roles[foundAt] = delgRole + } + tgt.Dirty = true + utils.RemoveUnusedKeys(tgt) + return StopWalk{} } - - parent := path.Dir(role) - - // check the parent role - if parentRole := tr.keysDB.GetRole(parent); parentRole == nil { - return nil, data.ErrInvalidRole{Role: role, Reason: "parent role not found"} - } - - // check the parent role's metadata - p, ok := tr.Targets[parent] - if !ok { // the parent targetfile may not exist yet, so it can't be in the list - return nil, data.ErrNoSuchRole{Role: role} - } - - foundAt := utils.FindRoleIndex(p.Signed.Delegations.Roles, role) - if foundAt < 0 { - return nil, data.ErrNoSuchRole{Role: role} - } - return p.Signed.Delegations.Roles[foundAt], nil } -// UpdateDelegations updates the appropriate delegations, either adding +// UpdateDelegationKeys updates the appropriate delegations, either adding // a new delegation or updating an existing one. If keys are // provided, the IDs will be added to the role (if they do not exist // there already), and the keys will be added to the targets file. -func (tr *Repo) UpdateDelegations(role *data.Role, keys []data.PublicKey) error { - if !role.IsDelegation() || !role.IsValid() { - return data.ErrInvalidRole{Role: role.Name, Reason: "not a valid delegated role"} +func (tr *Repo) UpdateDelegationKeys(roleName string, addKeys data.KeyList, removeKeys []string, newThreshold int) error { + if !data.IsDelegation(roleName) { + return data.ErrInvalidRole{Role: roleName, Reason: "not a valid delegated role"} } - parent := path.Dir(role.Name) + parent := path.Dir(roleName) if err := tr.VerifyCanSign(parent); err != nil { return err } // check the parent role's metadata - p, ok := tr.Targets[parent] + _, ok := tr.Targets[parent] if !ok { // the parent targetfile may not exist yet - if not, then create it var err error - p, err = tr.InitTargets(parent) + _, err = tr.InitTargets(parent) if err != nil { return err } } - for _, k := range keys { - if !utils.StrSliceContains(role.KeyIDs, k.ID()) { - role.KeyIDs = append(role.KeyIDs, k.ID()) - } - p.Signed.Delegations.Keys[k.ID()] = k - tr.keysDB.AddKey(k) + // Walk to the parent of this delegation, since that is where its role metadata exists + // We do not have to verify that the walker reached its desired role in this scenario + // since we've already done another walk to the parent role in VerifyCanSign, and potentially made a targets file + err := tr.WalkTargets("", parent, delegationUpdateVisitor(roleName, addKeys, removeKeys, []string{}, []string{}, false, newThreshold)) + if err != nil { + return err + } + return nil +} + +// UpdateDelegationPaths updates the appropriate delegation's paths. +// It is not allowed to create a new delegation. +func (tr *Repo) UpdateDelegationPaths(roleName string, addPaths, removePaths []string, clearPaths bool) error { + if !data.IsDelegation(roleName) { + return data.ErrInvalidRole{Role: roleName, Reason: "not a valid delegated role"} + } + parent := path.Dir(roleName) + + if err := tr.VerifyCanSign(parent); err != nil { + return err } - // if the role has fewer keys than the threshold, it - // will never be able to create a valid targets file - // and should be considered invalid. - if len(role.KeyIDs) < role.Threshold { - return data.ErrInvalidRole{Role: role.Name, Reason: "insufficient keys to meet threshold"} + // check the parent role's metadata + _, ok := tr.Targets[parent] + if !ok { // the parent targetfile may not exist yet + // if not, this is an error because a delegation must exist to edit only paths + return data.ErrInvalidRole{Role: roleName, Reason: "no valid delegated role exists"} } - foundAt := utils.FindRoleIndex(p.Signed.Delegations.Roles, role.Name) - - if foundAt >= 0 { - p.Signed.Delegations.Roles[foundAt] = role - } else { - p.Signed.Delegations.Roles = append(p.Signed.Delegations.Roles, role) + // Walk to the parent of this delegation, since that is where its role metadata exists + // We do not have to verify that the walker reached its desired role in this scenario + // since we've already done another walk to the parent role in VerifyCanSign + err := tr.WalkTargets("", parent, delegationUpdateVisitor(roleName, data.KeyList{}, []string{}, addPaths, removePaths, clearPaths, notary.MinThreshold)) + if err != nil { + return err } - // We've made a change to parent. Set it to dirty - p.Dirty = true - - // We don't actually want to create the new delegation metadata yet. - // When we add a delegation, it may only be signable by a key we don't have - // (hence we are delegating signing). - - tr.keysDB.AddRole(role) - utils.RemoveUnusedKeys(p) - return nil } // DeleteDelegation removes a delegated targets role from its parent // targets object. It also deletes the delegation from the snapshot. // DeleteDelegation will only make use of the role Name field. -func (tr *Repo) DeleteDelegation(role data.Role) error { - if !role.IsDelegation() { - return data.ErrInvalidRole{Role: role.Name, Reason: "not a valid delegated role"} +func (tr *Repo) DeleteDelegation(roleName string) error { + if !data.IsDelegation(roleName) { + return data.ErrInvalidRole{Role: roleName, Reason: "not a valid delegated role"} } - // the role variable must not be used past this assignment for safety - name := role.Name - parent := path.Dir(name) + parent := path.Dir(roleName) if err := tr.VerifyCanSign(parent); err != nil { return err } // delete delegated data from Targets map and Snapshot - if they don't // exist, these are no-op - delete(tr.Targets, name) - tr.Snapshot.DeleteMeta(name) + delete(tr.Targets, roleName) + tr.Snapshot.DeleteMeta(roleName) p, ok := tr.Targets[parent] if !ok { @@ -292,7 +420,7 @@ func (tr *Repo) DeleteDelegation(role data.Role) error { return nil } - foundAt := utils.FindRoleIndex(p.Signed.Delegations.Roles, name) + foundAt := utils.FindRoleIndex(p.Signed.Delegations.Roles, roleName) if foundAt >= 0 { var roles []*data.Role @@ -311,53 +439,32 @@ func (tr *Repo) DeleteDelegation(role data.Role) error { return nil } -// InitRepo creates the base files for a repo. It inspects data.BaseRoles and -// data.ValidTypes to determine what the role names and filename should be. It -// also relies on the keysDB having already been populated with the keys and -// roles. -func (tr *Repo) InitRepo(consistent bool) error { - if err := tr.InitRoot(consistent); err != nil { - return err - } - if _, err := tr.InitTargets(data.CanonicalTargetsRole); err != nil { - return err - } - if err := tr.InitSnapshot(); err != nil { - return err - } - return tr.InitTimestamp() -} - -// InitRoot initializes an empty root file with the 4 core roles based -// on the current content of th ekey db -func (tr *Repo) InitRoot(consistent bool) error { +// InitRoot initializes an empty root file with the 4 core roles passed to the +// method, and the consistent flag. +func (tr *Repo) InitRoot(root, timestamp, snapshot, targets data.BaseRole, consistent bool) error { rootRoles := make(map[string]*data.RootRole) rootKeys := make(map[string]data.PublicKey) - for _, r := range data.BaseRoles { - role := tr.keysDB.GetRole(r) - if role == nil { - return data.ErrInvalidRole{Role: data.CanonicalRootRole, Reason: "root role not initialized in key database"} + + for _, r := range []data.BaseRole{root, timestamp, snapshot, targets} { + rootRoles[r.Name] = &data.RootRole{ + Threshold: r.Threshold, + KeyIDs: r.ListKeyIDs(), } - rootRoles[r] = &role.RootRole - for _, kid := range role.KeyIDs { - // don't need to check if GetKey returns nil, Key presence was - // checked by KeyDB when role was added. - key := tr.keysDB.GetKey(kid) - rootKeys[kid] = key + for kid, k := range r.Keys { + rootKeys[kid] = k } } - root, err := data.NewRoot(rootKeys, rootRoles, consistent) + r, err := data.NewRoot(rootKeys, rootRoles, consistent) if err != nil { return err } - tr.Root = root + tr.Root = r return nil } // InitTargets initializes an empty targets, and returns the new empty target func (tr *Repo) InitTargets(role string) (*data.SignedTargets, error) { - r := data.Role{Name: role} - if !r.IsDelegation() && role != data.CanonicalTargetsRole { + if !data.IsDelegation(role) && role != data.CanonicalTargetsRole { return nil, data.ErrInvalidRole{ Role: role, Reason: fmt.Sprintf("role is not a valid targets role name: %s", role), @@ -371,7 +478,7 @@ func (tr *Repo) InitTargets(role string) (*data.SignedTargets, error) { // InitSnapshot initializes a snapshot based on the current root and targets func (tr *Repo) InitSnapshot() error { if tr.Root == nil { - return ErrNotLoaded{role: "root"} + return ErrNotLoaded{Role: data.CanonicalRootRole} } root, err := tr.Root.ToSigned() if err != nil { @@ -379,7 +486,7 @@ func (tr *Repo) InitSnapshot() error { } if _, ok := tr.Targets[data.CanonicalTargetsRole]; !ok { - return ErrNotLoaded{role: "targets"} + return ErrNotLoaded{Role: data.CanonicalTargetsRole} } targets, err := tr.Targets[data.CanonicalTargetsRole].ToSigned() if err != nil { @@ -408,31 +515,8 @@ func (tr *Repo) InitTimestamp() error { return nil } -// SetRoot parses the Signed object into a SignedRoot object, sets -// the keys and roles in the KeyDB, and sets the Repo.Root field -// to the SignedRoot object. +// SetRoot sets the Repo.Root field to the SignedRoot object. func (tr *Repo) SetRoot(s *data.SignedRoot) error { - for _, key := range s.Signed.Keys { - logrus.Debug("Adding key ", key.ID()) - tr.keysDB.AddKey(key) - } - for roleName, role := range s.Signed.Roles { - logrus.Debugf("Adding role %s with keys %s", roleName, strings.Join(role.KeyIDs, ",")) - baseRole, err := data.NewRole( - roleName, - role.Threshold, - role.KeyIDs, - nil, - nil, - ) - if err != nil { - return err - } - err = tr.keysDB.AddRole(baseRole) - if err != nil { - return err - } - } tr.Root = s return nil } @@ -451,16 +535,9 @@ func (tr *Repo) SetSnapshot(s *data.SignedSnapshot) error { return nil } -// SetTargets parses the Signed object into a SignedTargets object, -// reads the delegated roles and keys into the KeyDB, and sets the -// SignedTargets object agaist the role in the Repo.Targets map. +// SetTargets sets the SignedTargets object agaist the role in the +// Repo.Targets map. func (tr *Repo) SetTargets(role string, s *data.SignedTargets) error { - for _, k := range s.Signed.Delegations.Keys { - tr.keysDB.AddKey(k) - } - for _, r := range s.Signed.Delegations.Roles { - tr.keysDB.AddRole(r) - } tr.Targets[role] = s return nil } @@ -479,15 +556,11 @@ func (tr Repo) TargetMeta(role, path string) *data.FileMeta { // TargetDelegations returns a slice of Roles that are valid publishers // for the target path provided. -func (tr Repo) TargetDelegations(role, path, pathHex string) []*data.Role { - if pathHex == "" { - pathDigest := sha256.Sum256([]byte(path)) - pathHex = hex.EncodeToString(pathDigest[:]) - } +func (tr Repo) TargetDelegations(role, path string) []*data.Role { var roles []*data.Role if t, ok := tr.Targets[role]; ok { for _, r := range t.Signed.Delegations.Roles { - if r.CheckPrefixes(pathHex) || r.CheckPaths(path) { + if r.CheckPaths(path) { roles = append(roles, r) } } @@ -495,50 +568,34 @@ func (tr Repo) TargetDelegations(role, path, pathHex string) []*data.Role { return roles } -// FindTarget attempts to find the target represented by the given -// path by starting at the top targets file and traversing -// appropriate delegations until the first entry is found or it -// runs out of locations to search. -// N.B. Multiple entries may exist in different delegated roles -// for the same target. Only the first one encountered is returned. -func (tr Repo) FindTarget(path string) *data.FileMeta { - pathDigest := sha256.Sum256([]byte(path)) - pathHex := hex.EncodeToString(pathDigest[:]) - - var walkTargets func(role string) *data.FileMeta - walkTargets = func(role string) *data.FileMeta { - if m := tr.TargetMeta(role, path); m != nil { - return m - } - // Depth first search of delegations based on order - // as presented in current targets file for role: - for _, r := range tr.TargetDelegations(role, path, pathHex) { - if m := walkTargets(r.Name); m != nil { - return m - } - } - return nil - } - - return walkTargets("targets") -} - // VerifyCanSign returns nil if the role exists and we have at least one // signing key for the role, false otherwise. This does not check that we have // enough signing keys to meet the threshold, since we want to support the use // case of multiple signers for a role. It returns an error if the role doesn't // exist or if there are no signing keys. func (tr *Repo) VerifyCanSign(roleName string) error { - role := tr.keysDB.GetRole(roleName) - if role == nil { + var ( + role data.BaseRole + err error + ) + // we only need the BaseRole part of a delegation because we're just + // checking KeyIDs + if data.IsDelegation(roleName) { + r, err := tr.GetDelegationRole(roleName) + if err != nil { + return err + } + role = r.BaseRole + } else { + role, err = tr.GetBaseRole(roleName) + } + if err != nil { return data.ErrInvalidRole{Role: roleName, Reason: "does not exist"} } - for _, keyID := range role.KeyIDs { - k := tr.keysDB.GetKey(keyID) - canonicalID, err := utils.CanonicalKeyID(k) + for keyID, k := range role.Keys { check := []string{keyID} - if err == nil { + if canonicalID, err := utils.CanonicalKeyID(k); err == nil { check = append(check, canonicalID) } for _, id := range check { @@ -548,45 +605,123 @@ func (tr *Repo) VerifyCanSign(roleName string) error { } } } - return signed.ErrNoKeys{KeyIDs: role.KeyIDs} + return signed.ErrNoKeys{KeyIDs: role.ListKeyIDs()} +} + +// used for walking the targets/delegations tree, potentially modifying the underlying SignedTargets for the repo +type walkVisitorFunc func(*data.SignedTargets, data.DelegationRole) interface{} + +// WalkTargets will apply the specified visitor function to iteratively walk the targets/delegation metadata tree, +// until receiving a StopWalk. The walk starts from the base "targets" role, and searches for the correct targetPath and/or rolePath +// to call the visitor function on. Any roles passed into skipRoles will be excluded from the walk, as well as roles in those subtrees +func (tr *Repo) WalkTargets(targetPath, rolePath string, visitTargets walkVisitorFunc, skipRoles ...string) error { + // Start with the base targets role, which implicitly has the "" targets path + targetsRole, err := tr.GetBaseRole(data.CanonicalTargetsRole) + if err != nil { + return err + } + // Make the targets role have the empty path, when we treat it as a delegation role + roles := []data.DelegationRole{ + { + BaseRole: targetsRole, + Paths: []string{""}, + }, + } + + for len(roles) > 0 { + role := roles[0] + roles = roles[1:] + + // Check the role metadata + signedTgt, ok := tr.Targets[role.Name] + if !ok { + // The role meta doesn't exist in the repo so continue onward + continue + } + + // We're at a prefix of the desired role subtree, so add its delegation role children and continue walking + if strings.HasPrefix(rolePath, role.Name+"/") { + roles = append(roles, signedTgt.GetValidDelegations(role)...) + continue + } + + // Determine whether to visit this role or not: + // If the paths validate against the specified targetPath and the rolePath is empty or is in the subtree + // Also check if we are choosing to skip visiting this role on this walk (see ListTargets and GetTargetByName priority) + if isValidPath(targetPath, role) && isAncestorRole(role.Name, rolePath) && !utils.StrSliceContains(skipRoles, role.Name) { + // If we had matching path or role name, visit this target and determine whether or not to keep walking + res := visitTargets(signedTgt, role) + switch typedRes := res.(type) { + case StopWalk: + // If the visitor function signalled a stop, return nil to finish the walk + return nil + case nil: + // If the visitor function signalled to continue, add this role's delegation to the walk + roles = append(roles, signedTgt.GetValidDelegations(role)...) + case error: + // Propagate any errors from the visitor + return typedRes + default: + // Return out with an error if we got a different result + return fmt.Errorf("unexpected return while walking: %v", res) + } + + } + } + return nil +} + +// helper function that returns whether the candidateChild role name is an ancestor or equal to the candidateAncestor role name +// Will return true if given an empty candidateAncestor role name +// The HasPrefix check is for determining whether the role name for candidateChild is a child (direct or further down the chain) +// of candidateAncestor, for ex: candidateAncestor targets/a and candidateChild targets/a/b/c +func isAncestorRole(candidateChild, candidateAncestor string) bool { + return candidateAncestor == "" || candidateAncestor == candidateChild || strings.HasPrefix(candidateChild, candidateAncestor+"/") +} + +// helper function that returns whether the delegation Role is valid against the given path +// Will return true if given an empty candidatePath +func isValidPath(candidatePath string, delgRole data.DelegationRole) bool { + return candidatePath == "" || delgRole.CheckPaths(candidatePath) } // AddTargets will attempt to add the given targets specifically to // the directed role. If the metadata for the role doesn't exist yet, // AddTargets will create one. func (tr *Repo) AddTargets(role string, targets data.Files) (data.Files, error) { - err := tr.VerifyCanSign(role) if err != nil { return nil, err } - // check the role's metadata - t, ok := tr.Targets[role] + // check existence of the role's metadata + _, ok := tr.Targets[role] if !ok { // the targetfile may not exist yet - if not, then create it var err error - t, err = tr.InitTargets(role) + _, err = tr.InitTargets(role) if err != nil { return nil, err } } - // VerifyCanSign already makes sure this is not nil - r := tr.keysDB.GetRole(role) - - invalid := make(data.Files) - for path, target := range targets { - pathDigest := sha256.Sum256([]byte(path)) - pathHex := hex.EncodeToString(pathDigest[:]) - if role == data.CanonicalTargetsRole || (r.CheckPaths(path) || r.CheckPrefixes(pathHex)) { - t.Signed.Targets[path] = target - } else { - invalid[path] = target + addedTargets := make(data.Files) + addTargetVisitor := func(targetPath string, targetMeta data.FileMeta) func(*data.SignedTargets, data.DelegationRole) interface{} { + return func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} { + // We've already validated the role's target path in our walk, so just modify the metadata + tgt.Signed.Targets[targetPath] = targetMeta + tgt.Dirty = true + // Also add to our new addedTargets map to keep track of every target we've added successfully + addedTargets[targetPath] = targetMeta + return StopWalk{} } } - t.Dirty = true - if len(invalid) > 0 { - return invalid, fmt.Errorf("Could not add all targets") + + // Walk the role tree while validating the target paths, and add all of our targets + for path, target := range targets { + tr.WalkTargets(path, role, addTargetVisitor(path, target)) + } + if len(addedTargets) != len(targets) { + return nil, fmt.Errorf("Could not add all targets") } return nil, nil } @@ -597,13 +732,23 @@ func (tr *Repo) RemoveTargets(role string, targets ...string) error { return err } + removeTargetVisitor := func(targetPath string) func(*data.SignedTargets, data.DelegationRole) interface{} { + return func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} { + // We've already validated the role path in our walk, so just modify the metadata + // We don't check against the target path against the valid role paths because it's + // possible we got into an invalid state and are trying to fix it + delete(tgt.Signed.Targets, targetPath) + tgt.Dirty = true + return StopWalk{} + } + } + // if the role exists but metadata does not yet, then our work is done - t, ok := tr.Targets[role] + _, ok := tr.Targets[role] if ok { for _, path := range targets { - delete(t.Signed.Targets, path) + tr.WalkTargets("", role, removeTargetVisitor(path)) } - t.Dirty = true } return nil @@ -644,12 +789,15 @@ func (tr *Repo) SignRoot(expires time.Time) (*data.Signed, error) { logrus.Debug("signing root...") tr.Root.Signed.Expires = expires tr.Root.Signed.Version++ - root := tr.keysDB.GetRole(data.CanonicalRootRole) + root, err := tr.GetBaseRole(data.CanonicalRootRole) + if err != nil { + return nil, err + } signed, err := tr.Root.ToSigned() if err != nil { return nil, err } - signed, err = tr.sign(signed, *root) + signed, err = tr.sign(signed, root) if err != nil { return nil, err } @@ -673,8 +821,22 @@ func (tr *Repo) SignTargets(role string, expires time.Time) (*data.Signed, error logrus.Debug("errored getting targets data.Signed object") return nil, err } - targets := tr.keysDB.GetRole(role) - signed, err = tr.sign(signed, *targets) + + var targets data.BaseRole + if role == data.CanonicalTargetsRole { + targets, err = tr.GetBaseRole(role) + } else { + tr, err := tr.GetDelegationRole(role) + if err != nil { + return nil, err + } + targets = tr.BaseRole + } + if err != nil { + return nil, err + } + + signed, err = tr.sign(signed, targets) if err != nil { logrus.Debug("errored signing ", role) return nil, err @@ -712,8 +874,11 @@ func (tr *Repo) SignSnapshot(expires time.Time) (*data.Signed, error) { if err != nil { return nil, err } - snapshot := tr.keysDB.GetRole(data.CanonicalSnapshotRole) - signed, err = tr.sign(signed, *snapshot) + snapshot, err := tr.GetBaseRole(data.CanonicalSnapshotRole) + if err != nil { + return nil, err + } + signed, err = tr.sign(signed, snapshot) if err != nil { return nil, err } @@ -738,8 +903,11 @@ func (tr *Repo) SignTimestamp(expires time.Time) (*data.Signed, error) { if err != nil { return nil, err } - timestamp := tr.keysDB.GetRole(data.CanonicalTimestampRole) - signed, err = tr.sign(signed, *timestamp) + timestamp, err := tr.GetBaseRole(data.CanonicalTimestampRole) + if err != nil { + return nil, err + } + signed, err = tr.sign(signed, timestamp) if err != nil { return nil, err } @@ -748,17 +916,10 @@ func (tr *Repo) SignTimestamp(expires time.Time) (*data.Signed, error) { return signed, nil } -func (tr Repo) sign(signedData *data.Signed, role data.Role) (*data.Signed, error) { - ks := make([]data.PublicKey, 0, len(role.KeyIDs)) - for _, kid := range role.KeyIDs { - k := tr.keysDB.GetKey(kid) - if k == nil { - continue - } - ks = append(ks, k) - } +func (tr Repo) sign(signedData *data.Signed, role data.BaseRole) (*data.Signed, error) { + ks := role.ListKeys() if len(ks) < 1 { - return nil, keys.ErrInvalidKey + return nil, signed.ErrNoKeys{} } err := signed.Sign(tr.cryptoService, signedData, ks...) if err != nil { diff --git a/vendor/src/github.com/docker/notary/tuf/utils/utils.go b/vendor/src/github.com/docker/notary/tuf/utils/utils.go index c09019c2e5..8190c5eccd 100644 --- a/vendor/src/github.com/docker/notary/tuf/utils/utils.go +++ b/vendor/src/github.com/docker/notary/tuf/utils/utils.go @@ -5,6 +5,7 @@ import ( "crypto/sha256" "crypto/sha512" "crypto/tls" + "encoding/hex" "fmt" "io" "net/http" @@ -61,6 +62,17 @@ func StrSliceContains(ss []string, s string) bool { return false } +// StrSliceRemove removes the the given string from the slice, returning a new slice +func StrSliceRemove(ss []string, s string) []string { + res := []string{} + for _, v := range ss { + if v != s { + res = append(res, v) + } + } + return res +} + // StrSliceContainsI checks if the given string appears in the slice // in a case insensitive manner func StrSliceContainsI(ss []string, s string) bool { @@ -146,3 +158,14 @@ func FindRoleIndex(rs []*data.Role, name string) int { } return -1 } + +// ConsistentName generates the appropriate HTTP URL path for the role, +// based on whether the repo is marked as consistent. The RemoteStore +// is responsible for adding file extensions. +func ConsistentName(role string, hashSha256 []byte) string { + if len(hashSha256) > 0 { + hash := hex.EncodeToString(hashSha256) + return fmt.Sprintf("%s.%s", role, hash) + } + return role +}