Browse code

Vendor in notary v0.2.0

Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>

Riyaz Faizullabhoy authored on 2016/02/26 06:40:00
Showing 45 changed files
... ...
@@ -169,7 +169,7 @@ RUN set -x \
169 169
 	&& rm -rf "$GOPATH"
170 170
 
171 171
 # Install notary server
172
-ENV NOTARY_VERSION docker-v1.10-5
172
+ENV NOTARY_VERSION v0.2.0
173 173
 RUN set -x \
174 174
 	&& export GOPATH="$(mktemp -d)" \
175 175
 	&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
... ...
@@ -119,7 +119,7 @@ RUN set -x \
119 119
 	&& rm -rf "$GOPATH"
120 120
 
121 121
 # Install notary server
122
-ENV NOTARY_VERSION docker-v1.10-5
122
+ENV NOTARY_VERSION v0.2.0
123 123
 RUN set -x \
124 124
 	&& export GOPATH="$(mktemp -d)" \
125 125
 	&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
... ...
@@ -135,7 +135,7 @@ RUN set -x \
135 135
 	&& rm -rf "$GOPATH"
136 136
 
137 137
 # Install notary server
138
-ENV NOTARY_VERSION docker-v1.10-5
138
+ENV NOTARY_VERSION v0.2.0
139 139
 RUN set -x \
140 140
 	&& export GOPATH="$(mktemp -d)" \
141 141
 	&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
... ...
@@ -127,16 +127,16 @@ RUN set -x \
127 127
 		go build -o /usr/local/bin/registry-v2-schema1 github.com/docker/distribution/cmd/registry \
128 128
 	&& rm -rf "$GOPATH"
129 129
 
130
-# TODO update this when we upgrade to Go 1.5.1+
130
+
131 131
 # Install notary server
132
-#ENV NOTARY_VERSION docker-v1.10-5
133
-#RUN set -x \
134
-#	&& export GOPATH="$(mktemp -d)" \
135
-#	&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
136
-#	&& (cd "$GOPATH/src/github.com/docker/notary" && git checkout -q "$NOTARY_VERSION") \
137
-#	&& GOPATH="$GOPATH/src/github.com/docker/notary/Godeps/_workspace:$GOPATH" \
138
-#		go build -o /usr/local/bin/notary-server github.com/docker/notary/cmd/notary-server \
139
-#	&& rm -rf "$GOPATH"
132
+ENV NOTARY_VERSION v0.2.0
133
+RUN set -x \
134
+	&& export GOPATH="$(mktemp -d)" \
135
+	&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
136
+	&& (cd "$GOPATH/src/github.com/docker/notary" && git checkout -q "$NOTARY_VERSION") \
137
+	&& GOPATH="$GOPATH/src/github.com/docker/notary/Godeps/_workspace:$GOPATH" \
138
+		go build -o /usr/local/bin/notary-server github.com/docker/notary/cmd/notary-server \
139
+	&& rm -rf "$GOPATH"
140 140
 
141 141
 # Get the "docker-py" source so we can run their integration tests
142 142
 ENV DOCKER_PY_COMMIT e2878cbcc3a7eef99917adc1be252800b0e41ece
... ...
@@ -108,7 +108,7 @@ RUN set -x \
108 108
 	&& rm -rf "$GOPATH"
109 109
 
110 110
 # Install notary server
111
-ENV NOTARY_VERSION docker-v1.10-5
111
+ENV NOTARY_VERSION v0.2.0
112 112
 RUN set -x \
113 113
 	&& export GOPATH="$(mktemp -d)" \
114 114
 	&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
... ...
@@ -52,7 +52,7 @@ clone git github.com/docker/distribution 7b66c50bb7e0e4b3b83f8fd134a9f6ea4be08b5
52 52
 clone git github.com/vbatts/tar-split v0.9.11
53 53
 
54 54
 # get desired notary commit, might also need to be updated in Dockerfile
55
-clone git github.com/docker/notary docker-v1.10-5
55
+clone git github.com/docker/notary v0.2.0
56 56
 
57 57
 clone git google.golang.org/grpc 174192fc93efcb188fc8f46ca447f0da606b6885 https://github.com/grpc/grpc-go.git
58 58
 clone git github.com/miekg/pkcs11 80f102b5cac759de406949c47f0928b99bd64cdf
... ...
@@ -365,7 +365,7 @@ func (s *DockerTrustSuite) TestCreateWhenCertExpired(c *check.C) {
365 365
 
366 366
 func (s *DockerTrustSuite) TestTrustedCreateFromBadTrustServer(c *check.C) {
367 367
 	repoName := fmt.Sprintf("%v/dockerclievilcreate/trusted:latest", privateRegistryURL)
368
-	evilLocalConfigDir, err := ioutil.TempDir("", "evil-local-config-dir")
368
+	evilLocalConfigDir, err := ioutil.TempDir("", "evilcreate-local-config-dir")
369 369
 	c.Assert(err, check.IsNil)
370 370
 
371 371
 	// tag the image and upload it to the private registry
... ...
@@ -404,12 +404,16 @@ func (s *DockerTrustSuite) TestTrustedCreateFromBadTrustServer(c *check.C) {
404 404
 	c.Assert(err, check.IsNil)
405 405
 	c.Assert(string(out), checker.Contains, "Signing and pushing trust metadata", check.Commentf("Missing expected output on trusted push:\n%s", out))
406 406
 
407
-	// Now, try creating with the original client from this new trust server. This should fail.
407
+	// Now, try creating with the original client from this new trust server. This should fallback to our cached timestamp and metadata.
408 408
 	createCmd = exec.Command(dockerBinary, "create", repoName)
409 409
 	s.trustedCmd(createCmd)
410 410
 	out, _, err = runCommandWithOutput(createCmd)
411
-	c.Assert(err, check.Not(check.IsNil))
412
-	c.Assert(string(out), checker.Contains, "valid signatures did not meet threshold", check.Commentf("Missing expected output on trusted push:\n%s", out))
411
+	if err != nil {
412
+		c.Fatalf("Error falling back to cached trust data: %s\n%s", err, out)
413
+	}
414
+	if !strings.Contains(string(out), "Error while downloading remote metadata, using cached timestamp") {
415
+		c.Fatalf("Missing expected output on trusted create:\n%s", out)
416
+	}
413 417
 
414 418
 }
415 419
 
... ...
@@ -135,13 +135,16 @@ func (s *DockerTrustSuite) TestTrustedPullFromBadTrustServer(c *check.C) {
135 135
 	c.Assert(err, check.IsNil, check.Commentf(out))
136 136
 	c.Assert(string(out), checker.Contains, "Signing and pushing trust metadata", check.Commentf(out))
137 137
 
138
-	// Now, try pulling with the original client from this new trust server. This should fail.
138
+	// Now, try pulling with the original client from this new trust server. This should fall back to cached metadata.
139 139
 	pullCmd = exec.Command(dockerBinary, "pull", repoName)
140 140
 	s.trustedCmd(pullCmd)
141 141
 	out, _, err = runCommandWithOutput(pullCmd)
142
-
143
-	c.Assert(err, check.NotNil, check.Commentf(out))
144
-	c.Assert(string(out), checker.Contains, "valid signatures did not meet threshold", check.Commentf(out))
142
+	if err != nil {
143
+		c.Fatalf("Error falling back to cached trust data: %s\n%s", err, out)
144
+	}
145
+	if !strings.Contains(string(out), "Error while downloading remote metadata, using cached timestamp") {
146
+		c.Fatalf("Missing expected output on trusted pull:\n%s", out)
147
+	}
145 148
 }
146 149
 
147 150
 func (s *DockerTrustSuite) TestTrustedPullWithExpiredSnapshot(c *check.C) {
... ...
@@ -3260,7 +3260,7 @@ func (s *DockerTrustSuite) TestTrustedRunFromBadTrustServer(c *check.C) {
3260 3260
 	// Windows does not support this functionality
3261 3261
 	testRequires(c, DaemonIsLinux)
3262 3262
 	repoName := fmt.Sprintf("%v/dockerclievilrun/trusted:latest", privateRegistryURL)
3263
-	evilLocalConfigDir, err := ioutil.TempDir("", "evil-local-config-dir")
3263
+	evilLocalConfigDir, err := ioutil.TempDir("", "evilrun-local-config-dir")
3264 3264
 	if err != nil {
3265 3265
 		c.Fatalf("Failed to create local temp dir")
3266 3266
 	}
... ...
@@ -3316,15 +3316,15 @@ func (s *DockerTrustSuite) TestTrustedRunFromBadTrustServer(c *check.C) {
3316 3316
 		c.Fatalf("Missing expected output on trusted push:\n%s", out)
3317 3317
 	}
3318 3318
 
3319
-	// Now, try running with the original client from this new trust server. This should fail.
3319
+	// Now, try running with the original client from this new trust server. This should fallback to our cached timestamp and metadata.
3320 3320
 	runCmd = exec.Command(dockerBinary, "run", repoName)
3321 3321
 	s.trustedCmd(runCmd)
3322 3322
 	out, _, err = runCommandWithOutput(runCmd)
3323
-	if err == nil {
3324
-		c.Fatalf("Expected to fail on this run due to different remote data: %s\n%s", err, out)
3325
-	}
3326 3323
 
3327
-	if !strings.Contains(string(out), "valid signatures did not meet threshold") {
3324
+	if err != nil {
3325
+		c.Fatalf("Error falling back to cached trust data: %s\n%s", err, out)
3326
+	}
3327
+	if !strings.Contains(string(out), "Error while downloading remote metadata, using cached timestamp") {
3328 3328
 		c.Fatalf("Missing expected output on trusted push:\n%s", out)
3329 3329
 	}
3330 3330
 }
... ...
@@ -237,7 +237,7 @@ func (s *DockerTrustSuite) setupDelegations(c *check.C, repoName, pwd string) {
237 237
 	if err != nil {
238 238
 		c.Fatalf("Error creating delegation key: %s\n", err)
239 239
 	}
240
-	err = nRepo.AddDelegation("targets/releases", 1, []data.PublicKey{delgKey}, []string{""})
240
+	err = nRepo.AddDelegation("targets/releases", []data.PublicKey{delgKey}, []string{""})
241 241
 	if err != nil {
242 242
 		c.Fatalf("Error creating delegation: %s\n", err)
243 243
 	}
... ...
@@ -42,14 +42,6 @@ GO_VERSION = $(shell go version | awk '{print $$3}')
42 42
 .DELETE_ON_ERROR: cover
43 43
 .DEFAULT: default
44 44
 
45
-go_version:
46
-ifeq (,$(findstring go1.5.,$(GO_VERSION)))
47
-	$(error Requires go version 1.5.x - found $(GO_VERSION))
48
-else
49
-	@echo
50
-endif
51
-
52
-
53 45
 all: AUTHORS clean fmt vet fmt lint build test binaries
54 46
 
55 47
 AUTHORS: .git/HEAD
... ...
@@ -71,7 +63,23 @@ ${PREFIX}/bin/notary-signer: NOTARY_VERSION $(shell find . -type f -name '*.go')
71 71
 	@echo "+ $@"
72 72
 	@godep go build -tags ${NOTARY_BUILDTAGS} -o $@ ${GO_LDFLAGS} ./cmd/notary-signer
73 73
 
74
-vet: go_version
74
+ifeq ($(shell uname -s),Darwin)
75
+${PREFIX}/bin/static/notary-server:
76
+	@echo "notary-server: static builds not supported on OS X"
77
+
78
+${PREFIX}/bin/static/notary-signer:
79
+	@echo "notary-signer: static builds not supported on OS X"
80
+else
81
+${PREFIX}/bin/static/notary-server: NOTARY_VERSION $(shell find . -type f -name '*.go')
82
+	@echo "+ $@"
83
+	@godep go build -tags ${NOTARY_BUILDTAGS} -o $@ ${GO_LDFLAGS_STATIC} ./cmd/notary-server
84
+
85
+${PREFIX}/bin/static/notary-signer: NOTARY_VERSION $(shell find . -type f -name '*.go')
86
+	@echo "+ $@"
87
+	@godep go build -tags ${NOTARY_BUILDTAGS} -o $@ ${GO_LDFLAGS_STATIC} ./cmd/notary-signer
88
+endif
89
+
90
+vet: 
75 91
 	@echo "+ $@"
76 92
 ifeq ($(shell uname -s), Darwin)
77 93
 	@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:
88 88
 	@echo "+ $@"
89 89
 	@test -z "$$(golint ./... | grep -v .pb. | grep -v Godeps/_workspace/src/ | tee /dev/stderr)"
90 90
 
91
-build: go_version
91
+# Requires that the following:
92
+# go get -u github.com/client9/misspell/cmd/misspell
93
+#
94
+# be run first
95
+
96
+# misspell target, don't include Godeps, binaries, python tests, or git files
97
+misspell:
98
+	@echo "+ $@"
99
+	@test -z "$$(find . -name '*' | grep -v Godeps/_workspace/src/ | grep -v bin/ | grep -v misc/ | grep -v .git/ | xargs misspell | tee /dev/stderr)"
100
+
101
+build:
92 102
 	@echo "+ $@"
93 103
 	@go build -tags "${NOTARY_BUILDTAGS}" -v ${GO_LDFLAGS} ./...
94 104
 
95 105
 # When running `go test ./...`, it runs all the suites in parallel, which causes
96 106
 # problems when running with a yubikey
97 107
 test: TESTOPTS =
98
-test: go_version
108
+test: 
99 109
 	@echo Note: when testing with a yubikey plugged in, make sure to include 'TESTOPTS="-p 1"'
100 110
 	@echo "+ $@ $(TESTOPTS)"
101 111
 	@echo
... ...
@@ -121,7 +139,7 @@ define gocover
121 121
 $(GO_EXC) test $(OPTS) $(TESTOPTS) -covermode="$(COVERMODE)" -coverprofile="$(COVERDIR)/$(subst /,-,$(1)).$(subst $(_space),.,$(NOTARY_BUILDTAGS)).coverage.txt" "$(1)" || exit 1;
122 122
 endef
123 123
 
124
-gen-cover: go_version
124
+gen-cover: 
125 125
 	@mkdir -p "$(COVERDIR)"
126 126
 	$(foreach PKG,$(PKGS),$(call gocover,$(PKG)))
127 127
 	rm -f "$(COVERDIR)"/*testutils*.coverage.txt
... ...
@@ -150,7 +168,10 @@ covmerge:
150 150
 clean-protos:
151 151
 	@rm proto/*.pb.go
152 152
 
153
-binaries: go_version ${PREFIX}/bin/notary-server ${PREFIX}/bin/notary ${PREFIX}/bin/notary-signer
153
+binaries: ${PREFIX}/bin/notary-server ${PREFIX}/bin/notary ${PREFIX}/bin/notary-signer
154
+	@echo "+ $@"
155
+
156
+static: ${PREFIX}/bin/static/notary-server ${PREFIX}/bin/static/notary-signer
154 157
 	@echo "+ $@"
155 158
 
156 159
 define template
... ...
@@ -158,7 +179,7 @@ mkdir -p ${PREFIX}/cross/$(1)/$(2);
158 158
 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;
159 159
 endef
160 160
 
161
-cross: go_version
161
+cross: 
162 162
 	$(foreach GOARCH,$(GOARCHS),$(foreach GOOS,$(GOOSES),$(call template,$(GOOS),$(GOARCH))))
163 163
 
164 164
 
... ...
@@ -1,4 +1,5 @@
1
-# Notary [![Circle CI](https://circleci.com/gh/docker/notary/tree/master.svg?style=shield)](https://circleci.com/gh/docker/notary/tree/master)
1
+# Notary 
2
+[![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)
2 3
 
3 4
 The Notary project comprises a [server](cmd/notary-server) and a [client](cmd/notary) for running and interacting
4 5
 with trusted collections.
... ...
@@ -6,7 +6,7 @@ machine:
6 6
 
7 7
   post:
8 8
   # Install many go versions
9
-    - gvm install go1.5.1 -B --name=stable
9
+    - gvm install go1.6 -B --name=stable
10 10
 
11 11
   environment:
12 12
   # Convenient shortcuts to "common" locations
... ...
@@ -37,10 +37,11 @@ dependencies:
37 37
         pwd: $BASE_STABLE
38 38
 
39 39
   post:
40
-  # For the stable go version, additionally install linting tools
40
+  # For the stable go version, additionally install linting and misspell tools
41 41
     - >
42 42
       gvm use stable &&
43
-      go get github.com/golang/lint/golint
43
+      go get github.com/golang/lint/golint &&
44
+      go get -u github.com/client9/misspell/cmd/misspell
44 45
 test:
45 46
   pre:
46 47
   # Output the go versions we are going to test
... ...
@@ -62,6 +63,10 @@ test:
62 62
     - gvm use stable && make lint:
63 63
         pwd: $BASE_STABLE
64 64
 
65
+  # MISSPELL
66
+    - gvm use stable && make misspell:
67
+        pwd: $BASE_STABLE
68
+
65 69
   override:
66 70
   # Test stable, and report
67 71
   # hacking this to be parallel
... ...
@@ -17,7 +17,7 @@ const (
17 17
 // Types for TufChanges are namespaced by the Role they
18 18
 // are relevant for. The Root and Targets roles are the
19 19
 // only ones for which user action can cause a change, as
20
-// all changes in Snapshot and Timestamp are programatically
20
+// all changes in Snapshot and Timestamp are programmatically
21 21
 // generated base on Root and Targets changes.
22 22
 const (
23 23
 	TypeRootRole          = "role"
... ...
@@ -82,14 +82,13 @@ func (c TufChange) Content() []byte {
82 82
 // this includes creating a delegations. This format is used to avoid
83 83
 // unexpected race conditions between humans modifying the same delegation
84 84
 type TufDelegation struct {
85
-	NewName                string       `json:"new_name,omitempty"`
86
-	NewThreshold           int          `json:"threshold, omitempty"`
87
-	AddKeys                data.KeyList `json:"add_keys, omitempty"`
88
-	RemoveKeys             []string     `json:"remove_keys,omitempty"`
89
-	AddPaths               []string     `json:"add_paths,omitempty"`
90
-	RemovePaths            []string     `json:"remove_paths,omitempty"`
91
-	AddPathHashPrefixes    []string     `json:"add_prefixes,omitempty"`
92
-	RemovePathHashPrefixes []string     `json:"remove_prefixes,omitempty"`
85
+	NewName       string       `json:"new_name,omitempty"`
86
+	NewThreshold  int          `json:"threshold, omitempty"`
87
+	AddKeys       data.KeyList `json:"add_keys, omitempty"`
88
+	RemoveKeys    []string     `json:"remove_keys,omitempty"`
89
+	AddPaths      []string     `json:"add_paths,omitempty"`
90
+	RemovePaths   []string     `json:"remove_paths,omitempty"`
91
+	ClearAllPaths bool         `json:"clear_paths,omitempty"`
93 92
 }
94 93
 
95 94
 // ToNewRole creates a fresh role object from the TufDelegation data
... ...
@@ -98,5 +97,5 @@ func (td TufDelegation) ToNewRole(scope string) (*data.Role, error) {
98 98
 	if td.NewName != "" {
99 99
 		name = td.NewName
100 100
 	}
101
-	return data.NewRole(name, td.NewThreshold, td.AddKeys.IDs(), td.AddPaths, td.AddPathHashPrefixes)
101
+	return data.NewRole(name, td.NewThreshold, td.AddKeys.IDs(), td.AddPaths)
102 102
 }
... ...
@@ -9,6 +9,7 @@ import (
9 9
 	"net/url"
10 10
 	"os"
11 11
 	"path/filepath"
12
+	"strings"
12 13
 	"time"
13 14
 
14 15
 	"github.com/Sirupsen/logrus"
... ...
@@ -20,23 +21,13 @@ import (
20 20
 	"github.com/docker/notary/tuf"
21 21
 	tufclient "github.com/docker/notary/tuf/client"
22 22
 	"github.com/docker/notary/tuf/data"
23
-	"github.com/docker/notary/tuf/keys"
24 23
 	"github.com/docker/notary/tuf/signed"
25 24
 	"github.com/docker/notary/tuf/store"
26
-)
27
-
28
-const (
29
-	maxSize = 5 << 20
25
+	"github.com/docker/notary/tuf/utils"
30 26
 )
31 27
 
32 28
 func init() {
33
-	data.SetDefaultExpiryTimes(
34
-		map[string]int{
35
-			"root":     3650,
36
-			"targets":  1095,
37
-			"snapshot": 1095,
38
-		},
39
-	)
29
+	data.SetDefaultExpiryTimes(notary.NotaryDefaultExpiries)
40 30
 }
41 31
 
42 32
 // ErrRepoNotInitialized is returned when trying to publish an uninitialized
... ...
@@ -118,7 +109,6 @@ func repositoryFromKeystores(baseDir, gun, baseURL string, rt http.RoundTripper,
118 118
 		nRepo.tufRepoPath,
119 119
 		"metadata",
120 120
 		"json",
121
-		"",
122 121
 	)
123 122
 	if err != nil {
124 123
 		return nil, err
... ...
@@ -218,11 +208,16 @@ func (r *NotaryRepository) Initialize(rootKeyID string, serverManagedRoles ...st
218 218
 		return fmt.Errorf("invalid format for root key: %s", privKey.Algorithm())
219 219
 	}
220 220
 
221
-	kdb := keys.NewDB()
222
-	err = addKeyForRole(kdb, data.CanonicalRootRole, rootKey)
223
-	if err != nil {
224
-		return err
225
-	}
221
+	var (
222
+		rootRole = data.NewBaseRole(
223
+			data.CanonicalRootRole,
224
+			notary.MinThreshold,
225
+			rootKey,
226
+		)
227
+		timestampRole data.BaseRole
228
+		snapshotRole  data.BaseRole
229
+		targetsRole   data.BaseRole
230
+	)
226 231
 
227 232
 	// we want to create all the local keys first so we don't have to
228 233
 	// make unnecessary network calls
... ...
@@ -232,8 +227,19 @@ func (r *NotaryRepository) Initialize(rootKeyID string, serverManagedRoles ...st
232 232
 		if err != nil {
233 233
 			return err
234 234
 		}
235
-		if err := addKeyForRole(kdb, role, key); err != nil {
236
-			return err
235
+		switch role {
236
+		case data.CanonicalSnapshotRole:
237
+			snapshotRole = data.NewBaseRole(
238
+				role,
239
+				notary.MinThreshold,
240
+				key,
241
+			)
242
+		case data.CanonicalTargetsRole:
243
+			targetsRole = data.NewBaseRole(
244
+				role,
245
+				notary.MinThreshold,
246
+				key,
247
+			)
237 248
 		}
238 249
 	}
239 250
 	for _, role := range remotelyManagedKeys {
... ...
@@ -244,14 +250,31 @@ func (r *NotaryRepository) Initialize(rootKeyID string, serverManagedRoles ...st
244 244
 		}
245 245
 		logrus.Debugf("got remote %s %s key with keyID: %s",
246 246
 			role, key.Algorithm(), key.ID())
247
-		if err := addKeyForRole(kdb, role, key); err != nil {
248
-			return err
247
+		switch role {
248
+		case data.CanonicalSnapshotRole:
249
+			snapshotRole = data.NewBaseRole(
250
+				role,
251
+				notary.MinThreshold,
252
+				key,
253
+			)
254
+		case data.CanonicalTimestampRole:
255
+			timestampRole = data.NewBaseRole(
256
+				role,
257
+				notary.MinThreshold,
258
+				key,
259
+			)
249 260
 		}
250 261
 	}
251 262
 
252
-	r.tufRepo = tuf.NewRepo(kdb, r.CryptoService)
263
+	r.tufRepo = tuf.NewRepo(r.CryptoService)
253 264
 
254
-	err = r.tufRepo.InitRoot(false)
265
+	err = r.tufRepo.InitRoot(
266
+		rootRole,
267
+		timestampRole,
268
+		snapshotRole,
269
+		targetsRole,
270
+		false,
271
+	)
255 272
 	if err != nil {
256 273
 		logrus.Debug("Error on InitRoot: ", err.Error())
257 274
 		return err
... ...
@@ -305,96 +328,6 @@ func addChange(cl *changelist.FileChangelist, c changelist.Change, roles ...stri
305 305
 	return nil
306 306
 }
307 307
 
308
-// AddDelegation creates a new changelist entry to add a delegation to the repository
309
-// when the changelist gets applied at publish time.  This does not do any validation
310
-// other than checking the name of the delegation to add - all that will happen
311
-// at publish time.
312
-func (r *NotaryRepository) AddDelegation(name string, threshold int,
313
-	delegationKeys []data.PublicKey, paths []string) error {
314
-
315
-	if !data.IsDelegation(name) {
316
-		return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"}
317
-	}
318
-
319
-	cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
320
-	if err != nil {
321
-		return err
322
-	}
323
-	defer cl.Close()
324
-
325
-	logrus.Debugf(`Adding delegation "%s" with threshold %d, and %d keys\n`,
326
-		name, threshold, len(delegationKeys))
327
-
328
-	tdJSON, err := json.Marshal(&changelist.TufDelegation{
329
-		NewThreshold: threshold,
330
-		AddKeys:      data.KeyList(delegationKeys),
331
-		AddPaths:     paths,
332
-	})
333
-	if err != nil {
334
-		return err
335
-	}
336
-
337
-	template := changelist.NewTufChange(
338
-		changelist.ActionCreate,
339
-		name,
340
-		changelist.TypeTargetsDelegation,
341
-		"", // no path
342
-		tdJSON,
343
-	)
344
-
345
-	return addChange(cl, template, name)
346
-}
347
-
348
-// RemoveDelegation creates a new changelist entry to remove a delegation from
349
-// the repository when the changelist gets applied at publish time.
350
-// This does not validate that the delegation exists, since one might exist
351
-// after applying all changes.
352
-func (r *NotaryRepository) RemoveDelegation(name string, keyIDs, paths []string, removeAll bool) error {
353
-
354
-	if !data.IsDelegation(name) {
355
-		return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"}
356
-	}
357
-
358
-	cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
359
-	if err != nil {
360
-		return err
361
-	}
362
-	defer cl.Close()
363
-
364
-	logrus.Debugf(`Removing delegation "%s"\n`, name)
365
-	var template *changelist.TufChange
366
-
367
-	// We use the Delete action only for force removal, Update is used for removing individual keys and paths
368
-	if removeAll {
369
-		template = changelist.NewTufChange(
370
-			changelist.ActionDelete,
371
-			name,
372
-			changelist.TypeTargetsDelegation,
373
-			"",  // no path
374
-			nil, // deleting role, no data needed
375
-		)
376
-
377
-	} else {
378
-		tdJSON, err := json.Marshal(&changelist.TufDelegation{
379
-			RemoveKeys:  keyIDs,
380
-			RemovePaths: paths,
381
-		})
382
-		if err != nil {
383
-			return err
384
-		}
385
-
386
-		template = changelist.NewTufChange(
387
-			changelist.ActionUpdate,
388
-			name,
389
-			changelist.TypeTargetsDelegation,
390
-			"", // no path
391
-			tdJSON,
392
-		)
393
-	}
394
-
395
-	return addChange(cl, template, name)
396
-}
397
-
398 308
 // AddTarget creates new changelist entries to add a target to the given roles
399 309
 // in the repository when the changelist gets applied at publish time.
400 310
 // If roles are unspecified, the default role is "targets".
... ...
@@ -452,10 +385,24 @@ func (r *NotaryRepository) ListTargets(roles ...string) ([]*TargetWithRole, erro
452 452
 	}
453 453
 	targets := make(map[string]*TargetWithRole)
454 454
 	for _, role := range roles {
455
-		// we don't need to do anything special with removing role from
456
-		// roles because listSubtree always processes role and only excludes
457
-		// descendant delegations that appear in roles.
458
-		r.listSubtree(targets, role, roles...)
455
+		// Define an array of roles to skip for this walk (see IMPORTANT comment above)
456
+		skipRoles := utils.StrSliceRemove(roles, role)
457
+
458
+		// Define a visitor function to populate the targets map in priority order
459
+		listVisitorFunc := func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} {
460
+			// We found targets so we should try to add them to our targets map
461
+			for targetName, targetMeta := range tgt.Signed.Targets {
462
+				// Follow the priority by not overriding previously set targets
463
+				// and check that this path is valid with this role
464
+				if _, ok := targets[targetName]; ok || !validRole.CheckPaths(targetName) {
465
+					continue
466
+				}
467
+				targets[targetName] =
468
+					&TargetWithRole{Target: Target{Name: targetName, Hashes: targetMeta.Hashes, Length: targetMeta.Length}, Role: validRole.Name}
469
+			}
470
+			return nil
471
+		}
472
+		r.tufRepo.WalkTargets("", role, listVisitorFunc, skipRoles...)
459 473
 	}
460 474
 
461 475
 	var targetList []*TargetWithRole
... ...
@@ -466,34 +413,6 @@ func (r *NotaryRepository) ListTargets(roles ...string) ([]*TargetWithRole, erro
466 466
 	return targetList, nil
467 467
 }
468 468
 
469
-func (r *NotaryRepository) listSubtree(targets map[string]*TargetWithRole, role string, exclude ...string) {
470
-	excl := make(map[string]bool)
471
-	for _, r := range exclude {
472
-		excl[r] = true
473
-	}
474
-	roles := []string{role}
475
-	for len(roles) > 0 {
476
-		role = roles[0]
477
-		roles = roles[1:]
478
-		tgts, ok := r.tufRepo.Targets[role]
479
-		if !ok {
480
-			// not every role has to exist
481
-			continue
482
-		}
483
-		for name, meta := range tgts.Signed.Targets {
484
-			if _, ok := targets[name]; !ok {
485
-				targets[name] = &TargetWithRole{
486
-					Target: Target{Name: name, Hashes: meta.Hashes, Length: meta.Length}, Role: role}
487
-			}
488
-		}
489
-		for _, d := range tgts.Signed.Delegations.Roles {
490
-			if !excl[d.Name] {
491
-				roles = append(roles, d.Name)
492
-			}
493
-		}
494
-	}
495
-}
496
-
497 469
 // GetTargetByName returns a target given a name. If no roles are passed
498 470
 // it uses the targets role and does a search of the entire delegation
499 471
 // 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
502 502
 // will be returned
503 503
 // See the IMPORTANT section on ListTargets above. Those roles also apply here.
504 504
 func (r *NotaryRepository) GetTargetByName(name string, roles ...string) (*TargetWithRole, error) {
505
-	c, err := r.Update(false)
505
+	_, err := r.Update(false)
506 506
 	if err != nil {
507 507
 		return nil, err
508 508
 	}
... ...
@@ -510,11 +429,30 @@ func (r *NotaryRepository) GetTargetByName(name string, roles ...string) (*Targe
510 510
 	if len(roles) == 0 {
511 511
 		roles = append(roles, data.CanonicalTargetsRole)
512 512
 	}
513
+	var resultMeta data.FileMeta
514
+	var resultRoleName string
515
+	var foundTarget bool
513 516
 	for _, role := range roles {
514
-		meta, foundRole := c.TargetMeta(role, name, roles...)
515
-		if meta != nil {
516
-			return &TargetWithRole{
517
-				Target: Target{Name: name, Hashes: meta.Hashes, Length: meta.Length}, Role: foundRole}, nil
517
+		// Define an array of roles to skip for this walk (see IMPORTANT comment above)
518
+		skipRoles := utils.StrSliceRemove(roles, role)
519
+
520
+		// Define a visitor function to find the specified target
521
+		getTargetVisitorFunc := func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} {
522
+			if tgt == nil {
523
+				return nil
524
+			}
525
+			// We found the target and validated path compatibility in our walk,
526
+			// so we should stop our walk and set the resultMeta and resultRoleName variables
527
+			if resultMeta, foundTarget = tgt.Signed.Targets[name]; foundTarget {
528
+				resultRoleName = validRole.Name
529
+				return tuf.StopWalk{}
530
+			}
531
+			return nil
532
+		}
533
+		err = r.tufRepo.WalkTargets(name, role, getTargetVisitorFunc, skipRoles...)
534
+		// Check that we didn't error, and that we assigned to our target
535
+		if err == nil && foundTarget {
536
+			return &TargetWithRole{Target: Target{Name: name, Hashes: resultMeta.Hashes, Length: resultMeta.Length}, Role: resultRoleName}, nil
518 537
 		}
519 538
 	}
520 539
 	return nil, fmt.Errorf("No trust data for %s", name)
... ...
@@ -532,45 +470,6 @@ func (r *NotaryRepository) GetChangelist() (changelist.Changelist, error) {
532 532
 	return cl, nil
533 533
 }
534 534
 
535
-// GetDelegationRoles returns the keys and roles of the repository's delegations
536
-func (r *NotaryRepository) GetDelegationRoles() ([]*data.Role, error) {
537
-	// Update state of the repo to latest
538
-	if _, err := r.Update(false); err != nil {
539
-		return nil, err
540
-	}
541
-
542
-	// All top level delegations (ex: targets/level1) are stored exclusively in targets.json
543
-	targets, ok := r.tufRepo.Targets[data.CanonicalTargetsRole]
544
-	if !ok {
545
-		return nil, store.ErrMetaNotFound{Resource: data.CanonicalTargetsRole}
546
-	}
547
-
548
-	allDelegations := targets.Signed.Delegations.Roles
549
-
550
-	// make a copy for traversing nested delegations
551
-	delegationsList := make([]*data.Role, len(allDelegations))
552
-	copy(delegationsList, allDelegations)
553
-
554
-	// Now traverse to lower level delegations (ex: targets/level1/level2)
555
-	for len(delegationsList) > 0 {
556
-		// Pop off first delegation to traverse
557
-		delegation := delegationsList[0]
558
-		delegationsList = delegationsList[1:]
559
-
560
-		// Get metadata
561
-		delegationMeta, ok := r.tufRepo.Targets[delegation.Name]
562
-		// If we get an error, don't try to traverse further into this subtree because it doesn't exist or is malformed
563
-		if !ok {
564
-			continue
565
-		}
566
-
567
-		// Add nested delegations to return list and exploration list
568
-		allDelegations = append(allDelegations, delegationMeta.Signed.Delegations.Roles...)
569
-		delegationsList = append(delegationsList, delegationMeta.Signed.Delegations.Roles...)
570
-	}
571
-	return allDelegations, nil
572
-}
573
-
574 535
 // RoleWithSignatures is a Role with its associated signatures
575 536
 type RoleWithSignatures struct {
576 537
 	Signatures []data.Signature
... ...
@@ -604,7 +503,7 @@ func (r *NotaryRepository) ListRoles() ([]RoleWithSignatures, error) {
604 604
 		case data.CanonicalTimestampRole:
605 605
 			roleWithSig.Signatures = r.tufRepo.Timestamp.Signatures
606 606
 		default:
607
-			// If the role isn't a delegation, we should error -- this is only possible if we have invalid keyDB state
607
+			// If the role isn't a delegation, we should error -- this is only possible if we have invalid state
608 608
 			if !data.IsDelegation(role.Name) {
609 609
 				return nil, data.ErrInvalidRole{Role: role.Name, Reason: "invalid role name"}
610 610
 			}
... ...
@@ -705,7 +604,7 @@ func (r *NotaryRepository) Publish() error {
705 705
 		r.tufRepo, data.CanonicalSnapshotRole)
706 706
 
707 707
 	if err == nil {
708
-		// Only update the snapshot if we've sucessfully signed it.
708
+		// Only update the snapshot if we've successfully signed it.
709 709
 		updatedFiles[data.CanonicalSnapshotRole] = snapshotJSON
710 710
 	} else if _, ok := err.(signed.ErrNoKeys); ok {
711 711
 		// If signing fails due to us not having the snapshot key, then
... ...
@@ -743,11 +642,10 @@ func (r *NotaryRepository) Publish() error {
743 743
 // This can also be unified with some cache reading tools from tuf/client.
744 744
 // This assumes that bootstrapRepo is only used by Publish()
745 745
 func (r *NotaryRepository) bootstrapRepo() error {
746
-	kdb := keys.NewDB()
747
-	tufRepo := tuf.NewRepo(kdb, r.CryptoService)
746
+	tufRepo := tuf.NewRepo(r.CryptoService)
748 747
 
749 748
 	logrus.Debugf("Loading trusted collection.")
750
-	rootJSON, err := r.fileStore.GetMeta("root", 0)
749
+	rootJSON, err := r.fileStore.GetMeta("root", -1)
751 750
 	if err != nil {
752 751
 		return err
753 752
 	}
... ...
@@ -760,7 +658,7 @@ func (r *NotaryRepository) bootstrapRepo() error {
760 760
 	if err != nil {
761 761
 		return err
762 762
 	}
763
-	targetsJSON, err := r.fileStore.GetMeta("targets", 0)
763
+	targetsJSON, err := r.fileStore.GetMeta("targets", -1)
764 764
 	if err != nil {
765 765
 		return err
766 766
 	}
... ...
@@ -771,7 +669,7 @@ func (r *NotaryRepository) bootstrapRepo() error {
771 771
 	}
772 772
 	tufRepo.SetTargets("targets", targets)
773 773
 
774
-	snapshotJSON, err := r.fileStore.GetMeta("snapshot", 0)
774
+	snapshotJSON, err := r.fileStore.GetMeta("snapshot", -1)
775 775
 	if err == nil {
776 776
 		snapshot := &data.SignedSnapshot{}
777 777
 		err = json.Unmarshal(snapshotJSON, snapshot)
... ...
@@ -854,7 +752,10 @@ func (r *NotaryRepository) Update(forWrite bool) (*tufclient.Client, error) {
854 854
 	}
855 855
 	err = c.Update()
856 856
 	if err != nil {
857
-		if notFound, ok := err.(store.ErrMetaNotFound); ok && notFound.Resource == data.CanonicalRootRole {
857
+		// notFound.Resource may include a checksum so when the role is root,
858
+		// it will be root.json or root.<checksum>.json. Therefore best we can
859
+		// do it match a "root." prefix
860
+		if notFound, ok := err.(store.ErrMetaNotFound); ok && strings.HasPrefix(notFound.Resource, data.CanonicalRootRole+".") {
858 861
 			return nil, r.errRepositoryNotExist()
859 862
 		}
860 863
 		return nil, err
... ...
@@ -876,7 +777,7 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl
876 876
 	// try to read root from cache first. We will trust this root
877 877
 	// until we detect a problem during update which will cause
878 878
 	// us to download a new root and perform a rotation.
879
-	rootJSON, cachedRootErr := r.fileStore.GetMeta("root", maxSize)
879
+	rootJSON, cachedRootErr := r.fileStore.GetMeta("root", -1)
880 880
 
881 881
 	if cachedRootErr == nil {
882 882
 		signedRoot, cachedRootErr = r.validateRoot(rootJSON)
... ...
@@ -890,7 +791,8 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl
890 890
 		// checking for initialization of the repo).
891 891
 
892 892
 		// if remote store successfully set up, try and get root from remote
893
-		tmpJSON, err := remote.GetMeta("root", maxSize)
893
+		// We don't have any local data to determine the size of root, so try the maximum (though it is restricted at 100MB)
894
+		tmpJSON, err := remote.GetMeta("root", -1)
894 895
 		if err != nil {
895 896
 			// we didn't have a root in cache and were unable to load one from
896 897
 			// the server. Nothing we can do but error.
... ...
@@ -912,8 +814,7 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl
912 912
 		}
913 913
 	}
914 914
 
915
-	kdb := keys.NewDB()
916
-	r.tufRepo = tuf.NewRepo(kdb, r.CryptoService)
915
+	r.tufRepo = tuf.NewRepo(r.CryptoService)
917 916
 
918 917
 	if signedRoot == nil {
919 918
 		return nil, ErrRepoNotInitialized{}
... ...
@@ -927,7 +828,6 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl
927 927
 	return tufclient.NewClient(
928 928
 		r.tufRepo,
929 929
 		remote,
930
-		kdb,
931 930
 		r.fileStore,
932 931
 	), nil
933 932
 }
... ...
@@ -1020,7 +920,7 @@ func (r *NotaryRepository) DeleteTrustData() error {
1020 1020
 	if err := r.fileStore.RemoveAll(); err != nil {
1021 1021
 		return fmt.Errorf("error clearing TUF repo data: %v", err)
1022 1022
 	}
1023
-	r.tufRepo = tuf.NewRepo(nil, nil)
1023
+	r.tufRepo = tuf.NewRepo(nil)
1024 1024
 	// Clear certificates
1025 1025
 	certificates, err := r.CertStore.GetCertificatesByCN(r.gun)
1026 1026
 	if err != nil {
1027 1027
new file mode 100644
... ...
@@ -0,0 +1,294 @@
0
+package client
1
+
2
+import (
3
+	"encoding/json"
4
+	"fmt"
5
+	"path/filepath"
6
+
7
+	"github.com/Sirupsen/logrus"
8
+	"github.com/docker/notary"
9
+	"github.com/docker/notary/client/changelist"
10
+	"github.com/docker/notary/tuf/data"
11
+	"github.com/docker/notary/tuf/store"
12
+	"github.com/docker/notary/tuf/utils"
13
+)
14
+
15
+// AddDelegation creates changelist entries to add provided delegation public keys and paths.
16
+// This method composes AddDelegationRoleAndKeys and AddDelegationPaths (each creates one changelist if called).
17
+func (r *NotaryRepository) AddDelegation(name string, delegationKeys []data.PublicKey, paths []string) error {
18
+	if len(delegationKeys) > 0 {
19
+		err := r.AddDelegationRoleAndKeys(name, delegationKeys)
20
+		if err != nil {
21
+			return err
22
+		}
23
+	}
24
+	if len(paths) > 0 {
25
+		err := r.AddDelegationPaths(name, paths)
26
+		if err != nil {
27
+			return err
28
+		}
29
+	}
30
+	return nil
31
+}
32
+
33
+// AddDelegationRoleAndKeys creates a changelist entry to add provided delegation public keys.
34
+// This method is the simplest way to create a new delegation, because the delegation must have at least
35
+// one key upon creation to be valid since we will reject the changelist while validating the threshold.
36
+func (r *NotaryRepository) AddDelegationRoleAndKeys(name string, delegationKeys []data.PublicKey) error {
37
+
38
+	if !data.IsDelegation(name) {
39
+		return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"}
40
+	}
41
+
42
+	cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
43
+	if err != nil {
44
+		return err
45
+	}
46
+	defer cl.Close()
47
+
48
+	logrus.Debugf(`Adding delegation "%s" with threshold %d, and %d keys\n`,
49
+		name, notary.MinThreshold, len(delegationKeys))
50
+
51
+	// Defaulting to threshold of 1, since we don't allow for larger thresholds at the moment.
52
+	tdJSON, err := json.Marshal(&changelist.TufDelegation{
53
+		NewThreshold: notary.MinThreshold,
54
+		AddKeys:      data.KeyList(delegationKeys),
55
+	})
56
+	if err != nil {
57
+		return err
58
+	}
59
+
60
+	template := newCreateDelegationChange(name, tdJSON)
61
+	return addChange(cl, template, name)
62
+}
63
+
64
+// AddDelegationPaths creates a changelist entry to add provided paths to an existing delegation.
65
+// This method cannot create a new delegation itself because the role must meet the key threshold upon creation.
66
+func (r *NotaryRepository) AddDelegationPaths(name string, paths []string) error {
67
+
68
+	if !data.IsDelegation(name) {
69
+		return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"}
70
+	}
71
+
72
+	cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
73
+	if err != nil {
74
+		return err
75
+	}
76
+	defer cl.Close()
77
+
78
+	logrus.Debugf(`Adding %s paths to delegation %s\n`, paths, name)
79
+
80
+	tdJSON, err := json.Marshal(&changelist.TufDelegation{
81
+		AddPaths: paths,
82
+	})
83
+	if err != nil {
84
+		return err
85
+	}
86
+
87
+	template := newCreateDelegationChange(name, tdJSON)
88
+	return addChange(cl, template, name)
89
+}
90
+
91
+// RemoveDelegationKeysAndPaths creates changelist entries to remove provided delegation key IDs and paths.
92
+// This method composes RemoveDelegationPaths and RemoveDelegationKeys (each creates one changelist if called).
93
+func (r *NotaryRepository) RemoveDelegationKeysAndPaths(name string, keyIDs, paths []string) error {
94
+	if len(paths) > 0 {
95
+		err := r.RemoveDelegationPaths(name, paths)
96
+		if err != nil {
97
+			return err
98
+		}
99
+	}
100
+	if len(keyIDs) > 0 {
101
+		err := r.RemoveDelegationKeys(name, keyIDs)
102
+		if err != nil {
103
+			return err
104
+		}
105
+	}
106
+	return nil
107
+}
108
+
109
+// RemoveDelegationRole creates a changelist to remove all paths and keys from a role, and delete the role in its entirety.
110
+func (r *NotaryRepository) RemoveDelegationRole(name string) error {
111
+
112
+	if !data.IsDelegation(name) {
113
+		return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"}
114
+	}
115
+
116
+	cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
117
+	if err != nil {
118
+		return err
119
+	}
120
+	defer cl.Close()
121
+
122
+	logrus.Debugf(`Removing delegation "%s"\n`, name)
123
+
124
+	template := newDeleteDelegationChange(name, nil)
125
+	return addChange(cl, template, name)
126
+}
127
+
128
+// RemoveDelegationPaths creates a changelist entry to remove provided paths from an existing delegation.
129
+func (r *NotaryRepository) RemoveDelegationPaths(name string, paths []string) error {
130
+
131
+	if !data.IsDelegation(name) {
132
+		return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"}
133
+	}
134
+
135
+	cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
136
+	if err != nil {
137
+		return err
138
+	}
139
+	defer cl.Close()
140
+
141
+	logrus.Debugf(`Removing %s paths from delegation "%s"\n`, paths, name)
142
+
143
+	tdJSON, err := json.Marshal(&changelist.TufDelegation{
144
+		RemovePaths: paths,
145
+	})
146
+	if err != nil {
147
+		return err
148
+	}
149
+
150
+	template := newUpdateDelegationChange(name, tdJSON)
151
+	return addChange(cl, template, name)
152
+}
153
+
154
+// RemoveDelegationKeys creates a changelist entry to remove provided keys from an existing delegation.
155
+// When this changelist is applied, if the specified keys are the only keys left in the role,
156
+// the role itself will be deleted in its entirety.
157
+func (r *NotaryRepository) RemoveDelegationKeys(name string, keyIDs []string) error {
158
+
159
+	if !data.IsDelegation(name) {
160
+		return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"}
161
+	}
162
+
163
+	cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
164
+	if err != nil {
165
+		return err
166
+	}
167
+	defer cl.Close()
168
+
169
+	logrus.Debugf(`Removing %s keys from delegation "%s"\n`, keyIDs, name)
170
+
171
+	tdJSON, err := json.Marshal(&changelist.TufDelegation{
172
+		RemoveKeys: keyIDs,
173
+	})
174
+	if err != nil {
175
+		return err
176
+	}
177
+
178
+	template := newUpdateDelegationChange(name, tdJSON)
179
+	return addChange(cl, template, name)
180
+}
181
+
182
+// ClearDelegationPaths creates a changelist entry to remove all paths from an existing delegation.
183
+func (r *NotaryRepository) ClearDelegationPaths(name string) error {
184
+
185
+	if !data.IsDelegation(name) {
186
+		return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"}
187
+	}
188
+
189
+	cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
190
+	if err != nil {
191
+		return err
192
+	}
193
+	defer cl.Close()
194
+
195
+	logrus.Debugf(`Removing all paths from delegation "%s"\n`, name)
196
+
197
+	tdJSON, err := json.Marshal(&changelist.TufDelegation{
198
+		ClearAllPaths: true,
199
+	})
200
+	if err != nil {
201
+		return err
202
+	}
203
+
204
+	template := newUpdateDelegationChange(name, tdJSON)
205
+	return addChange(cl, template, name)
206
+}
207
+
208
+func newUpdateDelegationChange(name string, content []byte) *changelist.TufChange {
209
+	return changelist.NewTufChange(
210
+		changelist.ActionUpdate,
211
+		name,
212
+		changelist.TypeTargetsDelegation,
213
+		"", // no path for delegations
214
+		content,
215
+	)
216
+}
217
+
218
+func newCreateDelegationChange(name string, content []byte) *changelist.TufChange {
219
+	return changelist.NewTufChange(
220
+		changelist.ActionCreate,
221
+		name,
222
+		changelist.TypeTargetsDelegation,
223
+		"", // no path for delegations
224
+		content,
225
+	)
226
+}
227
+
228
+func newDeleteDelegationChange(name string, content []byte) *changelist.TufChange {
229
+	return changelist.NewTufChange(
230
+		changelist.ActionDelete,
231
+		name,
232
+		changelist.TypeTargetsDelegation,
233
+		"", // no path for delegations
234
+		content,
235
+	)
236
+}
237
+
238
+// GetDelegationRoles returns the keys and roles of the repository's delegations
239
+// Also converts key IDs to canonical key IDs to keep consistent with signing prompts
240
+func (r *NotaryRepository) GetDelegationRoles() ([]*data.Role, error) {
241
+	// Update state of the repo to latest
242
+	if _, err := r.Update(false); err != nil {
243
+		return nil, err
244
+	}
245
+
246
+	// All top level delegations (ex: targets/level1) are stored exclusively in targets.json
247
+	_, ok := r.tufRepo.Targets[data.CanonicalTargetsRole]
248
+	if !ok {
249
+		return nil, store.ErrMetaNotFound{Resource: data.CanonicalTargetsRole}
250
+	}
251
+
252
+	// make a copy for traversing nested delegations
253
+	allDelegations := []*data.Role{}
254
+
255
+	// Define a visitor function to populate the delegations list and translate their key IDs to canonical IDs
256
+	delegationCanonicalListVisitor := func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} {
257
+		// For the return list, update with a copy that includes canonicalKeyIDs
258
+		// These aren't validated by the validRole
259
+		canonicalDelegations, err := translateDelegationsToCanonicalIDs(tgt.Signed.Delegations)
260
+		if err != nil {
261
+			return err
262
+		}
263
+		allDelegations = append(allDelegations, canonicalDelegations...)
264
+		return nil
265
+	}
266
+	err := r.tufRepo.WalkTargets("", "", delegationCanonicalListVisitor)
267
+	if err != nil {
268
+		return nil, err
269
+	}
270
+	return allDelegations, nil
271
+}
272
+
273
+func translateDelegationsToCanonicalIDs(delegationInfo data.Delegations) ([]*data.Role, error) {
274
+	canonicalDelegations := make([]*data.Role, len(delegationInfo.Roles))
275
+	copy(canonicalDelegations, delegationInfo.Roles)
276
+	delegationKeys := delegationInfo.Keys
277
+	for i, delegation := range canonicalDelegations {
278
+		canonicalKeyIDs := []string{}
279
+		for _, keyID := range delegation.KeyIDs {
280
+			pubKey, ok := delegationKeys[keyID]
281
+			if !ok {
282
+				return nil, fmt.Errorf("Could not translate canonical key IDs for %s", delegation.Name)
283
+			}
284
+			canonicalKeyID, err := utils.CanonicalKeyID(pubKey)
285
+			if err != nil {
286
+				return nil, fmt.Errorf("Could not translate canonical key IDs for %s: %v", delegation.Name, err)
287
+			}
288
+			canonicalKeyIDs = append(canonicalKeyIDs, canonicalKeyID)
289
+		}
290
+		canonicalDelegations[i].KeyIDs = canonicalKeyIDs
291
+	}
292
+	return canonicalDelegations, nil
293
+}
... ...
@@ -12,8 +12,8 @@ import (
12 12
 	"github.com/docker/notary/client/changelist"
13 13
 	tuf "github.com/docker/notary/tuf"
14 14
 	"github.com/docker/notary/tuf/data"
15
-	"github.com/docker/notary/tuf/keys"
16 15
 	"github.com/docker/notary/tuf/store"
16
+	"github.com/docker/notary/tuf/utils"
17 17
 )
18 18
 
19 19
 // Use this to initialize remote HTTPStores from the config settings
... ...
@@ -22,7 +22,6 @@ func getRemoteStore(baseURL, gun string, rt http.RoundTripper) (store.RemoteStor
22 22
 		baseURL+"/v2/"+gun+"/_trust/tuf/",
23 23
 		"",
24 24
 		"json",
25
-		"",
26 25
 		"key",
27 26
 		rt,
28 27
 	)
... ...
@@ -80,53 +79,51 @@ func changeTargetsDelegation(repo *tuf.Repo, c changelist.Change) error {
80 80
 		if err != nil {
81 81
 			return err
82 82
 		}
83
-		r, err := repo.GetDelegation(c.Scope())
84
-		if _, ok := err.(data.ErrNoSuchRole); err != nil && !ok {
85
-			// error that wasn't ErrNoSuchRole
86
-			return err
87
-		}
88
-		if err == nil {
89
-			// role existed, attempt to merge paths and keys
90
-			if err := r.AddPaths(td.AddPaths); err != nil {
91
-				return err
92
-			}
93
-			return repo.UpdateDelegations(r, td.AddKeys)
94
-		}
95
-		// create brand new role
96
-		r, err = td.ToNewRole(c.Scope())
83
+
84
+		// Try to create brand new role or update one
85
+		// First add the keys, then the paths.  We can only add keys and paths in this scenario
86
+		err = repo.UpdateDelegationKeys(c.Scope(), td.AddKeys, []string{}, td.NewThreshold)
97 87
 		if err != nil {
98 88
 			return err
99 89
 		}
100
-		return repo.UpdateDelegations(r, td.AddKeys)
90
+		return repo.UpdateDelegationPaths(c.Scope(), td.AddPaths, []string{}, false)
101 91
 	case changelist.ActionUpdate:
102 92
 		td := changelist.TufDelegation{}
103 93
 		err := json.Unmarshal(c.Content(), &td)
104 94
 		if err != nil {
105 95
 			return err
106 96
 		}
107
-		r, err := repo.GetDelegation(c.Scope())
97
+		delgRole, err := repo.GetDelegationRole(c.Scope())
108 98
 		if err != nil {
109 99
 			return err
110 100
 		}
111
-		// If we specify the only keys left delete the role, else just delete specified keys
112
-		if strings.Join(r.KeyIDs, ";") == strings.Join(td.RemoveKeys, ";") && len(td.AddKeys) == 0 {
113
-			r := data.Role{Name: c.Scope()}
114
-			return repo.DeleteDelegation(r)
101
+
102
+		// We need to translate the keys from canonical ID to TUF ID for compatibility
103
+		canonicalToTUFID := make(map[string]string)
104
+		for tufID, pubKey := range delgRole.Keys {
105
+			canonicalID, err := utils.CanonicalKeyID(pubKey)
106
+			if err != nil {
107
+				return err
108
+			}
109
+			canonicalToTUFID[canonicalID] = tufID
115 110
 		}
116
-		// if we aren't deleting and the role exists, merge
117
-		if err := r.AddPaths(td.AddPaths); err != nil {
118
-			return err
111
+
112
+		removeTUFKeyIDs := []string{}
113
+		for _, canonID := range td.RemoveKeys {
114
+			removeTUFKeyIDs = append(removeTUFKeyIDs, canonicalToTUFID[canonID])
115
+		}
116
+
117
+		// If we specify the only keys left delete the role, else just delete specified keys
118
+		if strings.Join(delgRole.ListKeyIDs(), ";") == strings.Join(removeTUFKeyIDs, ";") && len(td.AddKeys) == 0 {
119
+			return repo.DeleteDelegation(c.Scope())
119 120
 		}
120
-		if err := r.AddPathHashPrefixes(td.AddPathHashPrefixes); err != nil {
121
+		err = repo.UpdateDelegationKeys(c.Scope(), td.AddKeys, removeTUFKeyIDs, td.NewThreshold)
122
+		if err != nil {
121 123
 			return err
122 124
 		}
123
-		r.RemoveKeys(td.RemoveKeys)
124
-		r.RemovePaths(td.RemovePaths)
125
-		r.RemovePathHashPrefixes(td.RemovePathHashPrefixes)
126
-		return repo.UpdateDelegations(r, td.AddKeys)
125
+		return repo.UpdateDelegationPaths(c.Scope(), td.AddPaths, td.RemovePaths, td.ClearAllPaths)
127 126
 	case changelist.ActionDelete:
128
-		r := data.Role{Name: c.Scope()}
129
-		return repo.DeleteDelegation(r)
127
+		return repo.DeleteDelegation(c.Scope())
130 128
 	default:
131 129
 		return fmt.Errorf("unsupported action against delegations: %s", c.Action())
132 130
 	}
... ...
@@ -239,19 +236,6 @@ func getRemoteKey(url, gun, role string, rt http.RoundTripper) (data.PublicKey,
239 239
 	return pubKey, nil
240 240
 }
241 241
 
242
-// add a key to a KeyDB, and create a role for the key and add it.
243
-func addKeyForRole(kdb *keys.KeyDB, role string, key data.PublicKey) error {
244
-	theRole, err := data.NewRole(role, 1, []string{key.ID()}, nil, nil)
245
-	if err != nil {
246
-		return err
247
-	}
248
-	kdb.AddKey(key)
249
-	if err := kdb.AddRole(theRole); err != nil {
250
-		return err
251
-	}
252
-	return nil
253
-}
254
-
255 242
 // signs and serializes the metadata for a canonical role in a tuf repo to JSON
256 243
 func serializeCanonicalRole(tufRepo *tuf.Repo, role string) (out []byte, err error) {
257 244
 	var s *data.Signed
... ...
@@ -1,7 +1,15 @@
1 1
 package notary
2 2
 
3
+import (
4
+	"time"
5
+)
6
+
3 7
 // application wide constants
4 8
 const (
9
+	// MaxDownloadSize is the maximum size we'll download for metadata if no limit is given
10
+	MaxDownloadSize int64 = 100 << 20
11
+	// MaxTimestampSize is the maximum size of timestamp metadata - 1MiB.
12
+	MaxTimestampSize int64 = 1 << 20
5 13
 	// MinRSABitSize is the minimum bit size for RSA keys allowed in notary
6 14
 	MinRSABitSize = 2048
7 15
 	// MinThreshold requires a minimum of one threshold for roles; currently we do not support a higher threshold
... ...
@@ -14,4 +22,29 @@ const (
14 14
 	Sha256HexSize = 64
15 15
 	// TrustedCertsDir is the directory, under the notary repo base directory, where trusted certs are stored
16 16
 	TrustedCertsDir = "trusted_certificates"
17
+	// PrivDir is the directory, under the notary repo base directory, where private keys are stored
18
+	PrivDir = "private"
19
+	// RootKeysSubdir is the subdirectory under PrivDir where root private keys are stored
20
+	RootKeysSubdir = "root_keys"
21
+	// NonRootKeysSubdir is the subdirectory under PrivDir where non-root private keys are stored
22
+	NonRootKeysSubdir = "tuf_keys"
23
+
24
+	// Day is a duration of one day
25
+	Day  = 24 * time.Hour
26
+	Year = 365 * Day
27
+
28
+	// NotaryRootExpiry is the duration representing the expiry time of the Root role
29
+	NotaryRootExpiry      = 10 * Year
30
+	NotaryTargetsExpiry   = 3 * Year
31
+	NotarySnapshotExpiry  = 3 * Year
32
+	NotaryTimestampExpiry = 14 * Day
17 33
 )
34
+
35
+// NotaryDefaultExpiries is the construct used to configure the default expiry times of
36
+// the various role files.
37
+var NotaryDefaultExpiries = map[string]time.Duration{
38
+	"root":      NotaryRootExpiry,
39
+	"targets":   NotaryTargetsExpiry,
40
+	"snapshot":  NotarySnapshotExpiry,
41
+	"timestamp": NotaryTimestampExpiry,
42
+}
... ...
@@ -11,8 +11,10 @@ import (
11 11
 	"path/filepath"
12 12
 	"strings"
13 13
 
14
+	"github.com/docker/notary"
14 15
 	"github.com/docker/notary/passphrase"
15 16
 	"github.com/docker/notary/trustmanager"
17
+	"github.com/docker/notary/tuf/data"
16 18
 )
17 19
 
18 20
 const zipMadeByUNIX = 3 << 8
... ...
@@ -31,14 +33,17 @@ var (
31 31
 	ErrNoKeysFoundForGUN = errors.New("no keys found for specified GUN")
32 32
 )
33 33
 
34
-// ExportRootKey exports the specified root key to an io.Writer in PEM format.
34
+// ExportKey exports the specified private key to an io.Writer in PEM format.
35 35
 // The key's existing encryption is preserved.
36
-func (cs *CryptoService) ExportRootKey(dest io.Writer, keyID string) error {
36
+func (cs *CryptoService) ExportKey(dest io.Writer, keyID, role string) error {
37 37
 	var (
38 38
 		pemBytes []byte
39 39
 		err      error
40 40
 	)
41 41
 
42
+	if role != data.CanonicalRootRole {
43
+		keyID = filepath.Join(cs.gun, keyID)
44
+	}
42 45
 	for _, ks := range cs.keyStores {
43 46
 		pemBytes, err = ks.ExportKey(keyID)
44 47
 		if err != nil {
... ...
@@ -59,9 +64,9 @@ func (cs *CryptoService) ExportRootKey(dest io.Writer, keyID string) error {
59 59
 	return nil
60 60
 }
61 61
 
62
-// ExportRootKeyReencrypt exports the specified root key to an io.Writer in
62
+// ExportKeyReencrypt exports the specified private key to an io.Writer in
63 63
 // PEM format. The key is reencrypted with a new passphrase.
64
-func (cs *CryptoService) ExportRootKeyReencrypt(dest io.Writer, keyID string, newPassphraseRetriever passphrase.Retriever) error {
64
+func (cs *CryptoService) ExportKeyReencrypt(dest io.Writer, keyID string, newPassphraseRetriever passphrase.Retriever) error {
65 65
 	privateKey, role, err := cs.GetPrivateKey(keyID)
66 66
 	if err != nil {
67 67
 		return err
... ...
@@ -103,14 +108,41 @@ func (cs *CryptoService) ImportRootKey(source io.Reader) error {
103 103
 	if err != nil {
104 104
 		return err
105 105
 	}
106
+	return cs.ImportRoleKey(pemBytes, data.CanonicalRootRole, nil)
107
+}
106 108
 
107
-	if err = checkRootKeyIsEncrypted(pemBytes); err != nil {
108
-		return err
109
+// ImportRoleKey imports a private key in PEM format key from a byte array
110
+// It prompts for the key's passphrase to verify the data and to determine
111
+// the key ID.
112
+func (cs *CryptoService) ImportRoleKey(pemBytes []byte, role string, newPassphraseRetriever passphrase.Retriever) error {
113
+	var alias string
114
+	var err error
115
+	if role == data.CanonicalRootRole {
116
+		alias = role
117
+		if err = checkRootKeyIsEncrypted(pemBytes); err != nil {
118
+			return err
119
+		}
120
+	} else {
121
+		// Parse the private key to get the key ID so that we can import it to the correct location
122
+		privKey, err := trustmanager.ParsePEMPrivateKey(pemBytes, "")
123
+		if err != nil {
124
+			privKey, _, err = trustmanager.GetPasswdDecryptBytes(newPassphraseRetriever, pemBytes, role, string(role))
125
+			if err != nil {
126
+				return err
127
+			}
128
+		}
129
+		// Since we're importing a non-root role, we need to pass the path as an alias
130
+		alias = filepath.Join(notary.NonRootKeysSubdir, cs.gun, privKey.ID())
131
+		// We also need to ensure that the role is properly set in the PEM headers
132
+		pemBytes, err = trustmanager.KeyToPEM(privKey, role)
133
+		if err != nil {
134
+			return err
135
+		}
109 136
 	}
110 137
 
111 138
 	for _, ks := range cs.keyStores {
112 139
 		// don't redeclare err, we want the value carried out of the loop
113
-		if err = ks.ImportKey(pemBytes, "root"); err == nil {
140
+		if err = ks.ImportKey(pemBytes, alias); err == nil {
114 141
 			return nil //bail on the first keystore we import to
115 142
 		}
116 143
 	}
... ...
@@ -1,27 +1,34 @@
1
-notaryserver:
1
+server:
2 2
   build: .
3 3
   dockerfile: server.Dockerfile
4 4
   links:
5
-   - notarymysql
6
-   - notarysigner
7
-  ports:
8
-   - "8080"
9
-   - "4443:4443"
5
+    - mysql
6
+    - signer
7
+    - signer:notarysigner
10 8
   environment:
11
-   - SERVICE_NAME=notary
12
-  command: -config=fixtures/server-config.json
13
-notarysigner:
14
-  volumes:
15
-   - /dev/bus/usb/003/010:/dev/bus/usb/002/010
16
-   - /var/run/pcscd/pcscd.comm:/var/run/pcscd/pcscd.comm
9
+    - SERVICE_NAME=notary_server
10
+  ports:
11
+    - "8080"
12
+    - "4443:4443"
13
+  entrypoint: /bin/bash
14
+  command: -c "./migrations/migrate.sh && notary-server -config=fixtures/server-config.json"
15
+signer:
17 16
   build: .
18 17
   dockerfile: signer.Dockerfile
19 18
   links:
20
-   - notarymysql
21
-  command: -config=fixtures/signer-config.json
22
-notarymysql:
19
+    - mysql
20
+  environment:
21
+    - SERVICE_NAME=notary_signer
22
+  entrypoint: /bin/bash
23
+  command: -c "./migrations/migrate.sh && notary-signer -config=fixtures/signer-config.json"
24
+mysql:
23 25
   volumes:
24
-    - notarymysql:/var/lib/mysql
25
-  build: ./notarymysql/
26
+    - ./notarymysql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
27
+    - notary_data:/var/lib/mysql
28
+  image: mariadb:10.1.10
26 29
   ports:
27 30
     - "3306:3306"
31
+  environment:
32
+    - TERM=dumb
33
+    - MYSQL_ALLOW_EMPTY_PASSWORD="true"
34
+  command: mysqld --innodb_file_per_table
28 35
deleted file mode 100644
... ...
@@ -1,21 +0,0 @@
1
-The MIT License (MIT)
2
-
3
-Copyright (c) 2014 Sameer Naik
4
-
5
-Permission is hereby granted, free of charge, to any person obtaining a copy
6
-of this software and associated documentation files (the "Software"), to deal
7
-in the Software without restriction, including without limitation the rights
8
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
-copies of the Software, and to permit persons to whom the Software is
10
-furnished to do so, subject to the following conditions:
11
-
12
-The above copyright notice and this permission notice shall be included in all
13
-copies or substantial portions of the Software.
14
-
15
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
-SOFTWARE.
... ...
@@ -1,4 +1,5 @@
1
-FROM golang:1.5.1
1
+FROM golang:1.5.3
2
+MAINTAINER David Lawrence "david.lawrence@docker.com"
2 3
 
3 4
 RUN apt-get update && apt-get install -y \
4 5
     libltdl-dev \
... ...
@@ -7,13 +8,20 @@ RUN apt-get update && apt-get install -y \
7 7
 
8 8
 EXPOSE 4443
9 9
 
10
+# Install DB migration tool
11
+RUN go get github.com/mattes/migrate
12
+
10 13
 ENV NOTARYPKG github.com/docker/notary
11 14
 ENV GOPATH /go/src/${NOTARYPKG}/Godeps/_workspace:$GOPATH
12 15
 
16
+
17
+
18
+# Copy the local repo to the expected go path
13 19
 COPY . /go/src/github.com/docker/notary
14 20
 
15 21
 WORKDIR /go/src/${NOTARYPKG}
16 22
 
23
+# Install notary-server
17 24
 RUN go install \
18 25
     -tags pkcs11 \
19 26
     -ldflags "-w -X ${NOTARYPKG}/version.GitCommit=`git rev-parse --short HEAD` -X ${NOTARYPKG}/version.NotaryVersion=`cat NOTARY_VERSION`" \
... ...
@@ -1,29 +1,20 @@
1
-FROM dockersecurity/golang-softhsm2
2
-MAINTAINER Diogo Monica "diogo@docker.com"
1
+FROM golang:1.5.3
2
+MAINTAINER David Lawrence "david.lawrence@docker.com"
3 3
 
4
-# CHANGE-ME: Default values for SoftHSM2 PIN and SOPIN, used to initialize the first token
5
-ENV NOTARY_SIGNER_PIN="1234"
6
-ENV SOPIN="1234"
7
-ENV LIBDIR="/usr/local/lib/softhsm/"
8
-ENV NOTARY_SIGNER_DEFAULT_ALIAS="timestamp_1"
9
-ENV NOTARY_SIGNER_TIMESTAMP_1="testpassword"
10
-
11
-# Install openSC and dependencies
12 4
 RUN apt-get update && apt-get install -y \
13 5
     libltdl-dev \
14
-    libpcsclite-dev \
15
-    opensc \
16
-    usbutils \
17 6
     --no-install-recommends \
18 7
     && rm -rf /var/lib/apt/lists/*
19 8
 
20
-# Initialize the SoftHSM2 token on slod 0, using PIN and SOPIN varaibles
21
-RUN softhsm2-util --init-token --slot 0 --label "test_token" --pin $NOTARY_SIGNER_PIN --so-pin $SOPIN
9
+EXPOSE 4444
10
+
11
+# Install DB migration tool
12
+RUN go get github.com/mattes/migrate
22 13
 
23 14
 ENV NOTARYPKG github.com/docker/notary
24 15
 ENV GOPATH /go/src/${NOTARYPKG}/Godeps/_workspace:$GOPATH
25
-
26
-EXPOSE 4444
16
+ENV NOTARY_SIGNER_DEFAULT_ALIAS="timestamp_1"
17
+ENV NOTARY_SIGNER_TIMESTAMP_1="testpassword"
27 18
 
28 19
 # Copy the local repo to the expected go path
29 20
 COPY . /go/src/github.com/docker/notary
... ...
@@ -36,6 +27,5 @@ RUN go install \
36 36
     -ldflags "-w -X ${NOTARYPKG}/version.GitCommit=`git rev-parse --short HEAD` -X ${NOTARYPKG}/version.NotaryVersion=`cat NOTARY_VERSION`" \
37 37
     ${NOTARYPKG}/cmd/notary-signer
38 38
 
39
-
40 39
 ENTRYPOINT [ "notary-signer" ]
41 40
 CMD [ "-config=fixtures/signer-config-local.json" ]
... ...
@@ -8,16 +8,11 @@ import (
8 8
 	"sync"
9 9
 
10 10
 	"github.com/Sirupsen/logrus"
11
+	"github.com/docker/notary"
11 12
 	"github.com/docker/notary/passphrase"
12 13
 	"github.com/docker/notary/tuf/data"
13 14
 )
14 15
 
15
-const (
16
-	rootKeysSubdir    = "root_keys"
17
-	nonRootKeysSubdir = "tuf_keys"
18
-	privDir           = "private"
19
-)
20
-
21 16
 // KeyFileStore persists and manages private keys on disk
22 17
 type KeyFileStore struct {
23 18
 	sync.Mutex
... ...
@@ -37,7 +32,7 @@ type KeyMemoryStore struct {
37 37
 // NewKeyFileStore returns a new KeyFileStore creating a private directory to
38 38
 // hold the keys.
39 39
 func NewKeyFileStore(baseDir string, passphraseRetriever passphrase.Retriever) (*KeyFileStore, error) {
40
-	baseDir = filepath.Join(baseDir, privDir)
40
+	baseDir = filepath.Join(baseDir, notary.PrivDir)
41 41
 	fileStore, err := NewPrivateSimpleFileStore(baseDir, keyExtension)
42 42
 	if err != nil {
43 43
 		return nil, err
... ...
@@ -242,10 +237,10 @@ func listKeys(s LimitedFileStore) map[string]string {
242 242
 	for _, f := range s.ListFiles() {
243 243
 		// Remove the prefix of the directory from the filename
244 244
 		var keyIDFull string
245
-		if strings.HasPrefix(f, rootKeysSubdir+"/") {
246
-			keyIDFull = strings.TrimPrefix(f, rootKeysSubdir+"/")
245
+		if strings.HasPrefix(f, notary.RootKeysSubdir+"/") {
246
+			keyIDFull = strings.TrimPrefix(f, notary.RootKeysSubdir+"/")
247 247
 		} else {
248
-			keyIDFull = strings.TrimPrefix(f, nonRootKeysSubdir+"/")
248
+			keyIDFull = strings.TrimPrefix(f, notary.NonRootKeysSubdir+"/")
249 249
 		}
250 250
 
251 251
 		keyIDFull = strings.TrimSpace(keyIDFull)
... ...
@@ -302,9 +297,9 @@ func removeKey(s LimitedFileStore, cachedKeys map[string]*cachedKey, name string
302 302
 // Assumes 2 subdirectories, 1 containing root keys and 1 containing tuf keys
303 303
 func getSubdir(alias string) string {
304 304
 	if alias == "root" {
305
-		return rootKeysSubdir
305
+		return notary.RootKeysSubdir
306 306
 	}
307
-	return nonRootKeysSubdir
307
+	return notary.NonRootKeysSubdir
308 308
 }
309 309
 
310 310
 // 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) {
327 327
 	return keyBytes, role, nil
328 328
 }
329 329
 
330
-// GetPasswdDecryptBytes gets the password to decript the given pem bytes.
330
+// GetPasswdDecryptBytes gets the password to decrypt the given pem bytes.
331 331
 // Returns the password and private key
332 332
 func GetPasswdDecryptBytes(passphraseRetriever passphrase.Retriever, pemBytes []byte, name, alias string) (data.PrivateKey, string, error) {
333 333
 	var (
... ...
@@ -470,12 +470,17 @@ func KeyToPEM(privKey data.PrivateKey, role string) ([]byte, error) {
470 470
 		return nil, err
471 471
 	}
472 472
 
473
-	block := &pem.Block{
474
-		Type: bt,
475
-		Headers: map[string]string{
473
+	headers := map[string]string{}
474
+	if role != "" {
475
+		headers = map[string]string{
476 476
 			"role": role,
477
-		},
478
-		Bytes: privKey.Private(),
477
+		}
478
+	}
479
+
480
+	block := &pem.Block{
481
+		Type:    bt,
482
+		Headers: headers,
483
+		Bytes:   privKey.Private(),
479 484
 	}
480 485
 
481 486
 	return pem.EncodeToMemory(block), nil
... ...
@@ -509,6 +514,19 @@ func EncryptPrivateKey(key data.PrivateKey, role, passphrase string) ([]byte, er
509 509
 	return pem.EncodeToMemory(encryptedPEMBlock), nil
510 510
 }
511 511
 
512
+// ReadRoleFromPEM returns the value from the role PEM header, if it exists
513
+func ReadRoleFromPEM(pemBytes []byte) string {
514
+	pemBlock, _ := pem.Decode(pemBytes)
515
+	if pemBlock.Headers == nil {
516
+		return ""
517
+	}
518
+	role, ok := pemBlock.Headers["role"]
519
+	if !ok {
520
+		return ""
521
+	}
522
+	return role
523
+}
524
+
512 525
 // CertToKey transforms a single input certificate into its corresponding
513 526
 // PublicKey
514 527
 func CertToKey(cert *x509.Certificate) data.PublicKey {
... ...
@@ -765,15 +765,15 @@ func (s *YubiKeyStore) ExportKey(keyID string) ([]byte, error) {
765 765
 // ImportKey imports a root key into a Yubikey
766 766
 func (s *YubiKeyStore) ImportKey(pemBytes []byte, keyPath string) error {
767 767
 	logrus.Debugf("Attempting to import: %s key inside of YubiKeyStore", keyPath)
768
+	if keyPath != data.CanonicalRootRole {
769
+		return fmt.Errorf("yubikey only supports storing root keys")
770
+	}
768 771
 	privKey, _, err := trustmanager.GetPasswdDecryptBytes(
769 772
 		s.passRetriever, pemBytes, "", "imported root")
770 773
 	if err != nil {
771 774
 		logrus.Debugf("Failed to get and retrieve a key from: %s", keyPath)
772 775
 		return err
773 776
 	}
774
-	if keyPath != data.CanonicalRootRole {
775
-		return fmt.Errorf("yubikey only supports storing root keys")
776
-	}
777 777
 	_, err = s.addKey(privKey.ID(), "root", privKey)
778 778
 	return err
779 779
 }
... ...
@@ -29,7 +29,7 @@ however in attempting to add delegations I found I was making such
29 29
 significant changes that I could not maintain backwards compatibility
30 30
 without the code becoming overly convoluted.
31 31
 
32
-Some features such as pluggable verifiers have alreayd been merged upstream to flynn/go-tuf
32
+Some features such as pluggable verifiers have already been merged upstream to flynn/go-tuf
33 33
 and we are in discussion with [titanous](https://github.com/titanous) about working to merge the 2 implementations.
34 34
 
35 35
 This implementation retains the same 3 Clause BSD license present on 
... ...
@@ -3,38 +3,31 @@ package client
3 3
 import (
4 4
 	"bytes"
5 5
 	"crypto/sha256"
6
-	"encoding/hex"
7 6
 	"encoding/json"
8 7
 	"fmt"
9
-	"io"
10 8
 	"path"
11
-	"strings"
12 9
 
13 10
 	"github.com/Sirupsen/logrus"
11
+	"github.com/docker/notary"
14 12
 	tuf "github.com/docker/notary/tuf"
15 13
 	"github.com/docker/notary/tuf/data"
16
-	"github.com/docker/notary/tuf/keys"
17 14
 	"github.com/docker/notary/tuf/signed"
18 15
 	"github.com/docker/notary/tuf/store"
19 16
 	"github.com/docker/notary/tuf/utils"
20 17
 )
21 18
 
22
-const maxSize int64 = 5 << 20
23
-
24 19
 // Client is a usability wrapper around a raw TUF repo
25 20
 type Client struct {
26 21
 	local  *tuf.Repo
27 22
 	remote store.RemoteStore
28
-	keysDB *keys.KeyDB
29 23
 	cache  store.MetadataStore
30 24
 }
31 25
 
32
-// NewClient initialized a Client with the given repo, remote source of content, key database, and cache
33
-func NewClient(local *tuf.Repo, remote store.RemoteStore, keysDB *keys.KeyDB, cache store.MetadataStore) *Client {
26
+// NewClient initialized a Client with the given repo, remote source of content, and cache
27
+func NewClient(local *tuf.Repo, remote store.RemoteStore, cache store.MetadataStore) *Client {
34 28
 	return &Client{
35 29
 		local:  local,
36 30
 		remote: remote,
37
-		keysDB: keysDB,
38 31
 		cache:  cache,
39 32
 	}
40 33
 }
... ...
@@ -131,11 +124,15 @@ func (c Client) checkRoot() error {
131 131
 func (c *Client) downloadRoot() error {
132 132
 	logrus.Debug("Downloading Root...")
133 133
 	role := data.CanonicalRootRole
134
-	size := maxSize
134
+	// We can't read an exact size for the root metadata without risking getting stuck in the TUF update cycle
135
+	// since it's possible that downloading timestamp/snapshot metadata may fail due to a signature mismatch
136
+	var size int64 = -1
135 137
 	var expectedSha256 []byte
136 138
 	if c.local.Snapshot != nil {
137
-		size = c.local.Snapshot.Signed.Meta[role].Length
138
-		expectedSha256 = c.local.Snapshot.Signed.Meta[role].Hashes["sha256"]
139
+		if prevRootMeta, ok := c.local.Snapshot.Signed.Meta[role]; ok {
140
+			size = prevRootMeta.Length
141
+			expectedSha256 = prevRootMeta.Hashes["sha256"]
142
+		}
139 143
 	}
140 144
 
141 145
 	// if we're bootstrapping we may not have a cached root, an
... ...
@@ -178,6 +175,7 @@ func (c *Client) downloadRoot() error {
178 178
 	var s *data.Signed
179 179
 	var raw []byte
180 180
 	if download {
181
+		// use consistent download if we have the checksum.
181 182
 		raw, s, err = c.downloadSigned(role, size, expectedSha256)
182 183
 		if err != nil {
183 184
 			return err
... ...
@@ -201,34 +199,45 @@ func (c *Client) downloadRoot() error {
201 201
 
202 202
 func (c Client) verifyRoot(role string, s *data.Signed, minVersion int) error {
203 203
 	// this will confirm that the root has been signed by the old root role
204
-	// as c.keysDB contains the root keys we bootstrapped with.
204
+	// with the root keys we bootstrapped with.
205 205
 	// Still need to determine if there has been a root key update and
206 206
 	// confirm signature with new root key
207 207
 	logrus.Debug("verifying root with existing keys")
208
-	err := signed.Verify(s, role, minVersion, c.keysDB)
208
+	rootRole, err := c.local.GetBaseRole(role)
209 209
 	if err != nil {
210
+		logrus.Debug("no previous root role loaded")
211
+		return err
212
+	}
213
+	// Verify using the rootRole loaded from the known root.json
214
+	if err = signed.Verify(s, rootRole, minVersion); err != nil {
210 215
 		logrus.Debug("root did not verify with existing keys")
211 216
 		return err
212 217
 	}
213 218
 
214
-	// This will cause keyDB to get updated, overwriting any keyIDs associated
215
-	// with the roles in root.json
216 219
 	logrus.Debug("updating known root roles and keys")
217 220
 	root, err := data.RootFromSigned(s)
218 221
 	if err != nil {
219 222
 		logrus.Error(err.Error())
220 223
 		return err
221 224
 	}
225
+	// replace the existing root.json with the new one (just in memory, we
226
+	// have another validation step before we fully accept the new root)
222 227
 	err = c.local.SetRoot(root)
223 228
 	if err != nil {
224 229
 		logrus.Error(err.Error())
225 230
 		return err
226 231
 	}
227
-	// verify again now that the old keys have been replaced with the new keys.
232
+	// Verify the new root again having loaded the rootRole out of this new
233
+	// file (verifies self-referential integrity)
228 234
 	// TODO(endophage): be more intelligent and only re-verify if we detect
229 235
 	//                  there has been a change in root keys
230 236
 	logrus.Debug("verifying root with updated keys")
231
-	err = signed.Verify(s, role, minVersion, c.keysDB)
237
+	rootRole, err = c.local.GetBaseRole(role)
238
+	if err != nil {
239
+		logrus.Debug("root role with new keys not loaded")
240
+		return err
241
+	}
242
+	err = signed.Verify(s, rootRole, minVersion)
232 243
 	if err != nil {
233 244
 		logrus.Debug("root did not verify with new keys")
234 245
 		return err
... ...
@@ -248,11 +257,11 @@ func (c *Client) downloadTimestamp() error {
248 248
 	// we're interacting with the repo. This will result in the
249 249
 	// version being 0
250 250
 	var (
251
-		saveToCache bool
252
-		old         *data.Signed
253
-		version     = 0
251
+		old     *data.Signed
252
+		ts      *data.SignedTimestamp
253
+		version = 0
254 254
 	)
255
-	cachedTS, err := c.cache.GetMeta(role, maxSize)
255
+	cachedTS, err := c.cache.GetMeta(role, notary.MaxTimestampSize)
256 256
 	if err == nil {
257 257
 		cached := &data.Signed{}
258 258
 		err := json.Unmarshal(cachedTS, cached)
... ...
@@ -266,49 +275,56 @@ func (c *Client) downloadTimestamp() error {
266 266
 	}
267 267
 	// unlike root, targets and snapshot, always try and download timestamps
268 268
 	// from remote, only using the cache one if we couldn't reach remote.
269
-	raw, s, err := c.downloadSigned(role, maxSize, nil)
270
-	if err != nil || len(raw) == 0 {
271
-		if old == nil {
272
-			if err == nil {
273
-				// couldn't retrieve data from server and don't have valid
274
-				// data in cache.
275
-				return store.ErrMetaNotFound{Resource: data.CanonicalTimestampRole}
276
-			}
277
-			return err
269
+	raw, s, err := c.downloadSigned(role, notary.MaxTimestampSize, nil)
270
+	if err == nil {
271
+		ts, err = c.verifyTimestamp(s, version)
272
+		if err == nil {
273
+			logrus.Debug("successfully verified downloaded timestamp")
274
+			c.cache.SetMeta(role, raw)
275
+			c.local.SetTimestamp(ts)
276
+			return nil
278 277
 		}
279
-		logrus.Debug(err.Error())
280
-		logrus.Warn("Error while downloading remote metadata, using cached timestamp - this might not be the latest version available remotely")
281
-		s = old
282
-	} else {
283
-		saveToCache = true
284 278
 	}
285
-	err = signed.Verify(s, role, version, c.keysDB)
286
-	if err != nil {
279
+	if old == nil {
280
+		// couldn't retrieve valid data from server and don't have unmarshallable data in cache.
281
+		logrus.Debug("no cached timestamp available")
287 282
 		return err
288 283
 	}
289
-	logrus.Debug("successfully verified timestamp")
290
-	if saveToCache {
291
-		c.cache.SetMeta(role, raw)
292
-	}
293
-	ts, err := data.TimestampFromSigned(s)
284
+	logrus.Debug(err.Error())
285
+	logrus.Warn("Error while downloading remote metadata, using cached timestamp - this might not be the latest version available remotely")
286
+	ts, err = c.verifyTimestamp(old, version)
294 287
 	if err != nil {
295 288
 		return err
296 289
 	}
290
+	logrus.Debug("successfully verified cached timestamp")
297 291
 	c.local.SetTimestamp(ts)
298 292
 	return nil
299 293
 }
300 294
 
295
+// verifies that a timestamp is valid, and returned the SignedTimestamp object to add to the tuf repo
296
+func (c *Client) verifyTimestamp(s *data.Signed, minVersion int) (*data.SignedTimestamp, error) {
297
+	timestampRole, err := c.local.GetBaseRole(data.CanonicalTimestampRole)
298
+	if err != nil {
299
+		logrus.Debug("no timestamp role loaded")
300
+		return nil, err
301
+	}
302
+	if err := signed.Verify(s, timestampRole, minVersion); err != nil {
303
+		return nil, err
304
+	}
305
+	return data.TimestampFromSigned(s)
306
+}
307
+
301 308
 // downloadSnapshot is responsible for downloading the snapshot.json
302 309
 func (c *Client) downloadSnapshot() error {
303 310
 	logrus.Debug("Downloading Snapshot...")
304 311
 	role := data.CanonicalSnapshotRole
305 312
 	if c.local.Timestamp == nil {
306
-		return ErrMissingMeta{role: "snapshot"}
313
+		return tuf.ErrNotLoaded{Role: data.CanonicalTimestampRole}
307 314
 	}
308 315
 	size := c.local.Timestamp.Signed.Meta[role].Length
309 316
 	expectedSha256, ok := c.local.Timestamp.Signed.Meta[role].Hashes["sha256"]
310 317
 	if !ok {
311
-		return ErrMissingMeta{role: "snapshot"}
318
+		return data.ErrMissingMeta{Role: "snapshot"}
312 319
 	}
313 320
 
314 321
 	var download bool
... ...
@@ -350,7 +366,12 @@ func (c *Client) downloadSnapshot() error {
350 350
 		s = old
351 351
 	}
352 352
 
353
-	err = signed.Verify(s, role, version, c.keysDB)
353
+	snapshotRole, err := c.local.GetBaseRole(role)
354
+	if err != nil {
355
+		logrus.Debug("no snapshot role loaded")
356
+		return err
357
+	}
358
+	err = signed.Verify(s, snapshotRole, version)
354 359
 	if err != nil {
355 360
 		return err
356 361
 	}
... ...
@@ -382,18 +403,14 @@ func (c *Client) downloadTargets(role string) error {
382 382
 			return err
383 383
 		}
384 384
 		if c.local.Snapshot == nil {
385
-			return ErrMissingMeta{role: role}
385
+			return tuf.ErrNotLoaded{Role: data.CanonicalSnapshotRole}
386 386
 		}
387 387
 		snap := c.local.Snapshot.Signed
388 388
 		root := c.local.Root.Signed
389
-		r := c.keysDB.GetRole(role)
390
-		if r == nil {
391
-			return fmt.Errorf("Invalid role: %s", role)
392
-		}
393
-		keyIDs := r.KeyIDs
394
-		s, err := c.getTargetsFile(role, keyIDs, snap.Meta, root.ConsistentSnapshot, r.Threshold)
389
+
390
+		s, err := c.getTargetsFile(role, snap.Meta, root.ConsistentSnapshot)
395 391
 		if err != nil {
396
-			if _, ok := err.(ErrMissingMeta); ok && role != data.CanonicalTargetsRole {
392
+			if _, ok := err.(data.ErrMissingMeta); ok && role != data.CanonicalTargetsRole {
397 393
 				// if the role meta hasn't been published,
398 394
 				// that's ok, continue
399 395
 				continue
... ...
@@ -401,7 +418,7 @@ func (c *Client) downloadTargets(role string) error {
401 401
 			logrus.Error("Error getting targets file:", err)
402 402
 			return err
403 403
 		}
404
-		t, err := data.TargetsFromSigned(s)
404
+		t, err := data.TargetsFromSigned(s, role)
405 405
 		if err != nil {
406 406
 			return err
407 407
 		}
... ...
@@ -412,14 +429,19 @@ func (c *Client) downloadTargets(role string) error {
412 412
 
413 413
 		// push delegated roles contained in the targets file onto the stack
414 414
 		for _, r := range t.Signed.Delegations.Roles {
415
-			stack.Push(r.Name)
415
+			if path.Dir(r.Name) == role {
416
+				// only load children that are direct 1st generation descendants
417
+				// of the role we've just downloaded
418
+				stack.Push(r.Name)
419
+			}
416 420
 		}
417 421
 	}
418 422
 	return nil
419 423
 }
420 424
 
421 425
 func (c *Client) downloadSigned(role string, size int64, expectedSha256 []byte) ([]byte, *data.Signed, error) {
422
-	raw, err := c.remote.GetMeta(role, size)
426
+	rolePath := utils.ConsistentName(role, expectedSha256)
427
+	raw, err := c.remote.GetMeta(rolePath, size)
423 428
 	if err != nil {
424 429
 		return nil, nil, err
425 430
 	}
... ...
@@ -437,15 +459,15 @@ func (c *Client) downloadSigned(role string, size int64, expectedSha256 []byte)
437 437
 	return raw, s, nil
438 438
 }
439 439
 
440
-func (c Client) getTargetsFile(role string, keyIDs []string, snapshotMeta data.Files, consistent bool, threshold int) (*data.Signed, error) {
440
+func (c Client) getTargetsFile(role string, snapshotMeta data.Files, consistent bool) (*data.Signed, error) {
441 441
 	// require role exists in snapshots
442 442
 	roleMeta, ok := snapshotMeta[role]
443 443
 	if !ok {
444
-		return nil, ErrMissingMeta{role: role}
444
+		return nil, data.ErrMissingMeta{Role: role}
445 445
 	}
446 446
 	expectedSha256, ok := snapshotMeta[role].Hashes["sha256"]
447 447
 	if !ok {
448
-		return nil, ErrMissingMeta{role: role}
448
+		return nil, data.ErrMissingMeta{Role: role}
449 449
 	}
450 450
 
451 451
 	// try to get meta file from content addressed cache
... ...
@@ -464,7 +486,7 @@ func (c Client) getTargetsFile(role string, keyIDs []string, snapshotMeta data.F
464 464
 		}
465 465
 		err := json.Unmarshal(raw, old)
466 466
 		if err == nil {
467
-			targ, err := data.TargetsFromSigned(old)
467
+			targ, err := data.TargetsFromSigned(old, role)
468 468
 			if err == nil {
469 469
 				version = targ.Signed.Version
470 470
 			} else {
... ...
@@ -478,21 +500,30 @@ func (c Client) getTargetsFile(role string, keyIDs []string, snapshotMeta data.F
478 478
 	size := snapshotMeta[role].Length
479 479
 	var s *data.Signed
480 480
 	if download {
481
-		rolePath, err := c.RoleTargetsPath(role, hex.EncodeToString(expectedSha256), consistent)
481
+		raw, s, err = c.downloadSigned(role, size, expectedSha256)
482 482
 		if err != nil {
483 483
 			return nil, err
484 484
 		}
485
-		raw, s, err = c.downloadSigned(rolePath, size, expectedSha256)
485
+	} else {
486
+		logrus.Debug("using cached ", role)
487
+		s = old
488
+	}
489
+	var targetOrDelgRole data.BaseRole
490
+	if data.IsDelegation(role) {
491
+		delgRole, err := c.local.GetDelegationRole(role)
486 492
 		if err != nil {
493
+			logrus.Debugf("no %s delegation role loaded", role)
487 494
 			return nil, err
488 495
 		}
496
+		targetOrDelgRole = delgRole.BaseRole
489 497
 	} else {
490
-		logrus.Debug("using cached ", role)
491
-		s = old
498
+		targetOrDelgRole, err = c.local.GetBaseRole(role)
499
+		if err != nil {
500
+			logrus.Debugf("no %s role loaded", role)
501
+			return nil, err
502
+		}
492 503
 	}
493
-
494
-	err = signed.Verify(s, role, version, c.keysDB)
495
-	if err != nil {
504
+	if err = signed.Verify(s, targetOrDelgRole, version); err != nil {
496 505
 		return nil, err
497 506
 	}
498 507
 	logrus.Debugf("successfully verified %s", role)
... ...
@@ -505,73 +536,3 @@ func (c Client) getTargetsFile(role string, keyIDs []string, snapshotMeta data.F
505 505
 	}
506 506
 	return s, nil
507 507
 }
508
-
509
-// RoleTargetsPath generates the appropriate HTTP URL for the targets file,
510
-// based on whether the repo is marked as consistent.
511
-func (c Client) RoleTargetsPath(role string, hashSha256 string, consistent bool) (string, error) {
512
-	if consistent {
513
-		// Use path instead of filepath since we refer to the TUF role directly instead of its target files
514
-		dir := path.Dir(role)
515
-		if strings.Contains(role, "/") {
516
-			lastSlashIdx := strings.LastIndex(role, "/")
517
-			role = role[lastSlashIdx+1:]
518
-		}
519
-		role = path.Join(
520
-			dir,
521
-			fmt.Sprintf("%s.%s.json", hashSha256, role),
522
-		)
523
-	}
524
-	return role, nil
525
-}
526
-
527
-// TargetMeta ensures the repo is up to date. It assumes downloadTargets
528
-// has already downloaded all delegated roles
529
-func (c Client) TargetMeta(role, path string, excludeRoles ...string) (*data.FileMeta, string) {
530
-	excl := make(map[string]bool)
531
-	for _, r := range excludeRoles {
532
-		excl[r] = true
533
-	}
534
-
535
-	pathDigest := sha256.Sum256([]byte(path))
536
-	pathHex := hex.EncodeToString(pathDigest[:])
537
-
538
-	// FIFO list of targets delegations to inspect for target
539
-	roles := []string{role}
540
-	var (
541
-		meta *data.FileMeta
542
-		curr string
543
-	)
544
-	for len(roles) > 0 {
545
-		// have to do these lines here because of order of execution in for statement
546
-		curr = roles[0]
547
-		roles = roles[1:]
548
-
549
-		meta = c.local.TargetMeta(curr, path)
550
-		if meta != nil {
551
-			// we found the target!
552
-			return meta, curr
553
-		}
554
-		delegations := c.local.TargetDelegations(curr, path, pathHex)
555
-		for _, d := range delegations {
556
-			if !excl[d.Name] {
557
-				roles = append(roles, d.Name)
558
-			}
559
-		}
560
-	}
561
-	return meta, ""
562
-}
563
-
564
-// DownloadTarget downloads the target to dst from the remote
565
-func (c Client) DownloadTarget(dst io.Writer, path string, meta *data.FileMeta) error {
566
-	reader, err := c.remote.GetTarget(path)
567
-	if err != nil {
568
-		return err
569
-	}
570
-	defer reader.Close()
571
-	r := io.TeeReader(
572
-		io.LimitReader(reader, meta.Length),
573
-		dst,
574
-	)
575
-	err = utils.ValidateTarget(r, meta)
576
-	return err
577
-}
... ...
@@ -13,15 +13,6 @@ func (e ErrChecksumMismatch) Error() string {
13 13
 	return fmt.Sprintf("tuf: checksum for %s did not match", e.role)
14 14
 }
15 15
 
16
-// ErrMissingMeta - couldn't find the FileMeta object for a role or target
17
-type ErrMissingMeta struct {
18
-	role string
19
-}
20
-
21
-func (e ErrMissingMeta) Error() string {
22
-	return fmt.Sprintf("tuf: sha256 checksum required for %s", e.role)
23
-}
24
-
25 16
 // ErrCorruptedCache - local data is incorrect
26 17
 type ErrCorruptedCache struct {
27 18
 	file string
28 19
new file mode 100644
... ...
@@ -0,0 +1,22 @@
0
+package data
1
+
2
+import "fmt"
3
+
4
+// ErrInvalidMetadata is the error to be returned when metadata is invalid
5
+type ErrInvalidMetadata struct {
6
+	role string
7
+	msg  string
8
+}
9
+
10
+func (e ErrInvalidMetadata) Error() string {
11
+	return fmt.Sprintf("%s type metadata invalid: %s", e.role, e.msg)
12
+}
13
+
14
+// ErrMissingMeta - couldn't find the FileMeta object for a role or target
15
+type ErrMissingMeta struct {
16
+	Role string
17
+}
18
+
19
+func (e ErrMissingMeta) Error() string {
20
+	return fmt.Sprintf("tuf: sha256 checksum required for %s", e.Role)
21
+}
... ...
@@ -46,7 +46,7 @@ type Keys map[string]PublicKey
46 46
 
47 47
 // UnmarshalJSON implements the json.Unmarshaller interface
48 48
 func (ks *Keys) UnmarshalJSON(data []byte) error {
49
-	parsed := make(map[string]tufKey)
49
+	parsed := make(map[string]TUFKey)
50 50
 	err := json.Unmarshal(data, &parsed)
51 51
 	if err != nil {
52 52
 		return err
... ...
@@ -64,7 +64,7 @@ type KeyList []PublicKey
64 64
 
65 65
 // UnmarshalJSON implements the json.Unmarshaller interface
66 66
 func (ks *KeyList) UnmarshalJSON(data []byte) error {
67
-	parsed := make([]tufKey, 0, 1)
67
+	parsed := make([]TUFKey, 0, 1)
68 68
 	err := json.Unmarshal(data, &parsed)
69 69
 	if err != nil {
70 70
 		return err
... ...
@@ -86,64 +86,64 @@ func (ks KeyList) IDs() []string {
86 86
 	return keyIDs
87 87
 }
88 88
 
89
-func typedPublicKey(tk tufKey) PublicKey {
89
+func typedPublicKey(tk TUFKey) PublicKey {
90 90
 	switch tk.Algorithm() {
91 91
 	case ECDSAKey:
92
-		return &ECDSAPublicKey{tufKey: tk}
92
+		return &ECDSAPublicKey{TUFKey: tk}
93 93
 	case ECDSAx509Key:
94
-		return &ECDSAx509PublicKey{tufKey: tk}
94
+		return &ECDSAx509PublicKey{TUFKey: tk}
95 95
 	case RSAKey:
96
-		return &RSAPublicKey{tufKey: tk}
96
+		return &RSAPublicKey{TUFKey: tk}
97 97
 	case RSAx509Key:
98
-		return &RSAx509PublicKey{tufKey: tk}
98
+		return &RSAx509PublicKey{TUFKey: tk}
99 99
 	case ED25519Key:
100
-		return &ED25519PublicKey{tufKey: tk}
100
+		return &ED25519PublicKey{TUFKey: tk}
101 101
 	}
102
-	return &UnknownPublicKey{tufKey: tk}
102
+	return &UnknownPublicKey{TUFKey: tk}
103 103
 }
104 104
 
105
-func typedPrivateKey(tk tufKey) (PrivateKey, error) {
105
+func typedPrivateKey(tk TUFKey) (PrivateKey, error) {
106 106
 	private := tk.Value.Private
107 107
 	tk.Value.Private = nil
108 108
 	switch tk.Algorithm() {
109 109
 	case ECDSAKey:
110 110
 		return NewECDSAPrivateKey(
111 111
 			&ECDSAPublicKey{
112
-				tufKey: tk,
112
+				TUFKey: tk,
113 113
 			},
114 114
 			private,
115 115
 		)
116 116
 	case ECDSAx509Key:
117 117
 		return NewECDSAPrivateKey(
118 118
 			&ECDSAx509PublicKey{
119
-				tufKey: tk,
119
+				TUFKey: tk,
120 120
 			},
121 121
 			private,
122 122
 		)
123 123
 	case RSAKey:
124 124
 		return NewRSAPrivateKey(
125 125
 			&RSAPublicKey{
126
-				tufKey: tk,
126
+				TUFKey: tk,
127 127
 			},
128 128
 			private,
129 129
 		)
130 130
 	case RSAx509Key:
131 131
 		return NewRSAPrivateKey(
132 132
 			&RSAx509PublicKey{
133
-				tufKey: tk,
133
+				TUFKey: tk,
134 134
 			},
135 135
 			private,
136 136
 		)
137 137
 	case ED25519Key:
138 138
 		return NewED25519PrivateKey(
139 139
 			ED25519PublicKey{
140
-				tufKey: tk,
140
+				TUFKey: tk,
141 141
 			},
142 142
 			private,
143 143
 		)
144 144
 	}
145 145
 	return &UnknownPrivateKey{
146
-		tufKey:     tk,
146
+		TUFKey:     tk,
147 147
 		privateKey: privateKey{private: private},
148 148
 	}, nil
149 149
 }
... ...
@@ -151,7 +151,7 @@ func typedPrivateKey(tk tufKey) (PrivateKey, error) {
151 151
 // NewPublicKey creates a new, correctly typed PublicKey, using the
152 152
 // UnknownPublicKey catchall for unsupported ciphers
153 153
 func NewPublicKey(alg string, public []byte) PublicKey {
154
-	tk := tufKey{
154
+	tk := TUFKey{
155 155
 		Type: alg,
156 156
 		Value: KeyPair{
157 157
 			Public: public,
... ...
@@ -163,7 +163,7 @@ func NewPublicKey(alg string, public []byte) PublicKey {
163 163
 // NewPrivateKey creates a new, correctly typed PrivateKey, using the
164 164
 // UnknownPrivateKey catchall for unsupported ciphers
165 165
 func NewPrivateKey(pubKey PublicKey, private []byte) (PrivateKey, error) {
166
-	tk := tufKey{
166
+	tk := TUFKey{
167 167
 		Type: pubKey.Algorithm(),
168 168
 		Value: KeyPair{
169 169
 			Public:  pubKey.Public(),
... ...
@@ -175,7 +175,7 @@ func NewPrivateKey(pubKey PublicKey, private []byte) (PrivateKey, error) {
175 175
 
176 176
 // UnmarshalPublicKey is used to parse individual public keys in JSON
177 177
 func UnmarshalPublicKey(data []byte) (PublicKey, error) {
178
-	var parsed tufKey
178
+	var parsed TUFKey
179 179
 	err := json.Unmarshal(data, &parsed)
180 180
 	if err != nil {
181 181
 		return nil, err
... ...
@@ -185,7 +185,7 @@ func UnmarshalPublicKey(data []byte) (PublicKey, error) {
185 185
 
186 186
 // UnmarshalPrivateKey is used to parse individual private keys in JSON
187 187
 func UnmarshalPrivateKey(data []byte) (PrivateKey, error) {
188
-	var parsed tufKey
188
+	var parsed TUFKey
189 189
 	err := json.Unmarshal(data, &parsed)
190 190
 	if err != nil {
191 191
 		return nil, err
... ...
@@ -193,26 +193,26 @@ func UnmarshalPrivateKey(data []byte) (PrivateKey, error) {
193 193
 	return typedPrivateKey(parsed)
194 194
 }
195 195
 
196
-// tufKey is the structure used for both public and private keys in TUF.
196
+// TUFKey is the structure used for both public and private keys in TUF.
197 197
 // Normally it would make sense to use a different structures for public and
198 198
 // private keys, but that would change the key ID algorithm (since the canonical
199 199
 // JSON would be different). This structure should normally be accessed through
200 200
 // the PublicKey or PrivateKey interfaces.
201
-type tufKey struct {
201
+type TUFKey struct {
202 202
 	id    string
203 203
 	Type  string  `json:"keytype"`
204 204
 	Value KeyPair `json:"keyval"`
205 205
 }
206 206
 
207 207
 // Algorithm returns the algorithm of the key
208
-func (k tufKey) Algorithm() string {
208
+func (k TUFKey) Algorithm() string {
209 209
 	return k.Type
210 210
 }
211 211
 
212 212
 // ID efficiently generates if necessary, and caches the ID of the key
213
-func (k *tufKey) ID() string {
213
+func (k *TUFKey) ID() string {
214 214
 	if k.id == "" {
215
-		pubK := tufKey{
215
+		pubK := TUFKey{
216 216
 			Type: k.Algorithm(),
217 217
 			Value: KeyPair{
218 218
 				Public:  k.Public(),
... ...
@@ -230,7 +230,7 @@ func (k *tufKey) ID() string {
230 230
 }
231 231
 
232 232
 // Public returns the public bytes
233
-func (k tufKey) Public() []byte {
233
+func (k TUFKey) Public() []byte {
234 234
 	return k.Value.Public
235 235
 }
236 236
 
... ...
@@ -239,42 +239,42 @@ func (k tufKey) Public() []byte {
239 239
 // ECDSAPublicKey represents an ECDSA key using a raw serialization
240 240
 // of the public key
241 241
 type ECDSAPublicKey struct {
242
-	tufKey
242
+	TUFKey
243 243
 }
244 244
 
245 245
 // ECDSAx509PublicKey represents an ECDSA key using an x509 cert
246 246
 // as the serialized format of the public key
247 247
 type ECDSAx509PublicKey struct {
248
-	tufKey
248
+	TUFKey
249 249
 }
250 250
 
251 251
 // RSAPublicKey represents an RSA key using a raw serialization
252 252
 // of the public key
253 253
 type RSAPublicKey struct {
254
-	tufKey
254
+	TUFKey
255 255
 }
256 256
 
257 257
 // RSAx509PublicKey represents an RSA key using an x509 cert
258 258
 // as the serialized format of the public key
259 259
 type RSAx509PublicKey struct {
260
-	tufKey
260
+	TUFKey
261 261
 }
262 262
 
263 263
 // ED25519PublicKey represents an ED25519 key using a raw serialization
264 264
 // of the public key
265 265
 type ED25519PublicKey struct {
266
-	tufKey
266
+	TUFKey
267 267
 }
268 268
 
269 269
 // UnknownPublicKey is a catchall for key types that are not supported
270 270
 type UnknownPublicKey struct {
271
-	tufKey
271
+	TUFKey
272 272
 }
273 273
 
274 274
 // NewECDSAPublicKey initializes a new public key with the ECDSAKey type
275 275
 func NewECDSAPublicKey(public []byte) *ECDSAPublicKey {
276 276
 	return &ECDSAPublicKey{
277
-		tufKey: tufKey{
277
+		TUFKey: TUFKey{
278 278
 			Type: ECDSAKey,
279 279
 			Value: KeyPair{
280 280
 				Public:  public,
... ...
@@ -287,7 +287,7 @@ func NewECDSAPublicKey(public []byte) *ECDSAPublicKey {
287 287
 // NewECDSAx509PublicKey initializes a new public key with the ECDSAx509Key type
288 288
 func NewECDSAx509PublicKey(public []byte) *ECDSAx509PublicKey {
289 289
 	return &ECDSAx509PublicKey{
290
-		tufKey: tufKey{
290
+		TUFKey: TUFKey{
291 291
 			Type: ECDSAx509Key,
292 292
 			Value: KeyPair{
293 293
 				Public:  public,
... ...
@@ -300,7 +300,7 @@ func NewECDSAx509PublicKey(public []byte) *ECDSAx509PublicKey {
300 300
 // NewRSAPublicKey initializes a new public key with the RSA type
301 301
 func NewRSAPublicKey(public []byte) *RSAPublicKey {
302 302
 	return &RSAPublicKey{
303
-		tufKey: tufKey{
303
+		TUFKey: TUFKey{
304 304
 			Type: RSAKey,
305 305
 			Value: KeyPair{
306 306
 				Public:  public,
... ...
@@ -313,7 +313,7 @@ func NewRSAPublicKey(public []byte) *RSAPublicKey {
313 313
 // NewRSAx509PublicKey initializes a new public key with the RSAx509Key type
314 314
 func NewRSAx509PublicKey(public []byte) *RSAx509PublicKey {
315 315
 	return &RSAx509PublicKey{
316
-		tufKey: tufKey{
316
+		TUFKey: TUFKey{
317 317
 			Type: RSAx509Key,
318 318
 			Value: KeyPair{
319 319
 				Public:  public,
... ...
@@ -326,7 +326,7 @@ func NewRSAx509PublicKey(public []byte) *RSAx509PublicKey {
326 326
 // NewED25519PublicKey initializes a new public key with the ED25519Key type
327 327
 func NewED25519PublicKey(public []byte) *ED25519PublicKey {
328 328
 	return &ED25519PublicKey{
329
-		tufKey: tufKey{
329
+		TUFKey: TUFKey{
330 330
 			Type: ED25519Key,
331 331
 			Value: KeyPair{
332 332
 				Public:  public,
... ...
@@ -367,7 +367,7 @@ type ED25519PrivateKey struct {
367 367
 
368 368
 // UnknownPrivateKey is a catchall for unsupported key types
369 369
 type UnknownPrivateKey struct {
370
-	tufKey
370
+	TUFKey
371 371
 	privateKey
372 372
 }
373 373
 
... ...
@@ -515,10 +515,10 @@ func (k UnknownPrivateKey) SignatureAlgorithm() SigAlgorithm {
515 515
 	return ""
516 516
 }
517 517
 
518
-// PublicKeyFromPrivate returns a new tufKey based on a private key, with
518
+// PublicKeyFromPrivate returns a new TUFKey based on a private key, with
519 519
 // the private key bytes guaranteed to be nil.
520 520
 func PublicKeyFromPrivate(pk PrivateKey) PublicKey {
521
-	return typedPublicKey(tufKey{
521
+	return typedPublicKey(TUFKey{
522 522
 		Type: pk.Algorithm(),
523 523
 		Value: KeyPair{
524 524
 			Public:  pk.Public(),
... ...
@@ -2,10 +2,11 @@ package data
2 2
 
3 3
 import (
4 4
 	"fmt"
5
-	"github.com/Sirupsen/logrus"
6 5
 	"path"
7 6
 	"regexp"
8 7
 	"strings"
8
+
9
+	"github.com/Sirupsen/logrus"
9 10
 )
10 11
 
11 12
 // Canonical base role names
... ...
@@ -85,32 +86,139 @@ func IsDelegation(role string) bool {
85 85
 		isClean
86 86
 }
87 87
 
88
+// BaseRole is an internal representation of a root/targets/snapshot/timestamp role, with its public keys included
89
+type BaseRole struct {
90
+	Keys      map[string]PublicKey
91
+	Name      string
92
+	Threshold int
93
+}
94
+
95
+// NewBaseRole creates a new BaseRole object with the provided parameters
96
+func NewBaseRole(name string, threshold int, keys ...PublicKey) BaseRole {
97
+	r := BaseRole{
98
+		Name:      name,
99
+		Threshold: threshold,
100
+		Keys:      make(map[string]PublicKey),
101
+	}
102
+	for _, k := range keys {
103
+		r.Keys[k.ID()] = k
104
+	}
105
+	return r
106
+}
107
+
108
+// ListKeys retrieves the public keys valid for this role
109
+func (b BaseRole) ListKeys() KeyList {
110
+	return listKeys(b.Keys)
111
+}
112
+
113
+// ListKeyIDs retrieves the list of key IDs valid for this role
114
+func (b BaseRole) ListKeyIDs() []string {
115
+	return listKeyIDs(b.Keys)
116
+}
117
+
118
+// DelegationRole is an internal representation of a delegation role, with its public keys included
119
+type DelegationRole struct {
120
+	BaseRole
121
+	Paths []string
122
+}
123
+
124
+func listKeys(keyMap map[string]PublicKey) KeyList {
125
+	keys := KeyList{}
126
+	for _, key := range keyMap {
127
+		keys = append(keys, key)
128
+	}
129
+	return keys
130
+}
131
+
132
+func listKeyIDs(keyMap map[string]PublicKey) []string {
133
+	keyIDs := []string{}
134
+	for id := range keyMap {
135
+		keyIDs = append(keyIDs, id)
136
+	}
137
+	return keyIDs
138
+}
139
+
140
+// Restrict restricts the paths and path hash prefixes for the passed in delegation role,
141
+// returning a copy of the role with validated paths as if it was a direct child
142
+func (d DelegationRole) Restrict(child DelegationRole) (DelegationRole, error) {
143
+	if !d.IsParentOf(child) {
144
+		return DelegationRole{}, fmt.Errorf("%s is not a parent of %s", d.Name, child.Name)
145
+	}
146
+	return DelegationRole{
147
+		BaseRole: BaseRole{
148
+			Keys:      child.Keys,
149
+			Name:      child.Name,
150
+			Threshold: child.Threshold,
151
+		},
152
+		Paths: RestrictDelegationPathPrefixes(d.Paths, child.Paths),
153
+	}, nil
154
+}
155
+
156
+// IsParentOf returns whether the passed in delegation role is the direct child of this role,
157
+// determined by delegation name.
158
+// Ex: targets/a is a direct parent of targets/a/b, but targets/a is not a direct parent of targets/a/b/c
159
+func (d DelegationRole) IsParentOf(child DelegationRole) bool {
160
+	return path.Dir(child.Name) == d.Name
161
+}
162
+
163
+// CheckPaths checks if a given path is valid for the role
164
+func (d DelegationRole) CheckPaths(path string) bool {
165
+	return checkPaths(path, d.Paths)
166
+}
167
+
168
+func checkPaths(path string, permitted []string) bool {
169
+	for _, p := range permitted {
170
+		if strings.HasPrefix(path, p) {
171
+			return true
172
+		}
173
+	}
174
+	return false
175
+}
176
+
177
+// RestrictDelegationPathPrefixes returns the list of valid delegationPaths that are prefixed by parentPaths
178
+func RestrictDelegationPathPrefixes(parentPaths, delegationPaths []string) []string {
179
+	validPaths := []string{}
180
+	if len(delegationPaths) == 0 {
181
+		return validPaths
182
+	}
183
+
184
+	// Validate each individual delegation path
185
+	for _, delgPath := range delegationPaths {
186
+		isPrefixed := false
187
+		for _, parentPath := range parentPaths {
188
+			if strings.HasPrefix(delgPath, parentPath) {
189
+				isPrefixed = true
190
+				break
191
+			}
192
+		}
193
+		// If the delegation path did not match prefix against any parent path, it is not valid
194
+		if isPrefixed {
195
+			validPaths = append(validPaths, delgPath)
196
+		}
197
+	}
198
+	return validPaths
199
+}
200
+
88 201
 // RootRole is a cut down role as it appears in the root.json
202
+// Eventually should only be used for immediately before and after serialization/deserialization
89 203
 type RootRole struct {
90 204
 	KeyIDs    []string `json:"keyids"`
91 205
 	Threshold int      `json:"threshold"`
92 206
 }
93 207
 
94 208
 // Role is a more verbose role as they appear in targets delegations
209
+// Eventually should only be used for immediately before and after serialization/deserialization
95 210
 type Role struct {
96 211
 	RootRole
97
-	Name             string   `json:"name"`
98
-	Paths            []string `json:"paths,omitempty"`
99
-	PathHashPrefixes []string `json:"path_hash_prefixes,omitempty"`
100
-	Email            string   `json:"email,omitempty"`
212
+	Name  string   `json:"name"`
213
+	Paths []string `json:"paths,omitempty"`
101 214
 }
102 215
 
103 216
 // NewRole creates a new Role object from the given parameters
104
-func NewRole(name string, threshold int, keyIDs, paths, pathHashPrefixes []string) (*Role, error) {
105
-	if len(paths) > 0 && len(pathHashPrefixes) > 0 {
106
-		return nil, ErrInvalidRole{
107
-			Role:   name,
108
-			Reason: "roles may not have both Paths and PathHashPrefixes",
109
-		}
110
-	}
217
+func NewRole(name string, threshold int, keyIDs, paths []string) (*Role, error) {
111 218
 	if IsDelegation(name) {
112
-		if len(paths) == 0 && len(pathHashPrefixes) == 0 {
113
-			logrus.Debugf("role %s with no Paths and no PathHashPrefixes will never be able to publish content until one or more are added", name)
219
+		if len(paths) == 0 {
220
+			logrus.Debugf("role %s with no Paths will never be able to publish content until one or more are added", name)
114 221
 		}
115 222
 	}
116 223
 	if threshold < 1 {
... ...
@@ -124,52 +232,15 @@ func NewRole(name string, threshold int, keyIDs, paths, pathHashPrefixes []strin
124 124
 			KeyIDs:    keyIDs,
125 125
 			Threshold: threshold,
126 126
 		},
127
-		Name:             name,
128
-		Paths:            paths,
129
-		PathHashPrefixes: pathHashPrefixes,
127
+		Name:  name,
128
+		Paths: paths,
130 129
 	}, nil
131 130
 
132 131
 }
133 132
 
134
-// IsValid checks if the role has defined both paths and path hash prefixes,
135
-// having both is invalid
136
-func (r Role) IsValid() bool {
137
-	return !(len(r.Paths) > 0 && len(r.PathHashPrefixes) > 0)
138
-}
139
-
140
-// ValidKey checks if the given id is a recognized signing key for the role
141
-func (r Role) ValidKey(id string) bool {
142
-	for _, key := range r.KeyIDs {
143
-		if key == id {
144
-			return true
145
-		}
146
-	}
147
-	return false
148
-}
149
-
150 133
 // CheckPaths checks if a given path is valid for the role
151 134
 func (r Role) CheckPaths(path string) bool {
152
-	for _, p := range r.Paths {
153
-		if strings.HasPrefix(path, p) {
154
-			return true
155
-		}
156
-	}
157
-	return false
158
-}
159
-
160
-// CheckPrefixes checks if a given hash matches the prefixes for the role
161
-func (r Role) CheckPrefixes(hash string) bool {
162
-	for _, p := range r.PathHashPrefixes {
163
-		if strings.HasPrefix(hash, p) {
164
-			return true
165
-		}
166
-	}
167
-	return false
168
-}
169
-
170
-// IsDelegation checks if the role is a delegation or a root role
171
-func (r Role) IsDelegation() bool {
172
-	return IsDelegation(r.Name)
135
+	return checkPaths(path, r.Paths)
173 136
 }
174 137
 
175 138
 // AddKeys merges the ids into the current list of role key ids
... ...
@@ -182,25 +253,10 @@ func (r *Role) AddPaths(paths []string) error {
182 182
 	if len(paths) == 0 {
183 183
 		return nil
184 184
 	}
185
-	if len(r.PathHashPrefixes) > 0 {
186
-		return ErrInvalidRole{Role: r.Name, Reason: "attempted to add paths to role that already has hash prefixes"}
187
-	}
188 185
 	r.Paths = mergeStrSlices(r.Paths, paths)
189 186
 	return nil
190 187
 }
191 188
 
192
-// AddPathHashPrefixes merges the prefixes into the list of role path hash prefixes
193
-func (r *Role) AddPathHashPrefixes(prefixes []string) error {
194
-	if len(prefixes) == 0 {
195
-		return nil
196
-	}
197
-	if len(r.Paths) > 0 {
198
-		return ErrInvalidRole{Role: r.Name, Reason: "attempted to add hash prefixes to role that already has paths"}
199
-	}
200
-	r.PathHashPrefixes = mergeStrSlices(r.PathHashPrefixes, prefixes)
201
-	return nil
202
-}
203
-
204 189
 // RemoveKeys removes the ids from the current list of key ids
205 190
 func (r *Role) RemoveKeys(ids []string) {
206 191
 	r.KeyIDs = subtractStrSlices(r.KeyIDs, ids)
... ...
@@ -211,11 +267,6 @@ func (r *Role) RemovePaths(paths []string) {
211 211
 	r.Paths = subtractStrSlices(r.Paths, paths)
212 212
 }
213 213
 
214
-// RemovePathHashPrefixes removes the prefixes from the current list of path hash prefixes
215
-func (r *Role) RemovePathHashPrefixes(prefixes []string) {
216
-	r.PathHashPrefixes = subtractStrSlices(r.PathHashPrefixes, prefixes)
217
-}
218
-
219 214
 func mergeStrSlices(orig, new []string) []string {
220 215
 	have := make(map[string]bool)
221 216
 	for _, e := range orig {
... ...
@@ -1,6 +1,7 @@
1 1
 package data
2 2
 
3 3
 import (
4
+	"fmt"
4 5
 	"time"
5 6
 
6 7
 	"github.com/docker/go/canonical/json"
... ...
@@ -23,14 +24,57 @@ type Root struct {
23 23
 	ConsistentSnapshot bool                 `json:"consistent_snapshot"`
24 24
 }
25 25
 
26
+// isValidRootStructure returns an error, or nil, depending on whether the content of the struct
27
+// is valid for root metadata.  This does not check signatures or expiry, just that
28
+// the metadata content is valid.
29
+func isValidRootStructure(r Root) error {
30
+	expectedType := TUFTypes[CanonicalRootRole]
31
+	if r.Type != expectedType {
32
+		return ErrInvalidMetadata{
33
+			role: CanonicalRootRole, msg: fmt.Sprintf("expected type %s, not %s", expectedType, r.Type)}
34
+	}
35
+
36
+	// all the base roles MUST appear in the root.json - other roles are allowed,
37
+	// but other than the mirror role (not currently supported) are out of spec
38
+	for _, roleName := range BaseRoles {
39
+		roleObj, ok := r.Roles[roleName]
40
+		if !ok || roleObj == nil {
41
+			return ErrInvalidMetadata{
42
+				role: CanonicalRootRole, msg: fmt.Sprintf("missing %s role specification", roleName)}
43
+		}
44
+		if err := isValidRootRoleStructure(CanonicalRootRole, roleName, *roleObj, r.Keys); err != nil {
45
+			return err
46
+		}
47
+	}
48
+	return nil
49
+}
50
+
51
+func isValidRootRoleStructure(metaContainingRole, rootRoleName string, r RootRole, validKeys Keys) error {
52
+	if r.Threshold < 1 {
53
+		return ErrInvalidMetadata{
54
+			role: metaContainingRole,
55
+			msg:  fmt.Sprintf("invalid threshold specified for %s: %v ", rootRoleName, r.Threshold),
56
+		}
57
+	}
58
+	for _, keyID := range r.KeyIDs {
59
+		if _, ok := validKeys[keyID]; !ok {
60
+			return ErrInvalidMetadata{
61
+				role: metaContainingRole,
62
+				msg:  fmt.Sprintf("key ID %s specified in %s without corresponding key", keyID, rootRoleName),
63
+			}
64
+		}
65
+	}
66
+	return nil
67
+}
68
+
26 69
 // NewRoot initializes a new SignedRoot with a set of keys, roles, and the consistent flag
27 70
 func NewRoot(keys map[string]PublicKey, roles map[string]*RootRole, consistent bool) (*SignedRoot, error) {
28 71
 	signedRoot := &SignedRoot{
29 72
 		Signatures: make([]Signature, 0),
30 73
 		Signed: Root{
31
-			Type:               TUFTypes["root"],
74
+			Type:               TUFTypes[CanonicalRootRole],
32 75
 			Version:            0,
33
-			Expires:            DefaultExpires("root"),
76
+			Expires:            DefaultExpires(CanonicalRootRole),
34 77
 			Keys:               keys,
35 78
 			Roles:              roles,
36 79
 			ConsistentSnapshot: consistent,
... ...
@@ -41,6 +85,34 @@ func NewRoot(keys map[string]PublicKey, roles map[string]*RootRole, consistent b
41 41
 	return signedRoot, nil
42 42
 }
43 43
 
44
+// BuildBaseRole returns a copy of a BaseRole using the information in this SignedRoot for the specified role name.
45
+// Will error for invalid role name or key metadata within this SignedRoot
46
+func (r SignedRoot) BuildBaseRole(roleName string) (BaseRole, error) {
47
+	roleData, ok := r.Signed.Roles[roleName]
48
+	if !ok {
49
+		return BaseRole{}, ErrInvalidRole{Role: roleName, Reason: "role not found in root file"}
50
+	}
51
+	// Get all public keys for the base role from TUF metadata
52
+	keyIDs := roleData.KeyIDs
53
+	pubKeys := make(map[string]PublicKey)
54
+	for _, keyID := range keyIDs {
55
+		pubKey, ok := r.Signed.Keys[keyID]
56
+		if !ok {
57
+			return BaseRole{}, ErrInvalidRole{
58
+				Role:   roleName,
59
+				Reason: fmt.Sprintf("key with ID %s was not found in root metadata", keyID),
60
+			}
61
+		}
62
+		pubKeys[keyID] = pubKey
63
+	}
64
+
65
+	return BaseRole{
66
+		Name:      roleName,
67
+		Keys:      pubKeys,
68
+		Threshold: roleData.Threshold,
69
+	}, nil
70
+}
71
+
44 72
 // ToSigned partially serializes a SignedRoot for further signing
45 73
 func (r SignedRoot) ToSigned() (*Signed, error) {
46 74
 	s, err := defaultSerializer.MarshalCanonical(r.Signed)
... ...
@@ -70,11 +142,14 @@ func (r SignedRoot) MarshalJSON() ([]byte, error) {
70 70
 	return defaultSerializer.Marshal(signed)
71 71
 }
72 72
 
73
-// RootFromSigned fully unpacks a Signed object into a SignedRoot
73
+// RootFromSigned fully unpacks a Signed object into a SignedRoot and ensures
74
+// that it is a valid SignedRoot
74 75
 func RootFromSigned(s *Signed) (*SignedRoot, error) {
75 76
 	r := Root{}
76
-	err := json.Unmarshal(s.Signed, &r)
77
-	if err != nil {
77
+	if err := defaultSerializer.Unmarshal(s.Signed, &r); err != nil {
78
+		return nil, err
79
+	}
80
+	if err := isValidRootStructure(r); err != nil {
78 81
 		return nil, err
79 82
 	}
80 83
 	sigs := make([]Signature, len(s.Signatures))
... ...
@@ -2,6 +2,8 @@ package data
2 2
 
3 3
 import (
4 4
 	"bytes"
5
+	"crypto/sha256"
6
+	"fmt"
5 7
 	"time"
6 8
 
7 9
 	"github.com/Sirupsen/logrus"
... ...
@@ -23,6 +25,30 @@ type Snapshot struct {
23 23
 	Meta    Files     `json:"meta"`
24 24
 }
25 25
 
26
+// isValidSnapshotStructure returns an error, or nil, depending on whether the content of the
27
+// struct is valid for snapshot metadata.  This does not check signatures or expiry, just that
28
+// the metadata content is valid.
29
+func isValidSnapshotStructure(s Snapshot) error {
30
+	expectedType := TUFTypes[CanonicalSnapshotRole]
31
+	if s.Type != expectedType {
32
+		return ErrInvalidMetadata{
33
+			role: CanonicalSnapshotRole, msg: fmt.Sprintf("expected type %s, not %s", expectedType, s.Type)}
34
+	}
35
+
36
+	for _, role := range []string{CanonicalRootRole, CanonicalTargetsRole} {
37
+		// Meta is a map of FileMeta, so if the role isn't in the map it returns
38
+		// an empty FileMeta, which has an empty map, and you can check on keys
39
+		// from an empty map.
40
+		if checksum, ok := s.Meta[role].Hashes["sha256"]; !ok || len(checksum) != sha256.Size {
41
+			return ErrInvalidMetadata{
42
+				role: CanonicalSnapshotRole,
43
+				msg:  fmt.Sprintf("missing or invalid %s sha256 checksum information", role),
44
+			}
45
+		}
46
+	}
47
+	return nil
48
+}
49
+
26 50
 // NewSnapshot initilizes a SignedSnapshot with a given top level root
27 51
 // and targets objects
28 52
 func NewSnapshot(root *Signed, targets *Signed) (*SignedSnapshot, error) {
... ...
@@ -64,8 +90,8 @@ func (sp *SignedSnapshot) hashForRole(role string) []byte {
64 64
 }
65 65
 
66 66
 // ToSigned partially serializes a SignedSnapshot for further signing
67
-func (sp SignedSnapshot) ToSigned() (*Signed, error) {
68
-	s, err := json.MarshalCanonical(sp.Signed)
67
+func (sp *SignedSnapshot) ToSigned() (*Signed, error) {
68
+	s, err := defaultSerializer.MarshalCanonical(sp.Signed)
69 69
 	if err != nil {
70 70
 		return nil, err
71 71
 	}
... ...
@@ -88,6 +114,15 @@ func (sp *SignedSnapshot) AddMeta(role string, meta FileMeta) {
88 88
 	sp.Dirty = true
89 89
 }
90 90
 
91
+// GetMeta gets the metadata for a particular role, returning an error if it's
92
+// not found
93
+func (sp *SignedSnapshot) GetMeta(role string) (*FileMeta, error) {
94
+	if meta, ok := sp.Signed.Meta[role]; ok {
95
+		return &meta, nil
96
+	}
97
+	return nil, ErrMissingMeta{Role: role}
98
+}
99
+
91 100
 // DeleteMeta removes a role from the snapshot. If the role doesn't
92 101
 // exist in the snapshot, it's a noop.
93 102
 func (sp *SignedSnapshot) DeleteMeta(role string) {
... ...
@@ -97,11 +132,22 @@ func (sp *SignedSnapshot) DeleteMeta(role string) {
97 97
 	}
98 98
 }
99 99
 
100
+// MarshalJSON returns the serialized form of SignedSnapshot as bytes
101
+func (sp *SignedSnapshot) MarshalJSON() ([]byte, error) {
102
+	signed, err := sp.ToSigned()
103
+	if err != nil {
104
+		return nil, err
105
+	}
106
+	return defaultSerializer.Marshal(signed)
107
+}
108
+
100 109
 // SnapshotFromSigned fully unpacks a Signed object into a SignedSnapshot
101 110
 func SnapshotFromSigned(s *Signed) (*SignedSnapshot, error) {
102 111
 	sp := Snapshot{}
103
-	err := json.Unmarshal(s.Signed, &sp)
104
-	if err != nil {
112
+	if err := defaultSerializer.Unmarshal(s.Signed, &sp); err != nil {
113
+		return nil, err
114
+	}
115
+	if err := isValidSnapshotStructure(sp); err != nil {
105 116
 		return nil, err
106 117
 	}
107 118
 	sigs := make([]Signature, len(s.Signatures))
... ...
@@ -1,9 +1,9 @@
1 1
 package data
2 2
 
3 3
 import (
4
-	"crypto/sha256"
5
-	"encoding/hex"
6 4
 	"errors"
5
+	"fmt"
6
+	"path"
7 7
 
8 8
 	"github.com/docker/go/canonical/json"
9 9
 )
... ...
@@ -23,6 +23,33 @@ type Targets struct {
23 23
 	Delegations Delegations `json:"delegations,omitempty"`
24 24
 }
25 25
 
26
+// isValidTargetsStructure returns an error, or nil, depending on whether the content of the struct
27
+// is valid for targets metadata.  This does not check signatures or expiry, just that
28
+// the metadata content is valid.
29
+func isValidTargetsStructure(t Targets, roleName string) error {
30
+	if roleName != CanonicalTargetsRole && !IsDelegation(roleName) {
31
+		return ErrInvalidRole{Role: roleName}
32
+	}
33
+
34
+	// even if it's a delegated role, the metadata type is "Targets"
35
+	expectedType := TUFTypes[CanonicalTargetsRole]
36
+	if t.Type != expectedType {
37
+		return ErrInvalidMetadata{
38
+			role: roleName, msg: fmt.Sprintf("expected type %s, not %s", expectedType, t.Type)}
39
+	}
40
+
41
+	for _, roleObj := range t.Delegations.Roles {
42
+		if !IsDelegation(roleObj.Name) || path.Dir(roleObj.Name) != roleName {
43
+			return ErrInvalidMetadata{
44
+				role: roleName, msg: fmt.Sprintf("delegation role %s invalid", roleObj.Name)}
45
+		}
46
+		if err := isValidRootRoleStructure(roleName, roleObj.Name, roleObj.RootRole, t.Delegations.Keys); err != nil {
47
+			return err
48
+		}
49
+	}
50
+	return nil
51
+}
52
+
26 53
 // NewTargets intiializes a new empty SignedTargets object
27 54
 func NewTargets() *SignedTargets {
28 55
 	return &SignedTargets{
... ...
@@ -51,30 +78,58 @@ func (t SignedTargets) GetMeta(path string) *FileMeta {
51 51
 	return nil
52 52
 }
53 53
 
54
-// GetDelegations filters the roles and associated keys that may be
55
-// the signers for the given target path. If no appropriate roles
56
-// can be found, it will simply return nil for the return values.
57
-// The returned slice of Role will have order maintained relative
58
-// to the role slice on Delegations per TUF spec proposal on using
59
-// order to determine priority.
60
-func (t SignedTargets) GetDelegations(path string) []*Role {
61
-	var roles []*Role
62
-	pathHashBytes := sha256.Sum256([]byte(path))
63
-	pathHash := hex.EncodeToString(pathHashBytes[:])
64
-	for _, r := range t.Signed.Delegations.Roles {
65
-		if !r.IsValid() {
66
-			// Role has both Paths and PathHashPrefixes.
54
+// GetValidDelegations filters the delegation roles specified in the signed targets, and
55
+// only returns roles that are direct children and restricts their paths
56
+func (t SignedTargets) GetValidDelegations(parent DelegationRole) []DelegationRole {
57
+	roles := t.buildDelegationRoles()
58
+	result := []DelegationRole{}
59
+	for _, r := range roles {
60
+		validRole, err := parent.Restrict(r)
61
+		if err != nil {
67 62
 			continue
68 63
 		}
69
-		if r.CheckPaths(path) {
70
-			roles = append(roles, r)
71
-			continue
64
+		result = append(result, validRole)
65
+	}
66
+	return result
67
+}
68
+
69
+// BuildDelegationRole returns a copy of a DelegationRole using the information in this SignedTargets for the specified role name.
70
+// Will error for invalid role name or key metadata within this SignedTargets.  Path data is not validated.
71
+func (t *SignedTargets) BuildDelegationRole(roleName string) (DelegationRole, error) {
72
+	for _, role := range t.Signed.Delegations.Roles {
73
+		if role.Name == roleName {
74
+			pubKeys := make(map[string]PublicKey)
75
+			for _, keyID := range role.KeyIDs {
76
+				pubKey, ok := t.Signed.Delegations.Keys[keyID]
77
+				if !ok {
78
+					// Couldn't retrieve all keys, so stop walking and return invalid role
79
+					return DelegationRole{}, ErrInvalidRole{Role: roleName, Reason: "delegation does not exist with all specified keys"}
80
+				}
81
+				pubKeys[keyID] = pubKey
82
+			}
83
+			return DelegationRole{
84
+				BaseRole: BaseRole{
85
+					Name:      role.Name,
86
+					Keys:      pubKeys,
87
+					Threshold: role.Threshold,
88
+				},
89
+				Paths: role.Paths,
90
+			}, nil
72 91
 		}
73
-		if r.CheckPrefixes(pathHash) {
74
-			roles = append(roles, r)
92
+	}
93
+	return DelegationRole{}, ErrNoSuchRole{Role: roleName}
94
+}
95
+
96
+// helper function to create DelegationRole structures from all delegations in a SignedTargets,
97
+// these delegations are read directly from the SignedTargets and not modified or validated
98
+func (t SignedTargets) buildDelegationRoles() []DelegationRole {
99
+	var roles []DelegationRole
100
+	for _, roleData := range t.Signed.Delegations.Roles {
101
+		delgRole, err := t.BuildDelegationRole(roleData.Name)
102
+		if err != nil {
75 103
 			continue
76 104
 		}
77
-		//keysDB.AddRole(r)
105
+		roles = append(roles, delgRole)
78 106
 	}
79 107
 	return roles
80 108
 }
... ...
@@ -93,8 +148,8 @@ func (t *SignedTargets) AddDelegation(role *Role, keys []*PublicKey) error {
93 93
 }
94 94
 
95 95
 // ToSigned partially serializes a SignedTargets for further signing
96
-func (t SignedTargets) ToSigned() (*Signed, error) {
97
-	s, err := json.MarshalCanonical(t.Signed)
96
+func (t *SignedTargets) ToSigned() (*Signed, error) {
97
+	s, err := defaultSerializer.MarshalCanonical(t.Signed)
98 98
 	if err != nil {
99 99
 		return nil, err
100 100
 	}
... ...
@@ -111,13 +166,25 @@ func (t SignedTargets) ToSigned() (*Signed, error) {
111 111
 	}, nil
112 112
 }
113 113
 
114
-// TargetsFromSigned fully unpacks a Signed object into a SignedTargets
115
-func TargetsFromSigned(s *Signed) (*SignedTargets, error) {
116
-	t := Targets{}
117
-	err := json.Unmarshal(s.Signed, &t)
114
+// MarshalJSON returns the serialized form of SignedTargets as bytes
115
+func (t *SignedTargets) MarshalJSON() ([]byte, error) {
116
+	signed, err := t.ToSigned()
118 117
 	if err != nil {
119 118
 		return nil, err
120 119
 	}
120
+	return defaultSerializer.Marshal(signed)
121
+}
122
+
123
+// TargetsFromSigned fully unpacks a Signed object into a SignedTargets, given
124
+// a role name (so it can validate the SignedTargets object)
125
+func TargetsFromSigned(s *Signed, roleName string) (*SignedTargets, error) {
126
+	t := Targets{}
127
+	if err := defaultSerializer.Unmarshal(s.Signed, &t); err != nil {
128
+		return nil, err
129
+	}
130
+	if err := isValidTargetsStructure(t, roleName); err != nil {
131
+		return nil, err
132
+	}
121 133
 	sigs := make([]Signature, len(s.Signatures))
122 134
 	copy(sigs, s.Signatures)
123 135
 	return &SignedTargets{
... ...
@@ -2,6 +2,8 @@ package data
2 2
 
3 3
 import (
4 4
 	"bytes"
5
+	"crypto/sha256"
6
+	"fmt"
5 7
 	"time"
6 8
 
7 9
 	"github.com/docker/go/canonical/json"
... ...
@@ -22,6 +24,26 @@ type Timestamp struct {
22 22
 	Meta    Files     `json:"meta"`
23 23
 }
24 24
 
25
+// isValidTimestampStructure returns an error, or nil, depending on whether the content of the struct
26
+// is valid for timestamp metadata.  This does not check signatures or expiry, just that
27
+// the metadata content is valid.
28
+func isValidTimestampStructure(t Timestamp) error {
29
+	expectedType := TUFTypes[CanonicalTimestampRole]
30
+	if t.Type != expectedType {
31
+		return ErrInvalidMetadata{
32
+			role: CanonicalTimestampRole, msg: fmt.Sprintf("expected type %s, not %s", expectedType, t.Type)}
33
+	}
34
+
35
+	// Meta is a map of FileMeta, so if the role isn't in the map it returns
36
+	// an empty FileMeta, which has an empty map, and you can check on keys
37
+	// from an empty map.
38
+	if cs, ok := t.Meta[CanonicalSnapshotRole].Hashes["sha256"]; !ok || len(cs) != sha256.Size {
39
+		return ErrInvalidMetadata{
40
+			role: CanonicalTimestampRole, msg: "missing or invalid snapshot sha256 checksum information"}
41
+	}
42
+	return nil
43
+}
44
+
25 45
 // NewTimestamp initializes a timestamp with an existing snapshot
26 46
 func NewTimestamp(snapshot *Signed) (*SignedTimestamp, error) {
27 47
 	snapshotJSON, err := json.Marshal(snapshot)
... ...
@@ -47,8 +69,8 @@ func NewTimestamp(snapshot *Signed) (*SignedTimestamp, error) {
47 47
 
48 48
 // ToSigned partially serializes a SignedTimestamp such that it can
49 49
 // be signed
50
-func (ts SignedTimestamp) ToSigned() (*Signed, error) {
51
-	s, err := json.MarshalCanonical(ts.Signed)
50
+func (ts *SignedTimestamp) ToSigned() (*Signed, error) {
51
+	s, err := defaultSerializer.MarshalCanonical(ts.Signed)
52 52
 	if err != nil {
53 53
 		return nil, err
54 54
 	}
... ...
@@ -65,12 +87,33 @@ func (ts SignedTimestamp) ToSigned() (*Signed, error) {
65 65
 	}, nil
66 66
 }
67 67
 
68
+// GetSnapshot gets the expected snapshot metadata hashes in the timestamp metadata,
69
+// or nil if it doesn't exist
70
+func (ts *SignedTimestamp) GetSnapshot() (*FileMeta, error) {
71
+	snapshotExpected, ok := ts.Signed.Meta[CanonicalSnapshotRole]
72
+	if !ok {
73
+		return nil, ErrMissingMeta{Role: CanonicalSnapshotRole}
74
+	}
75
+	return &snapshotExpected, nil
76
+}
77
+
78
+// MarshalJSON returns the serialized form of SignedTimestamp as bytes
79
+func (ts *SignedTimestamp) MarshalJSON() ([]byte, error) {
80
+	signed, err := ts.ToSigned()
81
+	if err != nil {
82
+		return nil, err
83
+	}
84
+	return defaultSerializer.Marshal(signed)
85
+}
86
+
68 87
 // TimestampFromSigned parsed a Signed object into a fully unpacked
69 88
 // SignedTimestamp
70 89
 func TimestampFromSigned(s *Signed) (*SignedTimestamp, error) {
71 90
 	ts := Timestamp{}
72
-	err := json.Unmarshal(s.Signed, &ts)
73
-	if err != nil {
91
+	if err := defaultSerializer.Unmarshal(s.Signed, &ts); err != nil {
92
+		return nil, err
93
+	}
94
+	if err := isValidTimestampStructure(ts); err != nil {
74 95
 		return nil, err
75 96
 	}
76 97
 	sigs := make([]Signature, len(s.Signatures))
... ...
@@ -12,6 +12,7 @@ import (
12 12
 
13 13
 	"github.com/Sirupsen/logrus"
14 14
 	"github.com/docker/go/canonical/json"
15
+	"github.com/docker/notary"
15 16
 )
16 17
 
17 18
 // SigAlgorithm for types of signatures
... ...
@@ -171,16 +172,16 @@ func NewDelegations() *Delegations {
171 171
 	}
172 172
 }
173 173
 
174
-// defines number of days in which something should expire
175
-var defaultExpiryTimes = map[string]int{
176
-	CanonicalRootRole:      365,
177
-	CanonicalTargetsRole:   90,
178
-	CanonicalSnapshotRole:  7,
179
-	CanonicalTimestampRole: 1,
174
+// These values are recommended TUF expiry times.
175
+var defaultExpiryTimes = map[string]time.Duration{
176
+	CanonicalRootRole:      notary.Year,
177
+	CanonicalTargetsRole:   90 * notary.Day,
178
+	CanonicalSnapshotRole:  7 * notary.Day,
179
+	CanonicalTimestampRole: notary.Day,
180 180
 }
181 181
 
182 182
 // SetDefaultExpiryTimes allows one to change the default expiries.
183
-func SetDefaultExpiryTimes(times map[string]int) {
183
+func SetDefaultExpiryTimes(times map[string]time.Duration) {
184 184
 	for key, value := range times {
185 185
 		if _, ok := defaultExpiryTimes[key]; !ok {
186 186
 			logrus.Errorf("Attempted to set default expiry for an unknown role: %s", key)
... ...
@@ -192,10 +193,10 @@ func SetDefaultExpiryTimes(times map[string]int) {
192 192
 
193 193
 // DefaultExpires gets the default expiry time for the given role
194 194
 func DefaultExpires(role string) time.Time {
195
-	var t time.Time
196
-	if t, ok := defaultExpiryTimes[role]; ok {
197
-		return time.Now().AddDate(0, 0, t)
195
+	if d, ok := defaultExpiryTimes[role]; ok {
196
+		return time.Now().Add(d)
198 197
 	}
198
+	var t time.Time
199 199
 	return t.UTC().Round(time.Second)
200 200
 }
201 201
 
202 202
deleted file mode 100644
... ...
@@ -1,78 +0,0 @@
1
-package keys
2
-
3
-import (
4
-	"errors"
5
-
6
-	"github.com/docker/notary/tuf/data"
7
-)
8
-
9
-// Various basic key database errors
10
-var (
11
-	ErrWrongType        = errors.New("tuf: invalid key type")
12
-	ErrExists           = errors.New("tuf: key already in db")
13
-	ErrWrongID          = errors.New("tuf: key id mismatch")
14
-	ErrInvalidKey       = errors.New("tuf: invalid key")
15
-	ErrInvalidKeyID     = errors.New("tuf: invalid key id")
16
-	ErrInvalidThreshold = errors.New("tuf: invalid role threshold")
17
-)
18
-
19
-// KeyDB is an in memory database of public keys and role associations.
20
-// It is populated when parsing TUF files and used during signature
21
-// verification to look up the keys for a given role
22
-type KeyDB struct {
23
-	roles map[string]*data.Role
24
-	keys  map[string]data.PublicKey
25
-}
26
-
27
-// NewDB initializes an empty KeyDB
28
-func NewDB() *KeyDB {
29
-	return &KeyDB{
30
-		roles: make(map[string]*data.Role),
31
-		keys:  make(map[string]data.PublicKey),
32
-	}
33
-}
34
-
35
-// AddKey adds a public key to the database
36
-func (db *KeyDB) AddKey(k data.PublicKey) {
37
-	db.keys[k.ID()] = k
38
-}
39
-
40
-// AddRole adds a role to the database. Any keys associated with the
41
-// role must have already been added.
42
-func (db *KeyDB) AddRole(r *data.Role) error {
43
-	if !data.ValidRole(r.Name) {
44
-		return data.ErrInvalidRole{Role: r.Name}
45
-	}
46
-	if r.Threshold < 1 {
47
-		return ErrInvalidThreshold
48
-	}
49
-
50
-	// validate all key ids are in the keys maps
51
-	for _, id := range r.KeyIDs {
52
-		if _, ok := db.keys[id]; !ok {
53
-			return ErrInvalidKeyID
54
-		}
55
-	}
56
-
57
-	db.roles[r.Name] = r
58
-	return nil
59
-}
60
-
61
-// GetAllRoles gets all roles from the database
62
-func (db *KeyDB) GetAllRoles() []*data.Role {
63
-	roles := []*data.Role{}
64
-	for _, role := range db.roles {
65
-		roles = append(roles, role)
66
-	}
67
-	return roles
68
-}
69
-
70
-// GetKey pulls a key out of the database by its ID
71
-func (db *KeyDB) GetKey(id string) data.PublicKey {
72
-	return db.keys[id]
73
-}
74
-
75
-// GetRole retrieves a role based on its name
76
-func (db *KeyDB) GetRole(name string) *data.Role {
77
-	return db.roles[name]
78
-}
... ...
@@ -8,7 +8,6 @@ import (
8 8
 	"github.com/Sirupsen/logrus"
9 9
 	"github.com/docker/go/canonical/json"
10 10
 	"github.com/docker/notary/tuf/data"
11
-	"github.com/docker/notary/tuf/keys"
12 11
 )
13 12
 
14 13
 // Various basic signing errors
... ...
@@ -57,18 +56,18 @@ func VerifyRoot(s *data.Signed, minVersion int, keys map[string]data.PublicKey)
57 57
 			continue
58 58
 		}
59 59
 		// threshold of 1 so return on first success
60
-		return verifyMeta(s, "root", minVersion)
60
+		return verifyMeta(s, data.CanonicalRootRole, minVersion)
61 61
 	}
62 62
 	return ErrRoleThreshold{}
63 63
 }
64 64
 
65 65
 // Verify checks the signatures and metadata (expiry, version) for the signed role
66 66
 // data
67
-func Verify(s *data.Signed, role string, minVersion int, db *keys.KeyDB) error {
68
-	if err := verifyMeta(s, role, minVersion); err != nil {
67
+func Verify(s *data.Signed, role data.BaseRole, minVersion int) error {
68
+	if err := verifyMeta(s, role.Name, minVersion); err != nil {
69 69
 		return err
70 70
 	}
71
-	return VerifySignatures(s, role, db)
71
+	return VerifySignatures(s, role)
72 72
 }
73 73
 
74 74
 func verifyMeta(s *data.Signed, role string, minVersion int) error {
... ...
@@ -96,21 +95,18 @@ func IsExpired(t time.Time) bool {
96 96
 }
97 97
 
98 98
 // VerifySignatures checks the we have sufficient valid signatures for the given role
99
-func VerifySignatures(s *data.Signed, role string, db *keys.KeyDB) error {
99
+func VerifySignatures(s *data.Signed, roleData data.BaseRole) error {
100 100
 	if len(s.Signatures) == 0 {
101 101
 		return ErrNoSignatures
102 102
 	}
103 103
 
104
-	roleData := db.GetRole(role)
105
-	if roleData == nil {
106
-		return ErrUnknownRole
107
-	}
108
-
109 104
 	if roleData.Threshold < 1 {
110 105
 		return ErrRoleThreshold{}
111 106
 	}
112
-	logrus.Debugf("%s role has key IDs: %s", role, strings.Join(roleData.KeyIDs, ","))
107
+	logrus.Debugf("%s role has key IDs: %s", roleData.Name, strings.Join(roleData.ListKeyIDs(), ","))
113 108
 
109
+	// remarshal the signed part so we can verify the signature, since the signature has
110
+	// to be of a canonically marshalled signed object
114 111
 	var decoded map[string]interface{}
115 112
 	if err := json.Unmarshal(s.Signed, &decoded); err != nil {
116 113
 		return err
... ...
@@ -123,12 +119,8 @@ func VerifySignatures(s *data.Signed, role string, db *keys.KeyDB) error {
123 123
 	valid := make(map[string]struct{})
124 124
 	for _, sig := range s.Signatures {
125 125
 		logrus.Debug("verifying signature for key ID: ", sig.KeyID)
126
-		if !roleData.ValidKey(sig.KeyID) {
127
-			logrus.Debugf("continuing b/c keyid was invalid: %s for roledata %s\n", sig.KeyID, roleData)
128
-			continue
129
-		}
130
-		key := db.GetKey(sig.KeyID)
131
-		if key == nil {
126
+		key, ok := roleData.Keys[sig.KeyID]
127
+		if !ok {
132 128
 			logrus.Debugf("continuing b/c keyid lookup was nil: %s\n", sig.KeyID)
133 129
 			continue
134 130
 		}
... ...
@@ -153,28 +145,3 @@ func VerifySignatures(s *data.Signed, role string, db *keys.KeyDB) error {
153 153
 
154 154
 	return nil
155 155
 }
156
-
157
-// Unmarshal unmarshals and verifys the raw bytes for a given role's metadata
158
-func Unmarshal(b []byte, v interface{}, role string, minVersion int, db *keys.KeyDB) error {
159
-	s := &data.Signed{}
160
-	if err := json.Unmarshal(b, s); err != nil {
161
-		return err
162
-	}
163
-	if err := Verify(s, role, minVersion, db); err != nil {
164
-		return err
165
-	}
166
-	return json.Unmarshal(s.Signed, v)
167
-}
168
-
169
-// UnmarshalTrusted unmarshals and verifies signatures only, not metadata, for a
170
-// given role's metadata
171
-func UnmarshalTrusted(b []byte, v interface{}, role string, db *keys.KeyDB) error {
172
-	s := &data.Signed{}
173
-	if err := json.Unmarshal(b, s); err != nil {
174
-		return err
175
-	}
176
-	if err := VerifySignatures(s, role, db); err != nil {
177
-		return err
178
-	}
179
-	return json.Unmarshal(s.Signed, v)
180
-}
... ...
@@ -2,6 +2,7 @@ package store
2 2
 
3 3
 import (
4 4
 	"fmt"
5
+	"github.com/docker/notary"
5 6
 	"io/ioutil"
6 7
 	"os"
7 8
 	"path"
... ...
@@ -9,25 +10,19 @@ import (
9 9
 )
10 10
 
11 11
 // NewFilesystemStore creates a new store in a directory tree
12
-func NewFilesystemStore(baseDir, metaSubDir, metaExtension, targetsSubDir string) (*FilesystemStore, error) {
12
+func NewFilesystemStore(baseDir, metaSubDir, metaExtension string) (*FilesystemStore, error) {
13 13
 	metaDir := path.Join(baseDir, metaSubDir)
14
-	targetsDir := path.Join(baseDir, targetsSubDir)
15 14
 
16 15
 	// Make sure we can create the necessary dirs and they are writable
17 16
 	err := os.MkdirAll(metaDir, 0700)
18 17
 	if err != nil {
19 18
 		return nil, err
20 19
 	}
21
-	err = os.MkdirAll(targetsDir, 0700)
22
-	if err != nil {
23
-		return nil, err
24
-	}
25 20
 
26 21
 	return &FilesystemStore{
27 22
 		baseDir:       baseDir,
28 23
 		metaDir:       metaDir,
29 24
 		metaExtension: metaExtension,
30
-		targetsDir:    targetsDir,
31 25
 	}, nil
32 26
 }
33 27
 
... ...
@@ -36,7 +31,6 @@ type FilesystemStore struct {
36 36
 	baseDir       string
37 37
 	metaDir       string
38 38
 	metaExtension string
39
-	targetsDir    string
40 39
 }
41 40
 
42 41
 func (f *FilesystemStore) getPath(name string) string {
... ...
@@ -44,7 +38,8 @@ func (f *FilesystemStore) getPath(name string) string {
44 44
 	return filepath.Join(f.metaDir, fileName)
45 45
 }
46 46
 
47
-// GetMeta returns the meta for the given name (a role)
47
+// GetMeta returns the meta for the given name (a role) up to size bytes
48
+// If size is -1, this corresponds to "infinite," but we cut off at 100MB
48 49
 func (f *FilesystemStore) GetMeta(name string, size int64) ([]byte, error) {
49 50
 	meta, err := ioutil.ReadFile(f.getPath(name))
50 51
 	if err != nil {
... ...
@@ -53,7 +48,14 @@ func (f *FilesystemStore) GetMeta(name string, size int64) ([]byte, error) {
53 53
 		}
54 54
 		return nil, err
55 55
 	}
56
-	return meta, nil
56
+	if size == -1 {
57
+		size = notary.MaxDownloadSize
58
+	}
59
+	// Only return up to size bytes
60
+	if int64(len(meta)) < size {
61
+		return meta, nil
62
+	}
63
+	return meta[:size], nil
57 64
 }
58 65
 
59 66
 // SetMultiMeta sets the metadata for multiple roles in one operation
... ...
@@ -23,6 +23,7 @@ import (
23 23
 	"path"
24 24
 
25 25
 	"github.com/Sirupsen/logrus"
26
+	"github.com/docker/notary"
26 27
 	"github.com/docker/notary/tuf/validation"
27 28
 )
28 29
 
... ...
@@ -33,6 +34,9 @@ type ErrServerUnavailable struct {
33 33
 }
34 34
 
35 35
 func (err ErrServerUnavailable) Error() string {
36
+	if err.code == 401 {
37
+		return fmt.Sprintf("you are not authorized to perform this operation: server returned 401.")
38
+	}
36 39
 	return fmt.Sprintf("unable to reach trust server at this time: %d.", err.code)
37 40
 }
38 41
 
... ...
@@ -71,13 +75,12 @@ type HTTPStore struct {
71 71
 	baseURL       url.URL
72 72
 	metaPrefix    string
73 73
 	metaExtension string
74
-	targetsPrefix string
75 74
 	keyExtension  string
76 75
 	roundTrip     http.RoundTripper
77 76
 }
78 77
 
79 78
 // NewHTTPStore initializes a new store against a URL and a number of configuration options
80
-func NewHTTPStore(baseURL, metaPrefix, metaExtension, targetsPrefix, keyExtension string, roundTrip http.RoundTripper) (RemoteStore, error) {
79
+func NewHTTPStore(baseURL, metaPrefix, metaExtension, keyExtension string, roundTrip http.RoundTripper) (RemoteStore, error) {
81 80
 	base, err := url.Parse(baseURL)
82 81
 	if err != nil {
83 82
 		return nil, err
... ...
@@ -92,7 +95,6 @@ func NewHTTPStore(baseURL, metaPrefix, metaExtension, targetsPrefix, keyExtensio
92 92
 		baseURL:       *base,
93 93
 		metaPrefix:    metaPrefix,
94 94
 		metaExtension: metaExtension,
95
-		targetsPrefix: targetsPrefix,
96 95
 		keyExtension:  keyExtension,
97 96
 		roundTrip:     roundTrip,
98 97
 	}, nil
... ...
@@ -137,6 +139,7 @@ func translateStatusToError(resp *http.Response, resource string) error {
137 137
 // GetMeta downloads the named meta file with the given size. A short body
138 138
 // is acceptable because in the case of timestamp.json, the size is a cap,
139 139
 // not an exact length.
140
+// If size is -1, this corresponds to "infinite," but we cut off at 100MB
140 141
 func (s HTTPStore) GetMeta(name string, size int64) ([]byte, error) {
141 142
 	url, err := s.buildMetaURL(name)
142 143
 	if err != nil {
... ...
@@ -155,6 +158,9 @@ func (s HTTPStore) GetMeta(name string, size int64) ([]byte, error) {
155 155
 		logrus.Debugf("received HTTP status %d when requesting %s.", resp.StatusCode, name)
156 156
 		return nil, err
157 157
 	}
158
+	if size == -1 {
159
+		size = notary.MaxDownloadSize
160
+	}
158 161
 	if resp.ContentLength > size {
159 162
 		return nil, ErrMaliciousServer{}
160 163
 	}
... ...
@@ -250,11 +256,6 @@ func (s HTTPStore) buildMetaURL(name string) (*url.URL, error) {
250 250
 	return s.buildURL(uri)
251 251
 }
252 252
 
253
-func (s HTTPStore) buildTargetsURL(name string) (*url.URL, error) {
254
-	uri := path.Join(s.targetsPrefix, name)
255
-	return s.buildURL(uri)
256
-}
257
-
258 253
 func (s HTTPStore) buildKeyURL(name string) (*url.URL, error) {
259 254
 	filename := fmt.Sprintf("%s.%s", name, s.keyExtension)
260 255
 	uri := path.Join(s.metaPrefix, filename)
... ...
@@ -269,29 +270,6 @@ func (s HTTPStore) buildURL(uri string) (*url.URL, error) {
269 269
 	return s.baseURL.ResolveReference(sub), nil
270 270
 }
271 271
 
272
-// GetTarget returns a reader for the desired target or an error.
273
-// N.B. The caller is responsible for closing the reader.
274
-func (s HTTPStore) GetTarget(path string) (io.ReadCloser, error) {
275
-	url, err := s.buildTargetsURL(path)
276
-	if err != nil {
277
-		return nil, err
278
-	}
279
-	logrus.Debug("Attempting to download target: ", url.String())
280
-	req, err := http.NewRequest("GET", url.String(), nil)
281
-	if err != nil {
282
-		return nil, err
283
-	}
284
-	resp, err := s.roundTrip.RoundTrip(req)
285
-	if err != nil {
286
-		return nil, err
287
-	}
288
-	defer resp.Body.Close()
289
-	if err := translateStatusToError(resp, path); err != nil {
290
-		return nil, err
291
-	}
292
-	return resp.Body, nil
293
-}
294
-
295 272
 // GetKey retrieves a public key from the remote server
296 273
 func (s HTTPStore) GetKey(role string) ([]byte, error) {
297 274
 	url, err := s.buildKeyURL(role)
... ...
@@ -1,13 +1,5 @@
1 1
 package store
2 2
 
3
-import (
4
-	"io"
5
-
6
-	"github.com/docker/notary/tuf/data"
7
-)
8
-
9
-type targetsWalkFunc func(path string, meta data.FileMeta) error
10
-
11 3
 // MetadataStore must be implemented by anything that intends to interact
12 4
 // with a store of TUF files
13 5
 type MetadataStore interface {
... ...
@@ -23,17 +15,9 @@ type PublicKeyStore interface {
23 23
 	GetKey(role string) ([]byte, error)
24 24
 }
25 25
 
26
-// TargetStore represents a collection of targets that can be walked similarly
27
-// to walking a directory, passing a callback that receives the path and meta
28
-// for each target
29
-type TargetStore interface {
30
-	WalkStagedTargets(paths []string, targetsFn targetsWalkFunc) error
31
-}
32
-
33 26
 // LocalStore represents a local TUF sture
34 27
 type LocalStore interface {
35 28
 	MetadataStore
36
-	TargetStore
37 29
 }
38 30
 
39 31
 // RemoteStore is similar to LocalStore with the added expectation that it should
... ...
@@ -41,5 +25,4 @@ type LocalStore interface {
41 41
 type RemoteStore interface {
42 42
 	MetadataStore
43 43
 	PublicKeyStore
44
-	GetTarget(path string) (io.ReadCloser, error)
45 44
 }
... ...
@@ -1,39 +1,60 @@
1 1
 package store
2 2
 
3 3
 import (
4
-	"bytes"
4
+	"crypto/sha256"
5 5
 	"fmt"
6
-	"io"
7 6
 
7
+	"github.com/docker/notary"
8 8
 	"github.com/docker/notary/tuf/data"
9 9
 	"github.com/docker/notary/tuf/utils"
10 10
 )
11 11
 
12 12
 // NewMemoryStore returns a MetadataStore that operates entirely in memory.
13 13
 // Very useful for testing
14
-func NewMemoryStore(meta map[string][]byte, files map[string][]byte) RemoteStore {
14
+func NewMemoryStore(meta map[string][]byte) *MemoryStore {
15
+	var consistent = make(map[string][]byte)
15 16
 	if meta == nil {
16 17
 		meta = make(map[string][]byte)
18
+	} else {
19
+		// add all seed meta to consistent
20
+		for name, data := range meta {
21
+			checksum := sha256.Sum256(data)
22
+			path := utils.ConsistentName(name, checksum[:])
23
+			consistent[path] = data
24
+		}
17 25
 	}
18
-	if files == nil {
19
-		files = make(map[string][]byte)
20
-	}
21
-	return &memoryStore{
22
-		meta:  meta,
23
-		files: files,
24
-		keys:  make(map[string][]data.PrivateKey),
26
+	return &MemoryStore{
27
+		meta:       meta,
28
+		consistent: consistent,
29
+		keys:       make(map[string][]data.PrivateKey),
25 30
 	}
26 31
 }
27 32
 
28
-type memoryStore struct {
29
-	meta  map[string][]byte
30
-	files map[string][]byte
31
-	keys  map[string][]data.PrivateKey
33
+// MemoryStore implements a mock RemoteStore entirely in memory.
34
+// For testing purposes only.
35
+type MemoryStore struct {
36
+	meta       map[string][]byte
37
+	consistent map[string][]byte
38
+	keys       map[string][]data.PrivateKey
32 39
 }
33 40
 
34
-func (m *memoryStore) GetMeta(name string, size int64) ([]byte, error) {
41
+// GetMeta returns up to size bytes of data references by name.
42
+// If size is -1, this corresponds to "infinite," but we cut off at 100MB
43
+// as we will always know the size for everything but a timestamp and
44
+// sometimes a root, neither of which should be exceptionally large
45
+func (m *MemoryStore) GetMeta(name string, size int64) ([]byte, error) {
35 46
 	d, ok := m.meta[name]
36 47
 	if ok {
48
+		if size == -1 {
49
+			size = notary.MaxDownloadSize
50
+		}
51
+		if int64(len(d)) < size {
52
+			return d, nil
53
+		}
54
+		return d[:size], nil
55
+	}
56
+	d, ok = m.consistent[name]
57
+	if ok {
37 58
 		if int64(len(d)) < size {
38 59
 			return d, nil
39 60
 		}
... ...
@@ -42,12 +63,19 @@ func (m *memoryStore) GetMeta(name string, size int64) ([]byte, error) {
42 42
 	return nil, ErrMetaNotFound{Resource: name}
43 43
 }
44 44
 
45
-func (m *memoryStore) SetMeta(name string, meta []byte) error {
45
+// SetMeta sets the metadata value for the given name
46
+func (m *MemoryStore) SetMeta(name string, meta []byte) error {
46 47
 	m.meta[name] = meta
48
+
49
+	checksum := sha256.Sum256(meta)
50
+	path := utils.ConsistentName(name, checksum[:])
51
+	m.consistent[path] = meta
47 52
 	return nil
48 53
 }
49 54
 
50
-func (m *memoryStore) SetMultiMeta(metas map[string][]byte) error {
55
+// SetMultiMeta sets multiple pieces of metadata for multiple names
56
+// in a single operation.
57
+func (m *MemoryStore) SetMultiMeta(metas map[string][]byte) error {
51 58
 	for role, blob := range metas {
52 59
 		m.SetMeta(role, blob)
53 60
 	}
... ...
@@ -56,57 +84,23 @@ func (m *memoryStore) SetMultiMeta(metas map[string][]byte) error {
56 56
 
57 57
 // RemoveMeta removes the metadata for a single role - if the metadata doesn't
58 58
 // exist, no error is returned
59
-func (m *memoryStore) RemoveMeta(name string) error {
60
-	delete(m.meta, name)
61
-	return nil
62
-}
63
-
64
-func (m *memoryStore) GetTarget(path string) (io.ReadCloser, error) {
65
-	return &utils.NoopCloser{Reader: bytes.NewReader(m.files[path])}, nil
66
-}
67
-
68
-func (m *memoryStore) WalkStagedTargets(paths []string, targetsFn targetsWalkFunc) error {
69
-	if len(paths) == 0 {
70
-		for path, dat := range m.files {
71
-			meta, err := data.NewFileMeta(bytes.NewReader(dat), "sha256")
72
-			if err != nil {
73
-				return err
74
-			}
75
-			if err = targetsFn(path, meta); err != nil {
76
-				return err
77
-			}
78
-		}
79
-		return nil
80
-	}
81
-
82
-	for _, path := range paths {
83
-		dat, ok := m.files[path]
84
-		if !ok {
85
-			return ErrMetaNotFound{Resource: path}
86
-		}
87
-		meta, err := data.NewFileMeta(bytes.NewReader(dat), "sha256")
88
-		if err != nil {
89
-			return err
90
-		}
91
-		if err = targetsFn(path, meta); err != nil {
92
-			return err
93
-		}
59
+func (m *MemoryStore) RemoveMeta(name string) error {
60
+	if meta, ok := m.meta[name]; ok {
61
+		checksum := sha256.Sum256(meta)
62
+		path := utils.ConsistentName(name, checksum[:])
63
+		delete(m.meta, name)
64
+		delete(m.consistent, path)
94 65
 	}
95 66
 	return nil
96 67
 }
97 68
 
98
-func (m *memoryStore) Commit(map[string][]byte, bool, map[string]data.Hashes) error {
99
-	return nil
100
-}
101
-
102
-func (m *memoryStore) GetKey(role string) ([]byte, error) {
103
-	return nil, fmt.Errorf("GetKey is not implemented for the memoryStore")
69
+// GetKey returns the public key for the given role
70
+func (m *MemoryStore) GetKey(role string) ([]byte, error) {
71
+	return nil, fmt.Errorf("GetKey is not implemented for the MemoryStore")
104 72
 }
105 73
 
106
-// Clear this existing memory store by setting this store as new empty one
107
-func (m *memoryStore) RemoveAll() error {
108
-	m.meta = make(map[string][]byte)
109
-	m.files = make(map[string][]byte)
110
-	m.keys = make(map[string][]data.PrivateKey)
74
+// RemoveAll clears the existing memory store by setting this store as new empty one
75
+func (m *MemoryStore) RemoveAll() error {
76
+	*m = *NewMemoryStore(nil)
111 77
 	return nil
112 78
 }
... ...
@@ -3,8 +3,6 @@ package tuf
3 3
 
4 4
 import (
5 5
 	"bytes"
6
-	"crypto/sha256"
7
-	"encoding/hex"
8 6
 	"encoding/json"
9 7
 	"fmt"
10 8
 	"path"
... ...
@@ -12,8 +10,8 @@ import (
12 12
 	"time"
13 13
 
14 14
 	"github.com/Sirupsen/logrus"
15
+	"github.com/docker/notary"
15 16
 	"github.com/docker/notary/tuf/data"
16
-	"github.com/docker/notary/tuf/keys"
17 17
 	"github.com/docker/notary/tuf/signed"
18 18
 	"github.com/docker/notary/tuf/utils"
19 19
 )
... ...
@@ -40,15 +38,19 @@ func (e ErrLocalRootExpired) Error() string {
40 40
 }
41 41
 
42 42
 // ErrNotLoaded - attempted to access data that has not been loaded into
43
-// the repo
43
+// the repo. This means specifically that the relevant JSON file has not
44
+// been loaded.
44 45
 type ErrNotLoaded struct {
45
-	role string
46
+	Role string
46 47
 }
47 48
 
48 49
 func (err ErrNotLoaded) Error() string {
49
-	return fmt.Sprintf("%s role has not been loaded", err.role)
50
+	return fmt.Sprintf("%s role has not been loaded", err.Role)
50 51
 }
51 52
 
53
+// StopWalk - used by visitor functions to signal WalkTargets to stop walking
54
+type StopWalk struct{}
55
+
52 56
 // Repo is an in memory representation of the TUF Repo.
53 57
 // It operates at the data.Signed level, accepting and producing
54 58
 // data.Signed objects. Users of a Repo are responsible for
... ...
@@ -59,16 +61,15 @@ type Repo struct {
59 59
 	Targets       map[string]*data.SignedTargets
60 60
 	Snapshot      *data.SignedSnapshot
61 61
 	Timestamp     *data.SignedTimestamp
62
-	keysDB        *keys.KeyDB
63 62
 	cryptoService signed.CryptoService
64 63
 }
65 64
 
66
-// NewRepo initializes a Repo instance with a keysDB and a signer.
67
-// If the Repo will only be used for reading, the signer should be nil.
68
-func NewRepo(keysDB *keys.KeyDB, cryptoService signed.CryptoService) *Repo {
65
+// NewRepo initializes a Repo instance with a CryptoService.
66
+// If the Repo will only be used for reading, the CryptoService
67
+// can be nil.
68
+func NewRepo(cryptoService signed.CryptoService) *Repo {
69 69
 	repo := &Repo{
70 70
 		Targets:       make(map[string]*data.SignedTargets),
71
-		keysDB:        keysDB,
72 71
 		cryptoService: cryptoService,
73 72
 	}
74 73
 	return repo
... ...
@@ -77,27 +78,15 @@ func NewRepo(keysDB *keys.KeyDB, cryptoService signed.CryptoService) *Repo {
77 77
 // AddBaseKeys is used to add keys to the role in root.json
78 78
 func (tr *Repo) AddBaseKeys(role string, keys ...data.PublicKey) error {
79 79
 	if tr.Root == nil {
80
-		return ErrNotLoaded{role: "root"}
80
+		return ErrNotLoaded{Role: data.CanonicalRootRole}
81 81
 	}
82 82
 	ids := []string{}
83 83
 	for _, k := range keys {
84 84
 		// Store only the public portion
85 85
 		tr.Root.Signed.Keys[k.ID()] = k
86
-		tr.keysDB.AddKey(k)
87 86
 		tr.Root.Signed.Roles[role].KeyIDs = append(tr.Root.Signed.Roles[role].KeyIDs, k.ID())
88 87
 		ids = append(ids, k.ID())
89 88
 	}
90
-	r, err := data.NewRole(
91
-		role,
92
-		tr.Root.Signed.Roles[role].Threshold,
93
-		ids,
94
-		nil,
95
-		nil,
96
-	)
97
-	if err != nil {
98
-		return err
99
-	}
100
-	tr.keysDB.AddRole(r)
101 89
 	tr.Root.Dirty = true
102 90
 
103 91
 	// 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 {
121 121
 
122 122
 // ReplaceBaseKeys is used to replace all keys for the given role with the new keys
123 123
 func (tr *Repo) ReplaceBaseKeys(role string, keys ...data.PublicKey) error {
124
-	r := tr.keysDB.GetRole(role)
125
-	err := tr.RemoveBaseKeys(role, r.KeyIDs...)
124
+	r, err := tr.GetBaseRole(role)
125
+	if err != nil {
126
+		return err
127
+	}
128
+	err = tr.RemoveBaseKeys(role, r.ListKeyIDs()...)
126 129
 	if err != nil {
127 130
 		return err
128 131
 	}
... ...
@@ -132,7 +124,7 @@ func (tr *Repo) ReplaceBaseKeys(role string, keys ...data.PublicKey) error {
132 132
 // RemoveBaseKeys is used to remove keys from the roles in root.json
133 133
 func (tr *Repo) RemoveBaseKeys(role string, keyIDs ...string) error {
134 134
 	if tr.Root == nil {
135
-		return ErrNotLoaded{role: "root"}
135
+		return ErrNotLoaded{Role: data.CanonicalRootRole}
136 136
 	}
137 137
 	var keep []string
138 138
 	toDelete := make(map[string]struct{})
... ...
@@ -173,117 +165,253 @@ func (tr *Repo) RemoveBaseKeys(role string, keyIDs ...string) error {
173 173
 	return nil
174 174
 }
175 175
 
176
-// GetAllLoadedRoles returns a list of all role entries loaded in this TUF repo, could be empty
177
-func (tr *Repo) GetAllLoadedRoles() []*data.Role {
178
-	return tr.keysDB.GetAllRoles()
176
+// GetBaseRole gets a base role from this repo's metadata
177
+func (tr *Repo) GetBaseRole(name string) (data.BaseRole, error) {
178
+	if !data.ValidRole(name) {
179
+		return data.BaseRole{}, data.ErrInvalidRole{Role: name, Reason: "invalid base role name"}
180
+	}
181
+	if tr.Root == nil {
182
+		return data.BaseRole{}, ErrNotLoaded{data.CanonicalRootRole}
183
+	}
184
+	// Find the role data public keys for the base role from TUF metadata
185
+	baseRole, err := tr.Root.BuildBaseRole(name)
186
+	if err != nil {
187
+		return data.BaseRole{}, err
188
+	}
189
+
190
+	return baseRole, nil
179 191
 }
180 192
 
181
-// GetDelegation finds the role entry representing the provided
182
-// role name or ErrInvalidRole
183
-func (tr *Repo) GetDelegation(role string) (*data.Role, error) {
184
-	r := data.Role{Name: role}
185
-	if !r.IsDelegation() {
186
-		return nil, data.ErrInvalidRole{Role: role, Reason: "not a valid delegated role"}
193
+// GetDelegationRole gets a delegation role from this repo's metadata, walking from the targets role down to the delegation itself
194
+func (tr *Repo) GetDelegationRole(name string) (data.DelegationRole, error) {
195
+	if !data.IsDelegation(name) {
196
+		return data.DelegationRole{}, data.ErrInvalidRole{Role: name, Reason: "invalid delegation name"}
197
+	}
198
+	if tr.Root == nil {
199
+		return data.DelegationRole{}, ErrNotLoaded{data.CanonicalRootRole}
200
+	}
201
+	_, ok := tr.Root.Signed.Roles[data.CanonicalTargetsRole]
202
+	if !ok {
203
+		return data.DelegationRole{}, ErrNotLoaded{data.CanonicalTargetsRole}
204
+	}
205
+	// Traverse target metadata, down to delegation itself
206
+	// Get all public keys for the base role from TUF metadata
207
+	_, ok = tr.Targets[data.CanonicalTargetsRole]
208
+	if !ok {
209
+		return data.DelegationRole{}, ErrNotLoaded{data.CanonicalTargetsRole}
210
+	}
211
+
212
+	// Start with top level roles in targets. Walk the chain of ancestors
213
+	// until finding the desired role, or we run out of targets files to search.
214
+	var foundRole *data.DelegationRole
215
+	buildDelegationRoleVisitor := func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} {
216
+		// Try to find the delegation and build a DelegationRole structure
217
+		for _, role := range tgt.Signed.Delegations.Roles {
218
+			if role.Name == name {
219
+				delgRole, err := tgt.BuildDelegationRole(name)
220
+				if err != nil {
221
+					return err
222
+				}
223
+				foundRole = &delgRole
224
+				return StopWalk{}
225
+			}
226
+		}
227
+		return nil
187 228
 	}
188 229
 
189
-	parent := path.Dir(role)
230
+	// Walk to the parent of this delegation, since that is where its role metadata exists
231
+	err := tr.WalkTargets("", path.Dir(name), buildDelegationRoleVisitor)
232
+	if err != nil {
233
+		return data.DelegationRole{}, err
234
+	}
190 235
 
191
-	// check the parent role
192
-	if parentRole := tr.keysDB.GetRole(parent); parentRole == nil {
193
-		return nil, data.ErrInvalidRole{Role: role, Reason: "parent role not found"}
236
+	// We never found the delegation. In the context of this repo it is considered
237
+	// invalid. N.B. it may be that it existed at one point but an ancestor has since
238
+	// been modified/removed.
239
+	if foundRole == nil {
240
+		return data.DelegationRole{}, data.ErrInvalidRole{Role: name, Reason: "delegation does not exist"}
194 241
 	}
195 242
 
196
-	// check the parent role's metadata
197
-	p, ok := tr.Targets[parent]
198
-	if !ok { // the parent targetfile may not exist yet, so it can't be in the list
199
-		return nil, data.ErrNoSuchRole{Role: role}
243
+	return *foundRole, nil
244
+}
245
+
246
+// GetAllLoadedRoles returns a list of all role entries loaded in this TUF repo, could be empty
247
+func (tr *Repo) GetAllLoadedRoles() []*data.Role {
248
+	var res []*data.Role
249
+	if tr.Root == nil {
250
+		// if root isn't loaded, we should consider we have no loaded roles because we can't
251
+		// trust any other state that might be present
252
+		return res
253
+	}
254
+	for name, rr := range tr.Root.Signed.Roles {
255
+		res = append(res, &data.Role{
256
+			RootRole: *rr,
257
+			Name:     name,
258
+		})
259
+	}
260
+	for _, delegate := range tr.Targets {
261
+		for _, r := range delegate.Signed.Delegations.Roles {
262
+			res = append(res, r)
263
+		}
200 264
 	}
265
+	return res
266
+}
201 267
 
202
-	foundAt := utils.FindRoleIndex(p.Signed.Delegations.Roles, role)
203
-	if foundAt < 0 {
204
-		return nil, data.ErrNoSuchRole{Role: role}
268
+// Walk to parent, and either create or update this delegation.  We can only create a new delegation if we're given keys
269
+// Ensure all updates are valid, by checking against parent ancestor paths and ensuring the keys meet the role threshold.
270
+func delegationUpdateVisitor(roleName string, addKeys data.KeyList, removeKeys, addPaths, removePaths []string, clearAllPaths bool, newThreshold int) walkVisitorFunc {
271
+	return func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} {
272
+		var err error
273
+		// Validate the changes underneath this restricted validRole for adding paths, reject invalid path additions
274
+		if len(addPaths) != len(data.RestrictDelegationPathPrefixes(validRole.Paths, addPaths)) {
275
+			return data.ErrInvalidRole{Role: roleName, Reason: "invalid paths to add to role"}
276
+		}
277
+		// Try to find the delegation and amend it using our changelist
278
+		var delgRole *data.Role
279
+		for _, role := range tgt.Signed.Delegations.Roles {
280
+			if role.Name == roleName {
281
+				// Make a copy and operate on this role until we validate the changes
282
+				keyIDCopy := make([]string, len(role.KeyIDs))
283
+				copy(keyIDCopy, role.KeyIDs)
284
+				pathsCopy := make([]string, len(role.Paths))
285
+				copy(pathsCopy, role.Paths)
286
+				delgRole = &data.Role{
287
+					RootRole: data.RootRole{
288
+						KeyIDs:    keyIDCopy,
289
+						Threshold: role.Threshold,
290
+					},
291
+					Name:  role.Name,
292
+					Paths: pathsCopy,
293
+				}
294
+				delgRole.RemovePaths(removePaths)
295
+				if clearAllPaths {
296
+					delgRole.Paths = []string{}
297
+				}
298
+				delgRole.AddPaths(addPaths)
299
+				delgRole.RemoveKeys(removeKeys)
300
+				break
301
+			}
302
+		}
303
+		// We didn't find the role earlier, so create it only if we have keys to add
304
+		if delgRole == nil {
305
+			if len(addKeys) > 0 {
306
+				delgRole, err = data.NewRole(roleName, newThreshold, addKeys.IDs(), addPaths)
307
+				if err != nil {
308
+					return err
309
+				}
310
+			} else {
311
+				// If we can't find the role and didn't specify keys to add, this is an error
312
+				return data.ErrInvalidRole{Role: roleName, Reason: "cannot create new delegation without keys"}
313
+			}
314
+		}
315
+		// Add the key IDs to the role and the keys themselves to the parent
316
+		for _, k := range addKeys {
317
+			if !utils.StrSliceContains(delgRole.KeyIDs, k.ID()) {
318
+				delgRole.KeyIDs = append(delgRole.KeyIDs, k.ID())
319
+			}
320
+		}
321
+		// Make sure we have a valid role still
322
+		if len(delgRole.KeyIDs) < delgRole.Threshold {
323
+			return data.ErrInvalidRole{Role: roleName, Reason: "insufficient keys to meet threshold"}
324
+		}
325
+		// NOTE: this closure CANNOT error after this point, as we've committed to editing the SignedTargets metadata in the repo object.
326
+		// Any errors related to updating this delegation must occur before this point.
327
+		// If all of our changes were valid, we should edit the actual SignedTargets to match our copy
328
+		for _, k := range addKeys {
329
+			tgt.Signed.Delegations.Keys[k.ID()] = k
330
+		}
331
+		foundAt := utils.FindRoleIndex(tgt.Signed.Delegations.Roles, delgRole.Name)
332
+		if foundAt < 0 {
333
+			tgt.Signed.Delegations.Roles = append(tgt.Signed.Delegations.Roles, delgRole)
334
+		} else {
335
+			tgt.Signed.Delegations.Roles[foundAt] = delgRole
336
+		}
337
+		tgt.Dirty = true
338
+		utils.RemoveUnusedKeys(tgt)
339
+		return StopWalk{}
205 340
 	}
206
-	return p.Signed.Delegations.Roles[foundAt], nil
207 341
 }
208 342
 
209
-// UpdateDelegations updates the appropriate delegations, either adding
343
+// UpdateDelegationKeys updates the appropriate delegations, either adding
210 344
 // a new delegation or updating an existing one. If keys are
211 345
 // provided, the IDs will be added to the role (if they do not exist
212 346
 // there already), and the keys will be added to the targets file.
213
-func (tr *Repo) UpdateDelegations(role *data.Role, keys []data.PublicKey) error {
214
-	if !role.IsDelegation() || !role.IsValid() {
215
-		return data.ErrInvalidRole{Role: role.Name, Reason: "not a valid delegated role"}
347
+func (tr *Repo) UpdateDelegationKeys(roleName string, addKeys data.KeyList, removeKeys []string, newThreshold int) error {
348
+	if !data.IsDelegation(roleName) {
349
+		return data.ErrInvalidRole{Role: roleName, Reason: "not a valid delegated role"}
216 350
 	}
217
-	parent := path.Dir(role.Name)
351
+	parent := path.Dir(roleName)
218 352
 
219 353
 	if err := tr.VerifyCanSign(parent); err != nil {
220 354
 		return err
221 355
 	}
222 356
 
223 357
 	// check the parent role's metadata
224
-	p, ok := tr.Targets[parent]
358
+	_, ok := tr.Targets[parent]
225 359
 	if !ok { // the parent targetfile may not exist yet - if not, then create it
226 360
 		var err error
227
-		p, err = tr.InitTargets(parent)
361
+		_, err = tr.InitTargets(parent)
228 362
 		if err != nil {
229 363
 			return err
230 364
 		}
231 365
 	}
232 366
 
233
-	for _, k := range keys {
234
-		if !utils.StrSliceContains(role.KeyIDs, k.ID()) {
235
-			role.KeyIDs = append(role.KeyIDs, k.ID())
236
-		}
237
-		p.Signed.Delegations.Keys[k.ID()] = k
238
-		tr.keysDB.AddKey(k)
367
+	// Walk to the parent of this delegation, since that is where its role metadata exists
368
+	// We do not have to verify that the walker reached its desired role in this scenario
369
+	// since we've already done another walk to the parent role in VerifyCanSign, and potentially made a targets file
370
+	err := tr.WalkTargets("", parent, delegationUpdateVisitor(roleName, addKeys, removeKeys, []string{}, []string{}, false, newThreshold))
371
+	if err != nil {
372
+		return err
239 373
 	}
374
+	return nil
375
+}
240 376
 
241
-	// if the role has fewer keys than the threshold, it
242
-	// will never be able to create a valid targets file
243
-	// and should be considered invalid.
244
-	if len(role.KeyIDs) < role.Threshold {
245
-		return data.ErrInvalidRole{Role: role.Name, Reason: "insufficient keys to meet threshold"}
377
+// UpdateDelegationPaths updates the appropriate delegation's paths.
378
+// It is not allowed to create a new delegation.
379
+func (tr *Repo) UpdateDelegationPaths(roleName string, addPaths, removePaths []string, clearPaths bool) error {
380
+	if !data.IsDelegation(roleName) {
381
+		return data.ErrInvalidRole{Role: roleName, Reason: "not a valid delegated role"}
246 382
 	}
383
+	parent := path.Dir(roleName)
247 384
 
248
-	foundAt := utils.FindRoleIndex(p.Signed.Delegations.Roles, role.Name)
249
-
250
-	if foundAt >= 0 {
251
-		p.Signed.Delegations.Roles[foundAt] = role
252
-	} else {
253
-		p.Signed.Delegations.Roles = append(p.Signed.Delegations.Roles, role)
385
+	if err := tr.VerifyCanSign(parent); err != nil {
386
+		return err
254 387
 	}
255
-	// We've made a change to parent. Set it to dirty
256
-	p.Dirty = true
257
-
258
-	// We don't actually want to create the new delegation metadata yet.
259
-	// When we add a delegation, it may only be signable by a key we don't have
260
-	// (hence we are delegating signing).
261 388
 
262
-	tr.keysDB.AddRole(role)
263
-	utils.RemoveUnusedKeys(p)
389
+	// check the parent role's metadata
390
+	_, ok := tr.Targets[parent]
391
+	if !ok { // the parent targetfile may not exist yet
392
+		// if not, this is an error because a delegation must exist to edit only paths
393
+		return data.ErrInvalidRole{Role: roleName, Reason: "no valid delegated role exists"}
394
+	}
264 395
 
396
+	// Walk to the parent of this delegation, since that is where its role metadata exists
397
+	// We do not have to verify that the walker reached its desired role in this scenario
398
+	// since we've already done another walk to the parent role in VerifyCanSign
399
+	err := tr.WalkTargets("", parent, delegationUpdateVisitor(roleName, data.KeyList{}, []string{}, addPaths, removePaths, clearPaths, notary.MinThreshold))
400
+	if err != nil {
401
+		return err
402
+	}
265 403
 	return nil
266 404
 }
267 405
 
268 406
 // DeleteDelegation removes a delegated targets role from its parent
269 407
 // targets object. It also deletes the delegation from the snapshot.
270 408
 // DeleteDelegation will only make use of the role Name field.
271
-func (tr *Repo) DeleteDelegation(role data.Role) error {
272
-	if !role.IsDelegation() {
273
-		return data.ErrInvalidRole{Role: role.Name, Reason: "not a valid delegated role"}
409
+func (tr *Repo) DeleteDelegation(roleName string) error {
410
+	if !data.IsDelegation(roleName) {
411
+		return data.ErrInvalidRole{Role: roleName, Reason: "not a valid delegated role"}
274 412
 	}
275
-	// the role variable must not be used past this assignment for safety
276
-	name := role.Name
277 413
 
278
-	parent := path.Dir(name)
414
+	parent := path.Dir(roleName)
279 415
 	if err := tr.VerifyCanSign(parent); err != nil {
280 416
 		return err
281 417
 	}
282 418
 
283 419
 	// delete delegated data from Targets map and Snapshot - if they don't
284 420
 	// exist, these are no-op
285
-	delete(tr.Targets, name)
286
-	tr.Snapshot.DeleteMeta(name)
421
+	delete(tr.Targets, roleName)
422
+	tr.Snapshot.DeleteMeta(roleName)
287 423
 
288 424
 	p, ok := tr.Targets[parent]
289 425
 	if !ok {
... ...
@@ -292,7 +420,7 @@ func (tr *Repo) DeleteDelegation(role data.Role) error {
292 292
 		return nil
293 293
 	}
294 294
 
295
-	foundAt := utils.FindRoleIndex(p.Signed.Delegations.Roles, name)
295
+	foundAt := utils.FindRoleIndex(p.Signed.Delegations.Roles, roleName)
296 296
 
297 297
 	if foundAt >= 0 {
298 298
 		var roles []*data.Role
... ...
@@ -311,53 +439,32 @@ func (tr *Repo) DeleteDelegation(role data.Role) error {
311 311
 	return nil
312 312
 }
313 313
 
314
-// InitRepo creates the base files for a repo. It inspects data.BaseRoles and
315
-// data.ValidTypes to determine what the role names and filename should be. It
316
-// also relies on the keysDB having already been populated with the keys and
317
-// roles.
318
-func (tr *Repo) InitRepo(consistent bool) error {
319
-	if err := tr.InitRoot(consistent); err != nil {
320
-		return err
321
-	}
322
-	if _, err := tr.InitTargets(data.CanonicalTargetsRole); err != nil {
323
-		return err
324
-	}
325
-	if err := tr.InitSnapshot(); err != nil {
326
-		return err
327
-	}
328
-	return tr.InitTimestamp()
329
-}
330
-
331
-// InitRoot initializes an empty root file with the 4 core roles based
332
-// on the current content of th ekey db
333
-func (tr *Repo) InitRoot(consistent bool) error {
314
+// InitRoot initializes an empty root file with the 4 core roles passed to the
315
+// method, and the consistent flag.
316
+func (tr *Repo) InitRoot(root, timestamp, snapshot, targets data.BaseRole, consistent bool) error {
334 317
 	rootRoles := make(map[string]*data.RootRole)
335 318
 	rootKeys := make(map[string]data.PublicKey)
336
-	for _, r := range data.BaseRoles {
337
-		role := tr.keysDB.GetRole(r)
338
-		if role == nil {
339
-			return data.ErrInvalidRole{Role: data.CanonicalRootRole, Reason: "root role not initialized in key database"}
319
+
320
+	for _, r := range []data.BaseRole{root, timestamp, snapshot, targets} {
321
+		rootRoles[r.Name] = &data.RootRole{
322
+			Threshold: r.Threshold,
323
+			KeyIDs:    r.ListKeyIDs(),
340 324
 		}
341
-		rootRoles[r] = &role.RootRole
342
-		for _, kid := range role.KeyIDs {
343
-			// don't need to check if GetKey returns nil, Key presence was
344
-			// checked by KeyDB when role was added.
345
-			key := tr.keysDB.GetKey(kid)
346
-			rootKeys[kid] = key
325
+		for kid, k := range r.Keys {
326
+			rootKeys[kid] = k
347 327
 		}
348 328
 	}
349
-	root, err := data.NewRoot(rootKeys, rootRoles, consistent)
329
+	r, err := data.NewRoot(rootKeys, rootRoles, consistent)
350 330
 	if err != nil {
351 331
 		return err
352 332
 	}
353
-	tr.Root = root
333
+	tr.Root = r
354 334
 	return nil
355 335
 }
356 336
 
357 337
 // InitTargets initializes an empty targets, and returns the new empty target
358 338
 func (tr *Repo) InitTargets(role string) (*data.SignedTargets, error) {
359
-	r := data.Role{Name: role}
360
-	if !r.IsDelegation() && role != data.CanonicalTargetsRole {
339
+	if !data.IsDelegation(role) && role != data.CanonicalTargetsRole {
361 340
 		return nil, data.ErrInvalidRole{
362 341
 			Role:   role,
363 342
 			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) {
371 371
 // InitSnapshot initializes a snapshot based on the current root and targets
372 372
 func (tr *Repo) InitSnapshot() error {
373 373
 	if tr.Root == nil {
374
-		return ErrNotLoaded{role: "root"}
374
+		return ErrNotLoaded{Role: data.CanonicalRootRole}
375 375
 	}
376 376
 	root, err := tr.Root.ToSigned()
377 377
 	if err != nil {
... ...
@@ -379,7 +486,7 @@ func (tr *Repo) InitSnapshot() error {
379 379
 	}
380 380
 
381 381
 	if _, ok := tr.Targets[data.CanonicalTargetsRole]; !ok {
382
-		return ErrNotLoaded{role: "targets"}
382
+		return ErrNotLoaded{Role: data.CanonicalTargetsRole}
383 383
 	}
384 384
 	targets, err := tr.Targets[data.CanonicalTargetsRole].ToSigned()
385 385
 	if err != nil {
... ...
@@ -408,31 +515,8 @@ func (tr *Repo) InitTimestamp() error {
408 408
 	return nil
409 409
 }
410 410
 
411
-// SetRoot parses the Signed object into a SignedRoot object, sets
412
-// the keys and roles in the KeyDB, and sets the Repo.Root field
413
-// to the SignedRoot object.
411
+// SetRoot sets the Repo.Root field to the SignedRoot object.
414 412
 func (tr *Repo) SetRoot(s *data.SignedRoot) error {
415
-	for _, key := range s.Signed.Keys {
416
-		logrus.Debug("Adding key ", key.ID())
417
-		tr.keysDB.AddKey(key)
418
-	}
419
-	for roleName, role := range s.Signed.Roles {
420
-		logrus.Debugf("Adding role %s with keys %s", roleName, strings.Join(role.KeyIDs, ","))
421
-		baseRole, err := data.NewRole(
422
-			roleName,
423
-			role.Threshold,
424
-			role.KeyIDs,
425
-			nil,
426
-			nil,
427
-		)
428
-		if err != nil {
429
-			return err
430
-		}
431
-		err = tr.keysDB.AddRole(baseRole)
432
-		if err != nil {
433
-			return err
434
-		}
435
-	}
436 413
 	tr.Root = s
437 414
 	return nil
438 415
 }
... ...
@@ -451,16 +535,9 @@ func (tr *Repo) SetSnapshot(s *data.SignedSnapshot) error {
451 451
 	return nil
452 452
 }
453 453
 
454
-// SetTargets parses the Signed object into a SignedTargets object,
455
-// reads the delegated roles and keys into the KeyDB, and sets the
456
-// SignedTargets object agaist the role in the Repo.Targets map.
454
+// SetTargets sets the SignedTargets object agaist the role in the
455
+// Repo.Targets map.
457 456
 func (tr *Repo) SetTargets(role string, s *data.SignedTargets) error {
458
-	for _, k := range s.Signed.Delegations.Keys {
459
-		tr.keysDB.AddKey(k)
460
-	}
461
-	for _, r := range s.Signed.Delegations.Roles {
462
-		tr.keysDB.AddRole(r)
463
-	}
464 457
 	tr.Targets[role] = s
465 458
 	return nil
466 459
 }
... ...
@@ -479,15 +556,11 @@ func (tr Repo) TargetMeta(role, path string) *data.FileMeta {
479 479
 
480 480
 // TargetDelegations returns a slice of Roles that are valid publishers
481 481
 // for the target path provided.
482
-func (tr Repo) TargetDelegations(role, path, pathHex string) []*data.Role {
483
-	if pathHex == "" {
484
-		pathDigest := sha256.Sum256([]byte(path))
485
-		pathHex = hex.EncodeToString(pathDigest[:])
486
-	}
482
+func (tr Repo) TargetDelegations(role, path string) []*data.Role {
487 483
 	var roles []*data.Role
488 484
 	if t, ok := tr.Targets[role]; ok {
489 485
 		for _, r := range t.Signed.Delegations.Roles {
490
-			if r.CheckPrefixes(pathHex) || r.CheckPaths(path) {
486
+			if r.CheckPaths(path) {
491 487
 				roles = append(roles, r)
492 488
 			}
493 489
 		}
... ...
@@ -495,50 +568,34 @@ func (tr Repo) TargetDelegations(role, path, pathHex string) []*data.Role {
495 495
 	return roles
496 496
 }
497 497
 
498
-// FindTarget attempts to find the target represented by the given
499
-// path by starting at the top targets file and traversing
500
-// appropriate delegations until the first entry is found or it
501
-// runs out of locations to search.
502
-// N.B. Multiple entries may exist in different delegated roles
503
-//      for the same target. Only the first one encountered is returned.
504
-func (tr Repo) FindTarget(path string) *data.FileMeta {
505
-	pathDigest := sha256.Sum256([]byte(path))
506
-	pathHex := hex.EncodeToString(pathDigest[:])
507
-
508
-	var walkTargets func(role string) *data.FileMeta
509
-	walkTargets = func(role string) *data.FileMeta {
510
-		if m := tr.TargetMeta(role, path); m != nil {
511
-			return m
512
-		}
513
-		// Depth first search of delegations based on order
514
-		// as presented in current targets file for role:
515
-		for _, r := range tr.TargetDelegations(role, path, pathHex) {
516
-			if m := walkTargets(r.Name); m != nil {
517
-				return m
518
-			}
519
-		}
520
-		return nil
521
-	}
522
-
523
-	return walkTargets("targets")
524
-}
525
-
526 498
 // VerifyCanSign returns nil if the role exists and we have at least one
527 499
 // signing key for the role, false otherwise.  This does not check that we have
528 500
 // enough signing keys to meet the threshold, since we want to support the use
529 501
 // case of multiple signers for a role.  It returns an error if the role doesn't
530 502
 // exist or if there are no signing keys.
531 503
 func (tr *Repo) VerifyCanSign(roleName string) error {
532
-	role := tr.keysDB.GetRole(roleName)
533
-	if role == nil {
504
+	var (
505
+		role data.BaseRole
506
+		err  error
507
+	)
508
+	// we only need the BaseRole part of a delegation because we're just
509
+	// checking KeyIDs
510
+	if data.IsDelegation(roleName) {
511
+		r, err := tr.GetDelegationRole(roleName)
512
+		if err != nil {
513
+			return err
514
+		}
515
+		role = r.BaseRole
516
+	} else {
517
+		role, err = tr.GetBaseRole(roleName)
518
+	}
519
+	if err != nil {
534 520
 		return data.ErrInvalidRole{Role: roleName, Reason: "does not exist"}
535 521
 	}
536 522
 
537
-	for _, keyID := range role.KeyIDs {
538
-		k := tr.keysDB.GetKey(keyID)
539
-		canonicalID, err := utils.CanonicalKeyID(k)
523
+	for keyID, k := range role.Keys {
540 524
 		check := []string{keyID}
541
-		if err == nil {
525
+		if canonicalID, err := utils.CanonicalKeyID(k); err == nil {
542 526
 			check = append(check, canonicalID)
543 527
 		}
544 528
 		for _, id := range check {
... ...
@@ -548,45 +605,123 @@ func (tr *Repo) VerifyCanSign(roleName string) error {
548 548
 			}
549 549
 		}
550 550
 	}
551
-	return signed.ErrNoKeys{KeyIDs: role.KeyIDs}
551
+	return signed.ErrNoKeys{KeyIDs: role.ListKeyIDs()}
552
+}
553
+
554
+// used for walking the targets/delegations tree, potentially modifying the underlying SignedTargets for the repo
555
+type walkVisitorFunc func(*data.SignedTargets, data.DelegationRole) interface{}
556
+
557
+// WalkTargets will apply the specified visitor function to iteratively walk the targets/delegation metadata tree,
558
+// until receiving a StopWalk.  The walk starts from the base "targets" role, and searches for the correct targetPath and/or rolePath
559
+// to call the visitor function on.  Any roles passed into skipRoles will be excluded from the walk, as well as roles in those subtrees
560
+func (tr *Repo) WalkTargets(targetPath, rolePath string, visitTargets walkVisitorFunc, skipRoles ...string) error {
561
+	// Start with the base targets role, which implicitly has the "" targets path
562
+	targetsRole, err := tr.GetBaseRole(data.CanonicalTargetsRole)
563
+	if err != nil {
564
+		return err
565
+	}
566
+	// Make the targets role have the empty path, when we treat it as a delegation role
567
+	roles := []data.DelegationRole{
568
+		{
569
+			BaseRole: targetsRole,
570
+			Paths:    []string{""},
571
+		},
572
+	}
573
+
574
+	for len(roles) > 0 {
575
+		role := roles[0]
576
+		roles = roles[1:]
577
+
578
+		// Check the role metadata
579
+		signedTgt, ok := tr.Targets[role.Name]
580
+		if !ok {
581
+			// The role meta doesn't exist in the repo so continue onward
582
+			continue
583
+		}
584
+
585
+		// We're at a prefix of the desired role subtree, so add its delegation role children and continue walking
586
+		if strings.HasPrefix(rolePath, role.Name+"/") {
587
+			roles = append(roles, signedTgt.GetValidDelegations(role)...)
588
+			continue
589
+		}
590
+
591
+		// Determine whether to visit this role or not:
592
+		// If the paths validate against the specified targetPath and the rolePath is empty or is in the subtree
593
+		// Also check if we are choosing to skip visiting this role on this walk (see ListTargets and GetTargetByName priority)
594
+		if isValidPath(targetPath, role) && isAncestorRole(role.Name, rolePath) && !utils.StrSliceContains(skipRoles, role.Name) {
595
+			// If we had matching path or role name, visit this target and determine whether or not to keep walking
596
+			res := visitTargets(signedTgt, role)
597
+			switch typedRes := res.(type) {
598
+			case StopWalk:
599
+				// If the visitor function signalled a stop, return nil to finish the walk
600
+				return nil
601
+			case nil:
602
+				// If the visitor function signalled to continue, add this role's delegation to the walk
603
+				roles = append(roles, signedTgt.GetValidDelegations(role)...)
604
+			case error:
605
+				// Propagate any errors from the visitor
606
+				return typedRes
607
+			default:
608
+				// Return out with an error if we got a different result
609
+				return fmt.Errorf("unexpected return while walking: %v", res)
610
+			}
611
+
612
+		}
613
+	}
614
+	return nil
615
+}
616
+
617
+// helper function that returns whether the candidateChild role name is an ancestor or equal to the candidateAncestor role name
618
+// Will return true if given an empty candidateAncestor role name
619
+// The HasPrefix check is for determining whether the role name for candidateChild is a child (direct or further down the chain)
620
+// of candidateAncestor, for ex: candidateAncestor targets/a and candidateChild targets/a/b/c
621
+func isAncestorRole(candidateChild, candidateAncestor string) bool {
622
+	return candidateAncestor == "" || candidateAncestor == candidateChild || strings.HasPrefix(candidateChild, candidateAncestor+"/")
623
+}
624
+
625
+// helper function that returns whether the delegation Role is valid against the given path
626
+// Will return true if given an empty candidatePath
627
+func isValidPath(candidatePath string, delgRole data.DelegationRole) bool {
628
+	return candidatePath == "" || delgRole.CheckPaths(candidatePath)
552 629
 }
553 630
 
554 631
 // AddTargets will attempt to add the given targets specifically to
555 632
 // the directed role. If the metadata for the role doesn't exist yet,
556 633
 // AddTargets will create one.
557 634
 func (tr *Repo) AddTargets(role string, targets data.Files) (data.Files, error) {
558
-
559 635
 	err := tr.VerifyCanSign(role)
560 636
 	if err != nil {
561 637
 		return nil, err
562 638
 	}
563 639
 
564
-	// check the role's metadata
565
-	t, ok := tr.Targets[role]
640
+	// check existence of the role's metadata
641
+	_, ok := tr.Targets[role]
566 642
 	if !ok { // the targetfile may not exist yet - if not, then create it
567 643
 		var err error
568
-		t, err = tr.InitTargets(role)
644
+		_, err = tr.InitTargets(role)
569 645
 		if err != nil {
570 646
 			return nil, err
571 647
 		}
572 648
 	}
573 649
 
574
-	// VerifyCanSign already makes sure this is not nil
575
-	r := tr.keysDB.GetRole(role)
650
+	addedTargets := make(data.Files)
651
+	addTargetVisitor := func(targetPath string, targetMeta data.FileMeta) func(*data.SignedTargets, data.DelegationRole) interface{} {
652
+		return func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} {
653
+			// We've already validated the role's target path in our walk, so just modify the metadata
654
+			tgt.Signed.Targets[targetPath] = targetMeta
655
+			tgt.Dirty = true
656
+			// Also add to our new addedTargets map to keep track of every target we've added successfully
657
+			addedTargets[targetPath] = targetMeta
658
+			return StopWalk{}
659
+		}
660
+	}
576 661
 
577
-	invalid := make(data.Files)
662
+	// Walk the role tree while validating the target paths, and add all of our targets
578 663
 	for path, target := range targets {
579
-		pathDigest := sha256.Sum256([]byte(path))
580
-		pathHex := hex.EncodeToString(pathDigest[:])
581
-		if role == data.CanonicalTargetsRole || (r.CheckPaths(path) || r.CheckPrefixes(pathHex)) {
582
-			t.Signed.Targets[path] = target
583
-		} else {
584
-			invalid[path] = target
585
-		}
664
+		tr.WalkTargets(path, role, addTargetVisitor(path, target))
586 665
 	}
587
-	t.Dirty = true
588
-	if len(invalid) > 0 {
589
-		return invalid, fmt.Errorf("Could not add all targets")
666
+	if len(addedTargets) != len(targets) {
667
+		return nil, fmt.Errorf("Could not add all targets")
590 668
 	}
591 669
 	return nil, nil
592 670
 }
... ...
@@ -597,13 +732,23 @@ func (tr *Repo) RemoveTargets(role string, targets ...string) error {
597 597
 		return err
598 598
 	}
599 599
 
600
+	removeTargetVisitor := func(targetPath string) func(*data.SignedTargets, data.DelegationRole) interface{} {
601
+		return func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} {
602
+			// We've already validated the role path in our walk, so just modify the metadata
603
+			// We don't check against the target path against the valid role paths because it's
604
+			// possible we got into an invalid state and are trying to fix it
605
+			delete(tgt.Signed.Targets, targetPath)
606
+			tgt.Dirty = true
607
+			return StopWalk{}
608
+		}
609
+	}
610
+
600 611
 	// if the role exists but metadata does not yet, then our work is done
601
-	t, ok := tr.Targets[role]
612
+	_, ok := tr.Targets[role]
602 613
 	if ok {
603 614
 		for _, path := range targets {
604
-			delete(t.Signed.Targets, path)
615
+			tr.WalkTargets("", role, removeTargetVisitor(path))
605 616
 		}
606
-		t.Dirty = true
607 617
 	}
608 618
 
609 619
 	return nil
... ...
@@ -644,12 +789,15 @@ func (tr *Repo) SignRoot(expires time.Time) (*data.Signed, error) {
644 644
 	logrus.Debug("signing root...")
645 645
 	tr.Root.Signed.Expires = expires
646 646
 	tr.Root.Signed.Version++
647
-	root := tr.keysDB.GetRole(data.CanonicalRootRole)
647
+	root, err := tr.GetBaseRole(data.CanonicalRootRole)
648
+	if err != nil {
649
+		return nil, err
650
+	}
648 651
 	signed, err := tr.Root.ToSigned()
649 652
 	if err != nil {
650 653
 		return nil, err
651 654
 	}
652
-	signed, err = tr.sign(signed, *root)
655
+	signed, err = tr.sign(signed, root)
653 656
 	if err != nil {
654 657
 		return nil, err
655 658
 	}
... ...
@@ -673,8 +821,22 @@ func (tr *Repo) SignTargets(role string, expires time.Time) (*data.Signed, error
673 673
 		logrus.Debug("errored getting targets data.Signed object")
674 674
 		return nil, err
675 675
 	}
676
-	targets := tr.keysDB.GetRole(role)
677
-	signed, err = tr.sign(signed, *targets)
676
+
677
+	var targets data.BaseRole
678
+	if role == data.CanonicalTargetsRole {
679
+		targets, err = tr.GetBaseRole(role)
680
+	} else {
681
+		tr, err := tr.GetDelegationRole(role)
682
+		if err != nil {
683
+			return nil, err
684
+		}
685
+		targets = tr.BaseRole
686
+	}
687
+	if err != nil {
688
+		return nil, err
689
+	}
690
+
691
+	signed, err = tr.sign(signed, targets)
678 692
 	if err != nil {
679 693
 		logrus.Debug("errored signing ", role)
680 694
 		return nil, err
... ...
@@ -712,8 +874,11 @@ func (tr *Repo) SignSnapshot(expires time.Time) (*data.Signed, error) {
712 712
 	if err != nil {
713 713
 		return nil, err
714 714
 	}
715
-	snapshot := tr.keysDB.GetRole(data.CanonicalSnapshotRole)
716
-	signed, err = tr.sign(signed, *snapshot)
715
+	snapshot, err := tr.GetBaseRole(data.CanonicalSnapshotRole)
716
+	if err != nil {
717
+		return nil, err
718
+	}
719
+	signed, err = tr.sign(signed, snapshot)
717 720
 	if err != nil {
718 721
 		return nil, err
719 722
 	}
... ...
@@ -738,8 +903,11 @@ func (tr *Repo) SignTimestamp(expires time.Time) (*data.Signed, error) {
738 738
 	if err != nil {
739 739
 		return nil, err
740 740
 	}
741
-	timestamp := tr.keysDB.GetRole(data.CanonicalTimestampRole)
742
-	signed, err = tr.sign(signed, *timestamp)
741
+	timestamp, err := tr.GetBaseRole(data.CanonicalTimestampRole)
742
+	if err != nil {
743
+		return nil, err
744
+	}
745
+	signed, err = tr.sign(signed, timestamp)
743 746
 	if err != nil {
744 747
 		return nil, err
745 748
 	}
... ...
@@ -748,17 +916,10 @@ func (tr *Repo) SignTimestamp(expires time.Time) (*data.Signed, error) {
748 748
 	return signed, nil
749 749
 }
750 750
 
751
-func (tr Repo) sign(signedData *data.Signed, role data.Role) (*data.Signed, error) {
752
-	ks := make([]data.PublicKey, 0, len(role.KeyIDs))
753
-	for _, kid := range role.KeyIDs {
754
-		k := tr.keysDB.GetKey(kid)
755
-		if k == nil {
756
-			continue
757
-		}
758
-		ks = append(ks, k)
759
-	}
751
+func (tr Repo) sign(signedData *data.Signed, role data.BaseRole) (*data.Signed, error) {
752
+	ks := role.ListKeys()
760 753
 	if len(ks) < 1 {
761
-		return nil, keys.ErrInvalidKey
754
+		return nil, signed.ErrNoKeys{}
762 755
 	}
763 756
 	err := signed.Sign(tr.cryptoService, signedData, ks...)
764 757
 	if err != nil {
... ...
@@ -5,6 +5,7 @@ import (
5 5
 	"crypto/sha256"
6 6
 	"crypto/sha512"
7 7
 	"crypto/tls"
8
+	"encoding/hex"
8 9
 	"fmt"
9 10
 	"io"
10 11
 	"net/http"
... ...
@@ -61,6 +62,17 @@ func StrSliceContains(ss []string, s string) bool {
61 61
 	return false
62 62
 }
63 63
 
64
+// StrSliceRemove removes the the given string from the slice, returning a new slice
65
+func StrSliceRemove(ss []string, s string) []string {
66
+	res := []string{}
67
+	for _, v := range ss {
68
+		if v != s {
69
+			res = append(res, v)
70
+		}
71
+	}
72
+	return res
73
+}
74
+
64 75
 // StrSliceContainsI checks if the given string appears in the slice
65 76
 // in a case insensitive manner
66 77
 func StrSliceContainsI(ss []string, s string) bool {
... ...
@@ -146,3 +158,14 @@ func FindRoleIndex(rs []*data.Role, name string) int {
146 146
 	}
147 147
 	return -1
148 148
 }
149
+
150
+// ConsistentName generates the appropriate HTTP URL path for the role,
151
+// based on whether the repo is marked as consistent. The RemoteStore
152
+// is responsible for adding file extensions.
153
+func ConsistentName(role string, hashSha256 []byte) string {
154
+	if len(hashSha256) > 0 {
155
+		hash := hex.EncodeToString(hashSha256)
156
+		return fmt.Sprintf("%s.%s", role, hash)
157
+	}
158
+	return role
159
+}