Browse code

vendor notary for docker1.11

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

Riyaz Faizullabhoy authored on 2016/03/18 02:23:18
Showing 35 changed files
... ...
@@ -171,7 +171,7 @@ RUN set -x \
171 171
 	&& rm -rf "$GOPATH"
172 172
 
173 173
 # Install notary server
174
-ENV NOTARY_VERSION v0.2.0
174
+ENV NOTARY_VERSION docker-v1.11-3
175 175
 RUN set -x \
176 176
 	&& export GOPATH="$(mktemp -d)" \
177 177
 	&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
... ...
@@ -117,7 +117,7 @@ RUN set -x \
117 117
 	&& rm -rf "$GOPATH"
118 118
 
119 119
 # Install notary server
120
-ENV NOTARY_VERSION v0.2.0
120
+ENV NOTARY_VERSION docker-v1.11-3
121 121
 RUN set -x \
122 122
 	&& export GOPATH="$(mktemp -d)" \
123 123
 	&& 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 v0.2.0
138
+ENV NOTARY_VERSION docker-v1.11-3
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,7 +127,7 @@ RUN set -x \
127 127
 	&& rm -rf "$GOPATH"
128 128
 
129 129
 # Install notary and notary-server
130
-ENV NOTARY_VERSION v0.2.0
130
+ENV NOTARY_VERSION docker-v1.11-3
131 131
 RUN set -x \
132 132
 	&& export GOPATH="$(mktemp -d)" \
133 133
 	&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
... ...
@@ -108,7 +108,7 @@ RUN set -x \
108 108
 	&& rm -rf "$GOPATH"
109 109
 
110 110
 # Install notary server
111
-ENV NOTARY_VERSION v0.2.0
111
+ENV NOTARY_VERSION docker-v1.11-3
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" \
... ...
@@ -202,17 +202,17 @@ func convertTarget(t client.Target) (target, error) {
202 202
 
203 203
 func (cli *DockerCli) getPassphraseRetriever() passphrase.Retriever {
204 204
 	aliasMap := map[string]string{
205
-		"root":             "root",
206
-		"snapshot":         "repository",
207
-		"targets":          "repository",
208
-		"targets/releases": "repository",
205
+		"root":     "root",
206
+		"snapshot": "repository",
207
+		"targets":  "repository",
208
+		"default":  "repository",
209 209
 	}
210 210
 	baseRetriever := passphrase.PromptRetrieverWithInOut(cli.in, cli.out, aliasMap)
211 211
 	env := map[string]string{
212
-		"root":             os.Getenv("DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE"),
213
-		"snapshot":         os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
214
-		"targets":          os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
215
-		"targets/releases": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
212
+		"root":     os.Getenv("DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE"),
213
+		"snapshot": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
214
+		"targets":  os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
215
+		"default":  os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
216 216
 	}
217 217
 
218 218
 	// Backwards compatibility with old env names. We should remove this in 1.10
... ...
@@ -222,11 +222,11 @@ func (cli *DockerCli) getPassphraseRetriever() passphrase.Retriever {
222 222
 			fmt.Fprintf(cli.err, "[DEPRECATED] The environment variable DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE has been deprecated and will be removed in v1.10. Please use DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE\n")
223 223
 		}
224 224
 	}
225
-	if env["snapshot"] == "" || env["targets"] == "" || env["targets/releases"] == "" {
225
+	if env["snapshot"] == "" || env["targets"] == "" || env["default"] == "" {
226 226
 		if passphrase := os.Getenv("DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE"); passphrase != "" {
227 227
 			env["snapshot"] = passphrase
228 228
 			env["targets"] = passphrase
229
-			env["targets/releases"] = passphrase
229
+			env["default"] = passphrase
230 230
 			fmt.Fprintf(cli.err, "[DEPRECATED] The environment variable DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE has been deprecated and will be removed in v1.10. Please use DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE\n")
231 231
 		}
232 232
 	}
... ...
@@ -235,6 +235,10 @@ func (cli *DockerCli) getPassphraseRetriever() passphrase.Retriever {
235 235
 		if v := env[alias]; v != "" {
236 236
 			return v, numAttempts > 1, nil
237 237
 		}
238
+		// For non-root roles, we can also try the "default" alias if it is specified
239
+		if v := env["default"]; v != "" && alias != data.CanonicalRootRole {
240
+			return v, numAttempts > 1, nil
241
+		}
238 242
 		return baseRetriever(keyName, alias, createNew, numAttempts)
239 243
 	}
240 244
 }
... ...
@@ -473,7 +477,7 @@ func (cli *DockerCli) trustedPush(repoInfo *registry.RepositoryInfo, tag string,
473 473
 			sort.Strings(keys)
474 474
 			rootKeyID = keys[0]
475 475
 		} else {
476
-			rootPublicKey, err := repo.CryptoService.Create(data.CanonicalRootRole, data.ECDSAKey)
476
+			rootPublicKey, err := repo.CryptoService.Create(data.CanonicalRootRole, "", data.ECDSAKey)
477 477
 			if err != nil {
478 478
 				return err
479 479
 			}
... ...
@@ -52,7 +52,7 @@ clone git github.com/docker/distribution d06d6d3b093302c02a93153ac7b06ebc0ffd179
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 v0.2.0
55
+clone git github.com/docker/notary docker-v1.11-3
56 56
 
57 57
 clone git google.golang.org/grpc a22b6611561e9f0a3e0919690dd2caf48f14c517 https://github.com/grpc/grpc-go.git
58 58
 clone git github.com/miekg/pkcs11 df8ae6ca730422dba20c768ff38ef7d79077a59f
... ...
@@ -232,6 +232,7 @@ func notaryClientEnv(cmd *exec.Cmd) {
232 232
 		fmt.Sprintf("NOTARY_ROOT_PASSPHRASE=%s", pwd),
233 233
 		fmt.Sprintf("NOTARY_TARGETS_PASSPHRASE=%s", pwd),
234 234
 		fmt.Sprintf("NOTARY_SNAPSHOT_PASSPHRASE=%s", pwd),
235
+		fmt.Sprintf("NOTARY_DELEGATION_PASSPHRASE=%s", pwd),
235 236
 	}
236 237
 	cmd.Env = append(os.Environ(), env...)
237 238
 }
238 239
new file mode 100644
... ...
@@ -0,0 +1,26 @@
0
+# Changelog
1
+
2
+## [v0.2](https://github.com/docker/notary/releases/tag/v0.2.0) 2/24/2016
3
++ Add support for delegation roles in `notary` server and client
4
++ Add `notary CLI` commands for managing delegation roles: `notary delegation`
5
+    + `add`, `list` and `remove` subcommands
6
++ Enhance `notary CLI` commands for adding targets to delegation roles
7
+    + `notary add --roles` and `notary remove --roles` to manipulate targets for delegations
8
++ Support for rotating the snapshot key to one managed by the `notary` server
9
++ Add consistent download functionality to download metadata and content by checksum
10
++ Update `docker-compose` configuration to use official mariadb image
11
+    + deprecate `notarymysql`
12
+    + default to using a volume for `data` directory
13
+    + use separate databases for `notary-server` and `notary-signer` with separate users
14
++ Add `notary CLI` command for changing private key passphrases: `notary key passwd`
15
++ Enhance `notary CLI` commands for importing and exporting keys
16
++ Change default `notary CLI` log level to fatal, introduce new verbose (error-level) and debug-level settings
17
++ Store roles as PEM headers in private keys, incompatible with previous notary v0.1 key format
18
+    + No longer store keys as `<KEY_ID>_role.key`, instead store as `<KEY_ID>.key`; new private keys from new notary clients will crash old notary clients
19
++ Support logging as JSON format on server and signer
20
++ Support mutual TLS between notary client and notary server
21
+
22
+## [v0.1](https://github.com/docker/notary/releases/tag/v0.1) 11/15/2015
23
++ Initial non-alpha `notary` version
24
++ Implement TUF (the update framework) with support for root, targets, snapshot, and timestamp roles
25
++ Add PKCS11 interface to store and sign with keys in HSMs (i.e. Yubikey)
... ...
@@ -1,4 +1,4 @@
1
-FROM golang:1.5.1
1
+FROM golang:1.6.0
2 2
 
3 3
 RUN apt-get update && apt-get install -y \
4 4
 	libltdl-dev \
... ...
@@ -12,6 +12,4 @@ RUN go get golang.org/x/tools/cmd/vet \
12 12
 
13 13
 COPY . /go/src/github.com/docker/notary
14 14
 
15
-ENV GOPATH /go/src/github.com/docker/notary/Godeps/_workspace:$GOPATH
16
-
17 15
 WORKDIR /go/src/github.com/docker/notary
... ...
@@ -20,7 +20,7 @@ GO_EXC = go
20 20
 NOTARYDIR := /go/src/github.com/docker/notary
21 21
 
22 22
 # check to be sure pkcs11 lib is always imported with a build tag
23
-GO_LIST_PKCS11 := $(shell go list -e -f '{{join .Deps "\n"}}' ./... | xargs go list -e -f '{{if not .Standard}}{{.ImportPath}}{{end}}' | grep -q pkcs11)
23
+GO_LIST_PKCS11 := $(shell go list -e -f '{{join .Deps "\n"}}' ./... | grep -v /vendor/ | xargs go list -e -f '{{if not .Standard}}{{.ImportPath}}{{end}}' | grep -q pkcs11)
24 24
 ifeq ($(GO_LIST_PKCS11),)
25 25
 $(info pkcs11 import was not found anywhere without a build tag, yay)
26 26
 else
... ...
@@ -34,7 +34,7 @@ _space := $(empty) $(empty)
34 34
 COVERDIR=.cover
35 35
 COVERPROFILE?=$(COVERDIR)/cover.out
36 36
 COVERMODE=count
37
-PKGS ?= $(shell go list ./... | tr '\n' ' ')
37
+PKGS ?= $(shell go list ./... | grep -v /vendor/ | tr '\n' ' ')
38 38
 
39 39
 GO_VERSION = $(shell go version | awk '{print $$3}')
40 40
 
... ...
@@ -79,22 +79,22 @@ ${PREFIX}/bin/static/notary-signer: NOTARY_VERSION $(shell find . -type f -name
79 79
 	@godep go build -tags ${NOTARY_BUILDTAGS} -o $@ ${GO_LDFLAGS_STATIC} ./cmd/notary-signer
80 80
 endif
81 81
 
82
-vet: 
82
+vet:
83 83
 	@echo "+ $@"
84 84
 ifeq ($(shell uname -s), Darwin)
85
-	@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)"
85
+	@test -z "$(shell find . -iname *test*.go | grep -v _test.go | grep -v vendor | xargs echo "This file should end with '_test':"  | tee /dev/stderr)"
86 86
 else
87
-	@test -z "$(shell find . -iname *test*.go | grep -v _test.go | grep -v Godeps | xargs -r echo "This file should end with '_test':"  | tee /dev/stderr)"
87
+	@test -z "$(shell find . -iname *test*.go | grep -v _test.go | grep -v vendor | xargs -r echo "This file should end with '_test':"  | tee /dev/stderr)"
88 88
 endif
89
-	@test -z "$$(go tool vet -printf=false . 2>&1 | grep -v Godeps/_workspace/src/ | tee /dev/stderr)"
89
+	@test -z "$$(go tool vet -printf=false . 2>&1 | grep -v vendor/ | tee /dev/stderr)"
90 90
 
91 91
 fmt:
92 92
 	@echo "+ $@"
93
-	@test -z "$$(gofmt -s -l .| grep -v .pb. | grep -v Godeps/_workspace/src/ | tee /dev/stderr)"
93
+	@test -z "$$(gofmt -s -l .| grep -v .pb. | grep -v vendor/ | tee /dev/stderr)"
94 94
 
95 95
 lint:
96 96
 	@echo "+ $@"
97
-	@test -z "$$(golint ./... | grep -v .pb. | grep -v Godeps/_workspace/src/ | tee /dev/stderr)"
97
+	@test -z "$$(golint ./... | grep -v .pb. | grep -v vendor/ | tee /dev/stderr)"
98 98
 
99 99
 # Requires that the following:
100 100
 # go get -u github.com/client9/misspell/cmd/misspell
... ...
@@ -104,27 +104,27 @@ lint:
104 104
 # misspell target, don't include Godeps, binaries, python tests, or git files
105 105
 misspell:
106 106
 	@echo "+ $@"
107
-	@test -z "$$(find . -name '*' | grep -v Godeps/_workspace/src/ | grep -v bin/ | grep -v misc/ | grep -v .git/ | xargs misspell | tee /dev/stderr)"
107
+	@test -z "$$(find . -name '*' | grep -v vendor/ | grep -v bin/ | grep -v misc/ | grep -v .git/ | xargs misspell | tee /dev/stderr)"
108 108
 
109 109
 build:
110 110
 	@echo "+ $@"
111
-	@go build -tags "${NOTARY_BUILDTAGS}" -v ${GO_LDFLAGS} ./...
111
+	@go build -tags "${NOTARY_BUILDTAGS}" -v ${GO_LDFLAGS} $(PKGS)
112 112
 
113 113
 # When running `go test ./...`, it runs all the suites in parallel, which causes
114 114
 # problems when running with a yubikey
115 115
 test: TESTOPTS =
116
-test: 
116
+test:
117 117
 	@echo Note: when testing with a yubikey plugged in, make sure to include 'TESTOPTS="-p 1"'
118 118
 	@echo "+ $@ $(TESTOPTS)"
119 119
 	@echo
120
-	go test -tags "${NOTARY_BUILDTAGS}" $(TESTOPTS) ./...
120
+	go test -tags "${NOTARY_BUILDTAGS}" $(TESTOPTS) $(PKGS)
121 121
 
122 122
 test-full: TESTOPTS =
123 123
 test-full: vet lint
124 124
 	@echo Note: when testing with a yubikey plugged in, make sure to include 'TESTOPTS="-p 1"'
125 125
 	@echo "+ $@"
126 126
 	@echo
127
-	go test -tags "${NOTARY_BUILDTAGS}" $(TESTOPTS) -v ./...
127
+	go test -tags "${NOTARY_BUILDTAGS}" $(TESTOPTS) -v $(PKGS)
128 128
 
129 129
 protos:
130 130
 	@protoc --go_out=plugins=grpc:. proto/*.proto
... ...
@@ -139,7 +139,7 @@ define gocover
139 139
 $(GO_EXC) test $(OPTS) $(TESTOPTS) -covermode="$(COVERMODE)" -coverprofile="$(COVERDIR)/$(subst /,-,$(1)).$(subst $(_space),.,$(NOTARY_BUILDTAGS)).coverage.txt" "$(1)" || exit 1;
140 140
 endef
141 141
 
142
-gen-cover: 
142
+gen-cover:
143 143
 	@mkdir -p "$(COVERDIR)"
144 144
 	$(foreach PKG,$(PKGS),$(call gocover,$(PKG)))
145 145
 	rm -f "$(COVERDIR)"/*testutils*.coverage.txt
... ...
@@ -179,7 +179,7 @@ mkdir -p ${PREFIX}/cross/$(1)/$(2);
179 179
 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;
180 180
 endef
181 181
 
182
-cross: 
182
+cross:
183 183
 	$(foreach GOARCH,$(GOARCHS),$(foreach GOOS,$(GOOSES),$(call template,$(GOOS),$(GOARCH))))
184 184
 
185 185
 
... ...
@@ -39,14 +39,25 @@ func (err ErrRepoNotInitialized) Error() string {
39 39
 }
40 40
 
41 41
 // ErrInvalidRemoteRole is returned when the server is requested to manage
42
-// an unsupported key type
42
+// a key type that is not permitted
43 43
 type ErrInvalidRemoteRole struct {
44 44
 	Role string
45 45
 }
46 46
 
47 47
 func (err ErrInvalidRemoteRole) Error() string {
48 48
 	return fmt.Sprintf(
49
-		"notary does not support the server managing the %s key", err.Role)
49
+		"notary does not permit the server managing the %s key", err.Role)
50
+}
51
+
52
+// ErrInvalidLocalRole is returned when the client wants to manage
53
+// a key type that is not permitted
54
+type ErrInvalidLocalRole struct {
55
+	Role string
56
+}
57
+
58
+func (err ErrInvalidLocalRole) Error() string {
59
+	return fmt.Sprintf(
60
+		"notary does not permit the client managing the %s key", err.Role)
50 61
 }
51 62
 
52 63
 // ErrRepositoryNotExist is returned when an action is taken on a remote
... ...
@@ -93,7 +104,7 @@ func repositoryFromKeystores(baseDir, gun, baseURL string, rt http.RoundTripper,
93 93
 		return nil, err
94 94
 	}
95 95
 
96
-	cryptoService := cryptoservice.NewCryptoService(gun, keyStores...)
96
+	cryptoService := cryptoservice.NewCryptoService(keyStores...)
97 97
 
98 98
 	nRepo := &NotaryRepository{
99 99
 		gun:           gun,
... ...
@@ -140,7 +151,7 @@ func NewTarget(targetName string, targetPath string) (*Target, error) {
140 140
 		return nil, err
141 141
 	}
142 142
 
143
-	meta, err := data.NewFileMeta(bytes.NewBuffer(b))
143
+	meta, err := data.NewFileMeta(bytes.NewBuffer(b), data.NotaryDefaultHashes...)
144 144
 	if err != nil {
145 145
 		return nil, err
146 146
 	}
... ...
@@ -223,7 +234,7 @@ func (r *NotaryRepository) Initialize(rootKeyID string, serverManagedRoles ...st
223 223
 	// make unnecessary network calls
224 224
 	for _, role := range locallyManagedKeys {
225 225
 		// This is currently hardcoding the keys to ECDSA.
226
-		key, err := r.CryptoService.Create(role, data.ECDSAKey)
226
+		key, err := r.CryptoService.Create(role, r.gun, data.ECDSAKey)
227 227
 		if err != nil {
228 228
 			return err
229 229
 		}
... ...
@@ -520,6 +531,25 @@ func (r *NotaryRepository) ListRoles() ([]RoleWithSignatures, error) {
520 520
 // Publish pushes the local changes in signed material to the remote notary-server
521 521
 // Conceptually it performs an operation similar to a `git rebase`
522 522
 func (r *NotaryRepository) Publish() error {
523
+	cl, err := r.GetChangelist()
524
+	if err != nil {
525
+		return err
526
+	}
527
+	if err = r.publish(cl); err != nil {
528
+		return err
529
+	}
530
+	if err = cl.Clear(""); err != nil {
531
+		// This is not a critical problem when only a single host is pushing
532
+		// but will cause weird behaviour if changelist cleanup is failing
533
+		// and there are multiple hosts writing to the repo.
534
+		logrus.Warn("Unable to clear changelist. You may want to manually delete the folder ", filepath.Join(r.tufRepoPath, "changelist"))
535
+	}
536
+	return nil
537
+}
538
+
539
+// publish pushes the changes in the given changelist to the remote notary-server
540
+// Conceptually it performs an operation similar to a `git rebase`
541
+func (r *NotaryRepository) publish(cl changelist.Changelist) error {
523 542
 	var initialPublish bool
524 543
 	// update first before publishing
525 544
 	_, err := r.Update(true)
... ...
@@ -543,15 +573,10 @@ func (r *NotaryRepository) Publish() error {
543 543
 			initialPublish = true
544 544
 		} else {
545 545
 			// We could not update, so we cannot publish.
546
-			logrus.Error("Could not publish Repository: ", err.Error())
546
+			logrus.Error("Could not publish Repository since we could not update: ", err.Error())
547 547
 			return err
548 548
 		}
549 549
 	}
550
-
551
-	cl, err := r.GetChangelist()
552
-	if err != nil {
553
-		return err
554
-	}
555 550
 	// apply the changelist to the repo
556 551
 	err = applyChangelist(r.tufRepo, cl)
557 552
 	if err != nil {
... ...
@@ -622,25 +647,14 @@ func (r *NotaryRepository) Publish() error {
622 622
 		return err
623 623
 	}
624 624
 
625
-	err = remote.SetMultiMeta(updatedFiles)
626
-	if err != nil {
627
-		return err
628
-	}
629
-	err = cl.Clear("")
630
-	if err != nil {
631
-		// This is not a critical problem when only a single host is pushing
632
-		// but will cause weird behaviour if changelist cleanup is failing
633
-		// and there are multiple hosts writing to the repo.
634
-		logrus.Warn("Unable to clear changelist. You may want to manually delete the folder ", filepath.Join(r.tufRepoPath, "changelist"))
635
-	}
636
-	return nil
625
+	return remote.SetMultiMeta(updatedFiles)
637 626
 }
638 627
 
639 628
 // bootstrapRepo loads the repository from the local file system.  This attempts
640 629
 // to load metadata for all roles.  Since server snapshots are supported,
641 630
 // if the snapshot metadata fails to load, that's ok.
642 631
 // This can also be unified with some cache reading tools from tuf/client.
643
-// This assumes that bootstrapRepo is only used by Publish()
632
+// This assumes that bootstrapRepo is only used by Publish() or RotateKey()
644 633
 func (r *NotaryRepository) bootstrapRepo() error {
645 634
 	tufRepo := tuf.NewRepo(r.CryptoService)
646 635
 
... ...
@@ -858,37 +872,53 @@ func (r *NotaryRepository) validateRoot(rootJSON []byte) (*data.SignedRoot, erro
858 858
 // creates and adds one new key or delegates managing the key to the server.
859 859
 // These changes are staged in a changelist until publish is called.
860 860
 func (r *NotaryRepository) RotateKey(role string, serverManagesKey bool) error {
861
-	if role == data.CanonicalRootRole || role == data.CanonicalTimestampRole {
862
-		return fmt.Errorf(
863
-			"notary does not currently support rotating the %s key", role)
864
-	}
865
-	if serverManagesKey && role == data.CanonicalTargetsRole {
861
+	switch {
862
+	// We currently support locally or remotely managing snapshot keys...
863
+	case role == data.CanonicalSnapshotRole:
864
+		break
865
+
866
+	// locally managing targets keys only
867
+	case role == data.CanonicalTargetsRole && !serverManagesKey:
868
+		break
869
+	case role == data.CanonicalTargetsRole && serverManagesKey:
866 870
 		return ErrInvalidRemoteRole{Role: data.CanonicalTargetsRole}
871
+
872
+	// and remotely managing timestamp keys only
873
+	case role == data.CanonicalTimestampRole && serverManagesKey:
874
+		break
875
+	case role == data.CanonicalTimestampRole && !serverManagesKey:
876
+		return ErrInvalidLocalRole{Role: data.CanonicalTimestampRole}
877
+
878
+	default:
879
+		return fmt.Errorf("notary does not currently permit rotating the %s key", role)
867 880
 	}
868 881
 
869 882
 	var (
870
-		pubKey data.PublicKey
871
-		err    error
883
+		pubKey    data.PublicKey
884
+		err       error
885
+		errFmtMsg string
872 886
 	)
873
-	if serverManagesKey {
887
+	switch serverManagesKey {
888
+	case true:
874 889
 		pubKey, err = getRemoteKey(r.baseURL, r.gun, role, r.roundTrip)
875
-	} else {
876
-		pubKey, err = r.CryptoService.Create(role, data.ECDSAKey)
890
+		errFmtMsg = "unable to rotate remote key: %s"
891
+	default:
892
+		pubKey, err = r.CryptoService.Create(role, r.gun, data.ECDSAKey)
893
+		errFmtMsg = "unable to generate key: %s"
877 894
 	}
895
+
878 896
 	if err != nil {
879
-		return err
897
+		return fmt.Errorf(errFmtMsg, err)
880 898
 	}
881 899
 
882
-	return r.rootFileKeyChange(role, changelist.ActionCreate, pubKey)
883
-}
884
-
885
-func (r *NotaryRepository) rootFileKeyChange(role, action string, key data.PublicKey) error {
886
-	cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
887
-	if err != nil {
900
+	cl := changelist.NewMemChangelist()
901
+	if err := r.rootFileKeyChange(cl, role, changelist.ActionCreate, pubKey); err != nil {
888 902
 		return err
889 903
 	}
890
-	defer cl.Close()
904
+	return r.publish(cl)
905
+}
891 906
 
907
+func (r *NotaryRepository) rootFileKeyChange(cl changelist.Changelist, role, action string, key data.PublicKey) error {
892 908
 	kl := make(data.KeyList, 0, 1)
893 909
 	kl = append(kl, key)
894 910
 	meta := changelist.TufRootData{
... ...
@@ -907,11 +937,7 @@ func (r *NotaryRepository) rootFileKeyChange(role, action string, key data.Publi
907 907
 		role,
908 908
 		metaJSON,
909 909
 	)
910
-	err = cl.Add(c)
911
-	if err != nil {
912
-		return err
913
-	}
914
-	return nil
910
+	return cl.Add(c)
915 911
 }
916 912
 
917 913
 // DeleteTrustData removes the trust data stored for this repo in the TUF cache and certificate store on the client side
... ...
@@ -4,7 +4,6 @@ import (
4 4
 	"encoding/json"
5 5
 	"fmt"
6 6
 	"net/http"
7
-	"path"
8 7
 	"strings"
9 8
 	"time"
10 9
 
... ...
@@ -130,22 +129,6 @@ func changeTargetsDelegation(repo *tuf.Repo, c changelist.Change) error {
130 130
 
131 131
 }
132 132
 
133
-// applies a function repeatedly, falling back on the parent role, until it no
134
-// longer can
135
-func doWithRoleFallback(role string, doFunc func(string) error) error {
136
-	for role == data.CanonicalTargetsRole || data.IsDelegation(role) {
137
-		err := doFunc(role)
138
-		if err == nil {
139
-			return nil
140
-		}
141
-		if _, ok := err.(data.ErrInvalidRole); !ok {
142
-			return err
143
-		}
144
-		role = path.Dir(role)
145
-	}
146
-	return data.ErrInvalidRole{Role: role}
147
-}
148
-
149 133
 func changeTargetMeta(repo *tuf.Repo, c changelist.Change) error {
150 134
 	var err error
151 135
 	switch c.Action() {
... ...
@@ -158,21 +141,16 @@ func changeTargetMeta(repo *tuf.Repo, c changelist.Change) error {
158 158
 		}
159 159
 		files := data.Files{c.Path(): *meta}
160 160
 
161
-		err = doWithRoleFallback(c.Scope(), func(role string) error {
162
-			_, e := repo.AddTargets(role, files)
163
-			return e
164
-		})
165
-		if err != nil {
161
+		// Attempt to add the target to this role
162
+		if _, err = repo.AddTargets(c.Scope(), files); err != nil {
166 163
 			logrus.Errorf("couldn't add target to %s: %s", c.Scope(), err.Error())
167 164
 		}
168 165
 
169 166
 	case changelist.ActionDelete:
170 167
 		logrus.Debug("changelist remove: ", c.Path())
171 168
 
172
-		err = doWithRoleFallback(c.Scope(), func(role string) error {
173
-			return repo.RemoveTargets(role, c.Path())
174
-		})
175
-		if err != nil {
169
+		// Attempt to remove the target from this role
170
+		if err = repo.RemoveTargets(c.Scope(), c.Path()); err != nil {
176 171
 			logrus.Errorf("couldn't remove target from %s: %s", c.Scope(), err.Error())
177 172
 		}
178 173
 
... ...
@@ -20,6 +20,10 @@ const (
20 20
 	PubCertPerms = 0755
21 21
 	// Sha256HexSize is how big a Sha256 hex is in number of characters
22 22
 	Sha256HexSize = 64
23
+	// SHA256 is the name of SHA256 hash algorithm
24
+	SHA256 = "sha256"
25
+	// SHA512 is the name of SHA512 hash algorithm
26
+	SHA512 = "sha512"
23 27
 	// TrustedCertsDir is the directory, under the notary repo base directory, where trusted certs are stored
24 28
 	TrustedCertsDir = "trusted_certificates"
25 29
 	// PrivDir is the directory, under the notary repo base directory, where private keys are stored
... ...
@@ -38,6 +42,13 @@ const (
38 38
 	NotaryTargetsExpiry   = 3 * Year
39 39
 	NotarySnapshotExpiry  = 3 * Year
40 40
 	NotaryTimestampExpiry = 14 * Day
41
+
42
+	ConsistentMetadataCacheMaxAge = 30 * Day
43
+	CurrentMetadataCacheMaxAge    = 5 * time.Minute
44
+	// CacheMaxAgeLimit is the generally recommended maximum age for Cache-Control headers
45
+	// (one year, in seconds, since one year is forever in terms of internet
46
+	// content)
47
+	CacheMaxAgeLimit = 1 * Year
41 48
 )
42 49
 
43 50
 // NotaryDefaultExpiries is the construct used to configure the default expiry times of
... ...
@@ -5,6 +5,6 @@
5 5
 # subpackage's dependencies within the containing package, as well as the
6 6
 # subpackage itself.
7 7
 
8
-DEPENDENCIES="$(go list -f $'{{range $f := .Deps}}{{$f}}\n{{end}}' ${1} | grep ${2})"
8
+DEPENDENCIES="$(go list -f $'{{range $f := .Deps}}{{$f}}\n{{end}}' ${1} | grep ${2} | grep -v ${2}/vendor)"
9 9
 
10 10
 echo "${1} ${DEPENDENCIES}" | xargs echo -n | tr ' ' ','
... ...
@@ -3,7 +3,6 @@ package cryptoservice
3 3
 import (
4 4
 	"crypto/rand"
5 5
 	"fmt"
6
-	"path/filepath"
7 6
 
8 7
 	"github.com/Sirupsen/logrus"
9 8
 	"github.com/docker/notary/trustmanager"
... ...
@@ -17,17 +16,16 @@ const (
17 17
 // CryptoService implements Sign and Create, holding a specific GUN and keystore to
18 18
 // operate on
19 19
 type CryptoService struct {
20
-	gun       string
21 20
 	keyStores []trustmanager.KeyStore
22 21
 }
23 22
 
24 23
 // NewCryptoService returns an instance of CryptoService
25
-func NewCryptoService(gun string, keyStores ...trustmanager.KeyStore) *CryptoService {
26
-	return &CryptoService{gun: gun, keyStores: keyStores}
24
+func NewCryptoService(keyStores ...trustmanager.KeyStore) *CryptoService {
25
+	return &CryptoService{keyStores: keyStores}
27 26
 }
28 27
 
29 28
 // Create is used to generate keys for targets, snapshots and timestamps
30
-func (cs *CryptoService) Create(role, algorithm string) (data.PublicKey, error) {
29
+func (cs *CryptoService) Create(role, gun, algorithm string) (data.PublicKey, error) {
31 30
 	var privKey data.PrivateKey
32 31
 	var err error
33 32
 
... ...
@@ -52,16 +50,9 @@ func (cs *CryptoService) Create(role, algorithm string) (data.PublicKey, error)
52 52
 	}
53 53
 	logrus.Debugf("generated new %s key for role: %s and keyID: %s", algorithm, role, privKey.ID())
54 54
 
55
-	// Store the private key into our keystore with the name being: /GUN/ID.key with an alias of role
56
-	var keyPath string
57
-	if role == data.CanonicalRootRole {
58
-		keyPath = privKey.ID()
59
-	} else {
60
-		keyPath = filepath.Join(cs.gun, privKey.ID())
61
-	}
62
-
55
+	// Store the private key into our keystore
63 56
 	for _, ks := range cs.keyStores {
64
-		err = ks.AddKey(keyPath, role, privKey)
57
+		err = ks.AddKey(trustmanager.KeyInfo{Role: role, Gun: gun}, privKey)
65 58
 		if err == nil {
66 59
 			return data.PublicKeyFromPrivate(privKey), nil
67 60
 		}
... ...
@@ -74,23 +65,16 @@ func (cs *CryptoService) Create(role, algorithm string) (data.PublicKey, error)
74 74
 }
75 75
 
76 76
 // GetPrivateKey returns a private key and role if present by ID.
77
-// It tries to get the key first without a GUN (in which case it's a root key).
78
-// If that fails, try to get the key with the GUN (non-root key).
79
-// If that fails, then we don't have the key.
80 77
 func (cs *CryptoService) GetPrivateKey(keyID string) (k data.PrivateKey, role string, err error) {
81
-	keyPaths := []string{keyID, filepath.Join(cs.gun, keyID)}
82 78
 	for _, ks := range cs.keyStores {
83
-		for _, keyPath := range keyPaths {
84
-			k, role, err = ks.GetKey(keyPath)
85
-			if err == nil {
86
-				return
87
-			}
88
-			switch err.(type) {
89
-			case trustmanager.ErrPasswordInvalid, trustmanager.ErrAttemptsExceeded:
90
-				return
91
-			default:
92
-				continue
93
-			}
79
+		if k, role, err = ks.GetKey(keyID); err == nil {
80
+			return
81
+		}
82
+		switch err.(type) {
83
+		case trustmanager.ErrPasswordInvalid, trustmanager.ErrAttemptsExceeded:
84
+			return
85
+		default:
86
+			continue
94 87
 		}
95 88
 	}
96 89
 	return // returns whatever the final values were
... ...
@@ -105,12 +89,42 @@ func (cs *CryptoService) GetKey(keyID string) data.PublicKey {
105 105
 	return data.PublicKeyFromPrivate(privKey)
106 106
 }
107 107
 
108
+// GetKeyInfo returns role and GUN info of a key by ID
109
+func (cs *CryptoService) GetKeyInfo(keyID string) (trustmanager.KeyInfo, error) {
110
+	for _, store := range cs.keyStores {
111
+		if info, err := store.GetKeyInfo(keyID); err == nil {
112
+			return info, nil
113
+		}
114
+	}
115
+	return trustmanager.KeyInfo{}, fmt.Errorf("Could not find info for keyID %s", keyID)
116
+}
117
+
108 118
 // RemoveKey deletes a key by ID
109 119
 func (cs *CryptoService) RemoveKey(keyID string) (err error) {
110
-	keyPaths := []string{keyID, filepath.Join(cs.gun, keyID)}
111 120
 	for _, ks := range cs.keyStores {
112
-		for _, keyPath := range keyPaths {
113
-			ks.RemoveKey(keyPath)
121
+		ks.RemoveKey(keyID)
122
+	}
123
+	return // returns whatever the final values were
124
+}
125
+
126
+// AddKey adds a private key to a specified role.
127
+// The GUN is inferred from the cryptoservice itself for non-root roles
128
+func (cs *CryptoService) AddKey(role, gun string, key data.PrivateKey) (err error) {
129
+	// First check if this key already exists in any of our keystores
130
+	for _, ks := range cs.keyStores {
131
+		if keyInfo, err := ks.GetKeyInfo(key.ID()); err == nil {
132
+			if keyInfo.Role != role {
133
+				return fmt.Errorf("key with same ID already exists for role: %s", keyInfo.Role)
134
+			}
135
+			logrus.Debugf("key with same ID %s and role %s already exists", key.ID(), keyInfo.Role)
136
+			return nil
137
+		}
138
+	}
139
+	// If the key didn't exist in any of our keystores, add and return on the first successful keystore
140
+	for _, ks := range cs.keyStores {
141
+		// Try to add to this keystore, return if successful
142
+		if err = ks.AddKey(trustmanager.KeyInfo{Role: role, Gun: gun}, key); err == nil {
143
+			return nil
114 144
 		}
115 145
 	}
116 146
 	return // returns whatever the final values were
... ...
@@ -121,7 +135,7 @@ func (cs *CryptoService) ListKeys(role string) []string {
121 121
 	var res []string
122 122
 	for _, ks := range cs.keyStores {
123 123
 		for k, r := range ks.ListKeys() {
124
-			if r == role {
124
+			if r.Role == role {
125 125
 				res = append(res, k)
126 126
 			}
127 127
 		}
... ...
@@ -134,7 +148,7 @@ func (cs *CryptoService) ListAllKeys() map[string]string {
134 134
 	res := make(map[string]string)
135 135
 	for _, ks := range cs.keyStores {
136 136
 		for k, r := range ks.ListKeys() {
137
-			res[k] = r // keys are content addressed so don't care about overwrites
137
+			res[k] = r.Role // keys are content addressed so don't care about overwrites
138 138
 		}
139 139
 	}
140 140
 	return res
... ...
@@ -11,10 +11,8 @@ import (
11 11
 	"path/filepath"
12 12
 	"strings"
13 13
 
14
-	"github.com/docker/notary"
15 14
 	"github.com/docker/notary/passphrase"
16 15
 	"github.com/docker/notary/trustmanager"
17
-	"github.com/docker/notary/tuf/data"
18 16
 )
19 17
 
20 18
 const zipMadeByUNIX = 3 << 8
... ...
@@ -41,9 +39,6 @@ func (cs *CryptoService) ExportKey(dest io.Writer, keyID, role string) error {
41 41
 		err      error
42 42
 	)
43 43
 
44
-	if role != data.CanonicalRootRole {
45
-		keyID = filepath.Join(cs.gun, keyID)
46
-	}
47 44
 	for _, ks := range cs.keyStores {
48 45
 		pemBytes, err = ks.ExportKey(keyID)
49 46
 		if err != nil {
... ...
@@ -67,7 +62,12 @@ func (cs *CryptoService) ExportKey(dest io.Writer, keyID, role string) error {
67 67
 // ExportKeyReencrypt exports the specified private key to an io.Writer in
68 68
 // PEM format. The key is reencrypted with a new passphrase.
69 69
 func (cs *CryptoService) ExportKeyReencrypt(dest io.Writer, keyID string, newPassphraseRetriever passphrase.Retriever) error {
70
-	privateKey, role, err := cs.GetPrivateKey(keyID)
70
+	privateKey, _, err := cs.GetPrivateKey(keyID)
71
+	if err != nil {
72
+		return err
73
+	}
74
+
75
+	keyInfo, err := cs.GetKeyInfo(keyID)
71 76
 	if err != nil {
72 77
 		return err
73 78
 	}
... ...
@@ -81,7 +81,7 @@ func (cs *CryptoService) ExportKeyReencrypt(dest io.Writer, keyID string, newPas
81 81
 		return err
82 82
 	}
83 83
 
84
-	err = tempKeyStore.AddKey(keyID, role, privateKey)
84
+	err = tempKeyStore.AddKey(keyInfo, privateKey)
85 85
 	if err != nil {
86 86
 		return err
87 87
 	}
... ...
@@ -100,56 +100,6 @@ func (cs *CryptoService) ExportKeyReencrypt(dest io.Writer, keyID string, newPas
100 100
 	return nil
101 101
 }
102 102
 
103
-// ImportRootKey imports a root in PEM format key from an io.Reader
104
-// It prompts for the key's passphrase to verify the data and to determine
105
-// the key ID.
106
-func (cs *CryptoService) ImportRootKey(source io.Reader) error {
107
-	pemBytes, err := ioutil.ReadAll(source)
108
-	if err != nil {
109
-		return err
110
-	}
111
-	return cs.ImportRoleKey(pemBytes, data.CanonicalRootRole, nil)
112
-}
113
-
114
-// ImportRoleKey imports a private key in PEM format key from a byte array
115
-// It prompts for the key's passphrase to verify the data and to determine
116
-// the key ID.
117
-func (cs *CryptoService) ImportRoleKey(pemBytes []byte, role string, newPassphraseRetriever passphrase.Retriever) error {
118
-	var alias string
119
-	var err error
120
-	if role == data.CanonicalRootRole {
121
-		alias = role
122
-		if err = checkRootKeyIsEncrypted(pemBytes); err != nil {
123
-			return err
124
-		}
125
-	} else {
126
-		// Parse the private key to get the key ID so that we can import it to the correct location
127
-		privKey, err := trustmanager.ParsePEMPrivateKey(pemBytes, "")
128
-		if err != nil {
129
-			privKey, _, err = trustmanager.GetPasswdDecryptBytes(newPassphraseRetriever, pemBytes, role, string(role))
130
-			if err != nil {
131
-				return err
132
-			}
133
-		}
134
-		// Since we're importing a non-root role, we need to pass the path as an alias
135
-		alias = filepath.Join(notary.NonRootKeysSubdir, cs.gun, privKey.ID())
136
-		// We also need to ensure that the role is properly set in the PEM headers
137
-		pemBytes, err = trustmanager.KeyToPEM(privKey, role)
138
-		if err != nil {
139
-			return err
140
-		}
141
-	}
142
-
143
-	for _, ks := range cs.keyStores {
144
-		// don't redeclare err, we want the value carried out of the loop
145
-		if err = ks.ImportKey(pemBytes, alias); err == nil {
146
-			return nil //bail on the first keystore we import to
147
-		}
148
-	}
149
-
150
-	return err
151
-}
152
-
153 103
 // ExportAllKeys exports all keys to an io.Writer in zip format.
154 104
 // newPassphraseRetriever will be used to obtain passphrases to use to encrypt the existing keys.
155 105
 func (cs *CryptoService) ExportAllKeys(dest io.Writer, newPassphraseRetriever passphrase.Retriever) error {
... ...
@@ -182,7 +132,7 @@ func (cs *CryptoService) ExportAllKeys(dest io.Writer, newPassphraseRetriever pa
182 182
 // ImportKeysZip imports keys from a zip file provided as an zip.Reader. The
183 183
 // keys in the root_keys directory are left encrypted, but the other keys are
184 184
 // decrypted with the specified passphrase.
185
-func (cs *CryptoService) ImportKeysZip(zipReader zip.Reader) error {
185
+func (cs *CryptoService) ImportKeysZip(zipReader zip.Reader, retriever passphrase.Retriever) error {
186 186
 	// Temporarily store the keys in maps, so we can bail early if there's
187 187
 	// an error (for example, wrong passphrase), without leaving the key
188 188
 	// store in an inconsistent state
... ...
@@ -191,7 +141,6 @@ func (cs *CryptoService) ImportKeysZip(zipReader zip.Reader) error {
191 191
 	// Iterate through the files in the archive. Don't add the keys
192 192
 	for _, f := range zipReader.File {
193 193
 		fNameTrimmed := strings.TrimSuffix(f.Name, filepath.Ext(f.Name))
194
-
195 194
 		rc, err := f.Open()
196 195
 		if err != nil {
197 196
 			return err
... ...
@@ -206,7 +155,7 @@ func (cs *CryptoService) ImportKeysZip(zipReader zip.Reader) error {
206 206
 		// Note that using / as a separator is okay here - the zip
207 207
 		// package guarantees that the separator will be /
208 208
 		if fNameTrimmed[len(fNameTrimmed)-5:] == "_root" {
209
-			if err = checkRootKeyIsEncrypted(fileBytes); err != nil {
209
+			if err = CheckRootKeyIsEncrypted(fileBytes); err != nil {
210 210
 				return err
211 211
 			}
212 212
 		}
... ...
@@ -214,22 +163,21 @@ func (cs *CryptoService) ImportKeysZip(zipReader zip.Reader) error {
214 214
 	}
215 215
 
216 216
 	for keyName, pemBytes := range newKeys {
217
-		if keyName[len(keyName)-5:] == "_root" {
218
-			keyName = "root"
217
+		// Get the key role information as well as its data.PrivateKey representation
218
+		_, keyInfo, err := trustmanager.KeyInfoFromPEM(pemBytes, keyName)
219
+		if err != nil {
220
+			return err
219 221
 		}
220
-		// try to import the key to all key stores. As long as one of them
221
-		// succeeds, consider it a success
222
-		var tmpErr error
223
-		for _, ks := range cs.keyStores {
224
-			if err := ks.ImportKey(pemBytes, keyName); err != nil {
225
-				tmpErr = err
226
-			} else {
227
-				tmpErr = nil
228
-				break
222
+		privKey, err := trustmanager.ParsePEMPrivateKey(pemBytes, "")
223
+		if err != nil {
224
+			privKey, _, err = trustmanager.GetPasswdDecryptBytes(retriever, pemBytes, "", "imported "+keyInfo.Role)
225
+			if err != nil {
226
+				return err
229 227
 			}
230 228
 		}
231
-		if tmpErr != nil {
232
-			return tmpErr
229
+		// Add the key to our cryptoservice, will add to the first successful keystore
230
+		if err = cs.AddKey(keyInfo.Role, keyInfo.Gun, privKey); err != nil {
231
+			return err
233 232
 		}
234 233
 	}
235 234
 
... ...
@@ -271,18 +219,18 @@ func (cs *CryptoService) ExportKeysByGUN(dest io.Writer, gun string, passphraseR
271 271
 }
272 272
 
273 273
 func moveKeysByGUN(oldKeyStore, newKeyStore trustmanager.KeyStore, gun string) error {
274
-	for relKeyPath := range oldKeyStore.ListKeys() {
274
+	for keyID, keyInfo := range oldKeyStore.ListKeys() {
275 275
 		// Skip keys that aren't associated with this GUN
276
-		if !strings.HasPrefix(relKeyPath, filepath.FromSlash(gun)) {
276
+		if keyInfo.Gun != gun {
277 277
 			continue
278 278
 		}
279 279
 
280
-		privKey, alias, err := oldKeyStore.GetKey(relKeyPath)
280
+		privKey, _, err := oldKeyStore.GetKey(keyID)
281 281
 		if err != nil {
282 282
 			return err
283 283
 		}
284 284
 
285
-		err = newKeyStore.AddKey(relKeyPath, alias, privKey)
285
+		err = newKeyStore.AddKey(keyInfo, privKey)
286 286
 		if err != nil {
287 287
 			return err
288 288
 		}
... ...
@@ -292,13 +240,13 @@ func moveKeysByGUN(oldKeyStore, newKeyStore trustmanager.KeyStore, gun string) e
292 292
 }
293 293
 
294 294
 func moveKeys(oldKeyStore, newKeyStore trustmanager.KeyStore) error {
295
-	for f := range oldKeyStore.ListKeys() {
296
-		privateKey, role, err := oldKeyStore.GetKey(f)
295
+	for keyID, keyInfo := range oldKeyStore.ListKeys() {
296
+		privateKey, _, err := oldKeyStore.GetKey(keyID)
297 297
 		if err != nil {
298 298
 			return err
299 299
 		}
300 300
 
301
-		err = newKeyStore.AddKey(f, role, privateKey)
301
+		err = newKeyStore.AddKey(keyInfo, privateKey)
302 302
 
303 303
 		if err != nil {
304 304
 			return err
... ...
@@ -349,9 +297,9 @@ func addKeysToArchive(zipWriter *zip.Writer, newKeyStore *trustmanager.KeyFileSt
349 349
 	return nil
350 350
 }
351 351
 
352
-// checkRootKeyIsEncrypted makes sure the root key is encrypted. We have
352
+// CheckRootKeyIsEncrypted makes sure the root key is encrypted. We have
353 353
 // internal assumptions that depend on this.
354
-func checkRootKeyIsEncrypted(pemBytes []byte) error {
354
+func CheckRootKeyIsEncrypted(pemBytes []byte) error {
355 355
 	block, _ := pem.Decode(pemBytes)
356 356
 	if block == nil {
357 357
 		return ErrNoValidPrivateKey
... ...
@@ -1,4 +1,4 @@
1
-FROM golang:1.5.3
1
+FROM golang:1.6.0
2 2
 MAINTAINER David Lawrence "david.lawrence@docker.com"
3 3
 
4 4
 RUN apt-get update && apt-get install -y \
... ...
@@ -12,9 +12,6 @@ EXPOSE 4443
12 12
 RUN go get github.com/mattes/migrate
13 13
 
14 14
 ENV NOTARYPKG github.com/docker/notary
15
-ENV GOPATH /go/src/${NOTARYPKG}/Godeps/_workspace:$GOPATH
16
-
17
-
18 15
 
19 16
 # Copy the local repo to the expected go path
20 17
 COPY . /go/src/github.com/docker/notary
... ...
@@ -1,4 +1,4 @@
1
-FROM golang:1.5.3
1
+FROM golang:1.6.0
2 2
 MAINTAINER David Lawrence "david.lawrence@docker.com"
3 3
 
4 4
 RUN apt-get update && apt-get install -y \
... ...
@@ -12,7 +12,6 @@ EXPOSE 4444
12 12
 RUN go get github.com/mattes/migrate
13 13
 
14 14
 ENV NOTARYPKG github.com/docker/notary
15
-ENV GOPATH /go/src/${NOTARYPKG}/Godeps/_workspace:$GOPATH
16 15
 ENV NOTARY_SIGNER_DEFAULT_ALIAS="timestamp_1"
17 16
 ENV NOTARY_SIGNER_TIMESTAMP_1="testpassword"
18 17
 
... ...
@@ -13,12 +13,15 @@ import (
13 13
 	"github.com/docker/notary/tuf/data"
14 14
 )
15 15
 
16
+type keyInfoMap map[string]KeyInfo
17
+
16 18
 // KeyFileStore persists and manages private keys on disk
17 19
 type KeyFileStore struct {
18 20
 	sync.Mutex
19 21
 	SimpleFileStore
20 22
 	passphrase.Retriever
21 23
 	cachedKeys map[string]*cachedKey
24
+	keyInfoMap
22 25
 }
23 26
 
24 27
 // KeyMemoryStore manages private keys in memory
... ...
@@ -27,6 +30,14 @@ type KeyMemoryStore struct {
27 27
 	MemoryFileStore
28 28
 	passphrase.Retriever
29 29
 	cachedKeys map[string]*cachedKey
30
+	keyInfoMap
31
+}
32
+
33
+// KeyInfo stores the role, path, and gun for a corresponding private key ID
34
+// It is assumed that each private key ID is unique
35
+type KeyInfo struct {
36
+	Gun  string
37
+	Role string
30 38
 }
31 39
 
32 40
 // NewKeyFileStore returns a new KeyFileStore creating a private directory to
... ...
@@ -38,10 +49,93 @@ func NewKeyFileStore(baseDir string, passphraseRetriever passphrase.Retriever) (
38 38
 		return nil, err
39 39
 	}
40 40
 	cachedKeys := make(map[string]*cachedKey)
41
+	keyInfoMap := make(keyInfoMap)
41 42
 
42
-	return &KeyFileStore{SimpleFileStore: *fileStore,
43
+	keyStore := &KeyFileStore{SimpleFileStore: *fileStore,
43 44
 		Retriever:  passphraseRetriever,
44
-		cachedKeys: cachedKeys}, nil
45
+		cachedKeys: cachedKeys,
46
+		keyInfoMap: keyInfoMap,
47
+	}
48
+
49
+	// Load this keystore's ID --> gun/role map
50
+	keyStore.loadKeyInfo()
51
+	return keyStore, nil
52
+}
53
+
54
+func generateKeyInfoMap(s LimitedFileStore) map[string]KeyInfo {
55
+	keyInfoMap := make(map[string]KeyInfo)
56
+	for _, keyPath := range s.ListFiles() {
57
+		d, err := s.Get(keyPath)
58
+		if err != nil {
59
+			logrus.Error(err)
60
+			continue
61
+		}
62
+		keyID, keyInfo, err := KeyInfoFromPEM(d, keyPath)
63
+		if err != nil {
64
+			logrus.Error(err)
65
+			continue
66
+		}
67
+		keyInfoMap[keyID] = keyInfo
68
+	}
69
+	return keyInfoMap
70
+}
71
+
72
+// Attempts to infer the keyID, role, and GUN from the specified key path.
73
+// Note that non-root roles can only be inferred if this is a legacy style filename: KEYID_ROLE.key
74
+func inferKeyInfoFromKeyPath(keyPath string) (string, string, string) {
75
+	var keyID, role, gun string
76
+	keyID = filepath.Base(keyPath)
77
+	underscoreIndex := strings.LastIndex(keyID, "_")
78
+
79
+	// This is the legacy KEYID_ROLE filename
80
+	// The keyID is the first part of the keyname
81
+	// The keyRole is the second part of the keyname
82
+	// in a key named abcde_root, abcde is the keyID and root is the KeyAlias
83
+	if underscoreIndex != -1 {
84
+		role = keyID[underscoreIndex+1:]
85
+		keyID = keyID[:underscoreIndex]
86
+	}
87
+
88
+	if filepath.HasPrefix(keyPath, notary.RootKeysSubdir+"/") {
89
+		return keyID, data.CanonicalRootRole, ""
90
+	}
91
+
92
+	keyPath = strings.TrimPrefix(keyPath, notary.NonRootKeysSubdir+"/")
93
+	gun = getGunFromFullID(keyPath)
94
+	return keyID, role, gun
95
+}
96
+
97
+func getGunFromFullID(fullKeyID string) string {
98
+	keyGun := filepath.Dir(fullKeyID)
99
+	// If the gun is empty, Dir will return .
100
+	if keyGun == "." {
101
+		keyGun = ""
102
+	}
103
+	return keyGun
104
+}
105
+
106
+func (s *KeyFileStore) loadKeyInfo() {
107
+	s.keyInfoMap = generateKeyInfoMap(s)
108
+}
109
+
110
+func (s *KeyMemoryStore) loadKeyInfo() {
111
+	s.keyInfoMap = generateKeyInfoMap(s)
112
+}
113
+
114
+// GetKeyInfo returns the corresponding gun and role key info for a keyID
115
+func (s *KeyFileStore) GetKeyInfo(keyID string) (KeyInfo, error) {
116
+	if info, ok := s.keyInfoMap[keyID]; ok {
117
+		return info, nil
118
+	}
119
+	return KeyInfo{}, fmt.Errorf("Could not find info for keyID %s", keyID)
120
+}
121
+
122
+// GetKeyInfo returns the corresponding gun and role key info for a keyID
123
+func (s *KeyMemoryStore) GetKeyInfo(keyID string) (KeyInfo, error) {
124
+	if info, ok := s.keyInfoMap[keyID]; ok {
125
+		return info, nil
126
+	}
127
+	return KeyInfo{}, fmt.Errorf("Could not find info for keyID %s", keyID)
45 128
 }
46 129
 
47 130
 // Name returns a user friendly name for the location this store
... ...
@@ -51,55 +145,81 @@ func (s *KeyFileStore) Name() string {
51 51
 }
52 52
 
53 53
 // AddKey stores the contents of a PEM-encoded private key as a PEM block
54
-func (s *KeyFileStore) AddKey(name, role string, privKey data.PrivateKey) error {
54
+func (s *KeyFileStore) AddKey(keyInfo KeyInfo, privKey data.PrivateKey) error {
55 55
 	s.Lock()
56 56
 	defer s.Unlock()
57
-	return addKey(s, s.Retriever, s.cachedKeys, name, role, privKey)
57
+	if keyInfo.Role == data.CanonicalRootRole || data.IsDelegation(keyInfo.Role) || !data.ValidRole(keyInfo.Role) {
58
+		keyInfo.Gun = ""
59
+	}
60
+	err := addKey(s, s.Retriever, s.cachedKeys, filepath.Join(keyInfo.Gun, privKey.ID()), keyInfo.Role, privKey)
61
+	if err != nil {
62
+		return err
63
+	}
64
+	s.keyInfoMap[privKey.ID()] = keyInfo
65
+	return nil
58 66
 }
59 67
 
60 68
 // GetKey returns the PrivateKey given a KeyID
61 69
 func (s *KeyFileStore) GetKey(name string) (data.PrivateKey, string, error) {
62 70
 	s.Lock()
63 71
 	defer s.Unlock()
72
+	// If this is a bare key ID without the gun, prepend the gun so the filestore lookup succeeds
73
+	if keyInfo, ok := s.keyInfoMap[name]; ok {
74
+		name = filepath.Join(keyInfo.Gun, name)
75
+	}
64 76
 	return getKey(s, s.Retriever, s.cachedKeys, name)
65 77
 }
66 78
 
67
-// ListKeys returns a list of unique PublicKeys present on the KeyFileStore.
68
-func (s *KeyFileStore) ListKeys() map[string]string {
69
-	return listKeys(s)
79
+// ListKeys returns a list of unique PublicKeys present on the KeyFileStore, by returning a copy of the keyInfoMap
80
+func (s *KeyFileStore) ListKeys() map[string]KeyInfo {
81
+	return copyKeyInfoMap(s.keyInfoMap)
70 82
 }
71 83
 
72 84
 // RemoveKey removes the key from the keyfilestore
73
-func (s *KeyFileStore) RemoveKey(name string) error {
85
+func (s *KeyFileStore) RemoveKey(keyID string) error {
74 86
 	s.Lock()
75 87
 	defer s.Unlock()
76
-	return removeKey(s, s.cachedKeys, name)
88
+	// If this is a bare key ID without the gun, prepend the gun so the filestore lookup succeeds
89
+	if keyInfo, ok := s.keyInfoMap[keyID]; ok {
90
+		keyID = filepath.Join(keyInfo.Gun, keyID)
91
+	}
92
+	err := removeKey(s, s.cachedKeys, keyID)
93
+	if err != nil {
94
+		return err
95
+	}
96
+	// Remove this key from our keyInfo map if we removed from our filesystem
97
+	delete(s.keyInfoMap, filepath.Base(keyID))
98
+	return nil
77 99
 }
78 100
 
79
-// ExportKey exportes the encrypted bytes from the keystore and writes it to
80
-// dest.
81
-func (s *KeyFileStore) ExportKey(name string) ([]byte, error) {
82
-	keyBytes, _, err := getRawKey(s, name)
101
+// ExportKey exports the encrypted bytes from the keystore
102
+func (s *KeyFileStore) ExportKey(keyID string) ([]byte, error) {
103
+	if keyInfo, ok := s.keyInfoMap[keyID]; ok {
104
+		keyID = filepath.Join(keyInfo.Gun, keyID)
105
+	}
106
+	keyBytes, _, err := getRawKey(s, keyID)
83 107
 	if err != nil {
84 108
 		return nil, err
85 109
 	}
86 110
 	return keyBytes, nil
87 111
 }
88 112
 
89
-// ImportKey imports the private key in the encrypted bytes into the keystore
90
-// with the given key ID and alias.
91
-func (s *KeyFileStore) ImportKey(pemBytes []byte, alias string) error {
92
-	return importKey(s, s.Retriever, s.cachedKeys, alias, pemBytes)
93
-}
94
-
95 113
 // NewKeyMemoryStore returns a new KeyMemoryStore which holds keys in memory
96 114
 func NewKeyMemoryStore(passphraseRetriever passphrase.Retriever) *KeyMemoryStore {
97 115
 	memStore := NewMemoryFileStore()
98 116
 	cachedKeys := make(map[string]*cachedKey)
99 117
 
100
-	return &KeyMemoryStore{MemoryFileStore: *memStore,
118
+	keyInfoMap := make(keyInfoMap)
119
+
120
+	keyStore := &KeyMemoryStore{MemoryFileStore: *memStore,
101 121
 		Retriever:  passphraseRetriever,
102
-		cachedKeys: cachedKeys}
122
+		cachedKeys: cachedKeys,
123
+		keyInfoMap: keyInfoMap,
124
+	}
125
+
126
+	// Load this keystore's ID --> gun/role map
127
+	keyStore.loadKeyInfo()
128
+	return keyStore
103 129
 }
104 130
 
105 131
 // Name returns a user friendly name for the location this store
... ...
@@ -109,45 +229,84 @@ func (s *KeyMemoryStore) Name() string {
109 109
 }
110 110
 
111 111
 // AddKey stores the contents of a PEM-encoded private key as a PEM block
112
-func (s *KeyMemoryStore) AddKey(name, alias string, privKey data.PrivateKey) error {
112
+func (s *KeyMemoryStore) AddKey(keyInfo KeyInfo, privKey data.PrivateKey) error {
113 113
 	s.Lock()
114 114
 	defer s.Unlock()
115
-	return addKey(s, s.Retriever, s.cachedKeys, name, alias, privKey)
115
+	if keyInfo.Role == data.CanonicalRootRole || data.IsDelegation(keyInfo.Role) || !data.ValidRole(keyInfo.Role) {
116
+		keyInfo.Gun = ""
117
+	}
118
+	err := addKey(s, s.Retriever, s.cachedKeys, filepath.Join(keyInfo.Gun, privKey.ID()), keyInfo.Role, privKey)
119
+	if err != nil {
120
+		return err
121
+	}
122
+	s.keyInfoMap[privKey.ID()] = keyInfo
123
+	return nil
116 124
 }
117 125
 
118 126
 // GetKey returns the PrivateKey given a KeyID
119 127
 func (s *KeyMemoryStore) GetKey(name string) (data.PrivateKey, string, error) {
120 128
 	s.Lock()
121 129
 	defer s.Unlock()
130
+	// If this is a bare key ID without the gun, prepend the gun so the filestore lookup succeeds
131
+	if keyInfo, ok := s.keyInfoMap[name]; ok {
132
+		name = filepath.Join(keyInfo.Gun, name)
133
+	}
122 134
 	return getKey(s, s.Retriever, s.cachedKeys, name)
123 135
 }
124 136
 
125
-// ListKeys returns a list of unique PublicKeys present on the KeyFileStore.
126
-func (s *KeyMemoryStore) ListKeys() map[string]string {
127
-	return listKeys(s)
137
+// ListKeys returns a list of unique PublicKeys present on the KeyFileStore, by returning a copy of the keyInfoMap
138
+func (s *KeyMemoryStore) ListKeys() map[string]KeyInfo {
139
+	return copyKeyInfoMap(s.keyInfoMap)
140
+}
141
+
142
+// copyKeyInfoMap returns a deep copy of the passed-in keyInfoMap
143
+func copyKeyInfoMap(keyInfoMap map[string]KeyInfo) map[string]KeyInfo {
144
+	copyMap := make(map[string]KeyInfo)
145
+	for keyID, keyInfo := range keyInfoMap {
146
+		copyMap[keyID] = KeyInfo{Role: keyInfo.Role, Gun: keyInfo.Gun}
147
+	}
148
+	return copyMap
128 149
 }
129 150
 
130 151
 // RemoveKey removes the key from the keystore
131
-func (s *KeyMemoryStore) RemoveKey(name string) error {
152
+func (s *KeyMemoryStore) RemoveKey(keyID string) error {
132 153
 	s.Lock()
133 154
 	defer s.Unlock()
134
-	return removeKey(s, s.cachedKeys, name)
155
+	// If this is a bare key ID without the gun, prepend the gun so the filestore lookup succeeds
156
+	if keyInfo, ok := s.keyInfoMap[keyID]; ok {
157
+		keyID = filepath.Join(keyInfo.Gun, keyID)
158
+	}
159
+	err := removeKey(s, s.cachedKeys, keyID)
160
+	if err != nil {
161
+		return err
162
+	}
163
+	// Remove this key from our keyInfo map if we removed from our filesystem
164
+	delete(s.keyInfoMap, filepath.Base(keyID))
165
+	return nil
135 166
 }
136 167
 
137
-// ExportKey exportes the encrypted bytes from the keystore and writes it to
138
-// dest.
139
-func (s *KeyMemoryStore) ExportKey(name string) ([]byte, error) {
140
-	keyBytes, _, err := getRawKey(s, name)
168
+// ExportKey exports the encrypted bytes from the keystore
169
+func (s *KeyMemoryStore) ExportKey(keyID string) ([]byte, error) {
170
+	keyBytes, _, err := getRawKey(s, keyID)
141 171
 	if err != nil {
142 172
 		return nil, err
143 173
 	}
144 174
 	return keyBytes, nil
145 175
 }
146 176
 
147
-// ImportKey imports the private key in the encrypted bytes into the keystore
148
-// with the given key ID and alias.
149
-func (s *KeyMemoryStore) ImportKey(pemBytes []byte, alias string) error {
150
-	return importKey(s, s.Retriever, s.cachedKeys, alias, pemBytes)
177
+// KeyInfoFromPEM attempts to get a keyID and KeyInfo from the filename and PEM bytes of a key
178
+func KeyInfoFromPEM(pemBytes []byte, filename string) (string, KeyInfo, error) {
179
+	keyID, role, gun := inferKeyInfoFromKeyPath(filename)
180
+	if role == "" {
181
+		block, _ := pem.Decode(pemBytes)
182
+		if block == nil {
183
+			return "", KeyInfo{}, fmt.Errorf("could not decode PEM block for key %s", filename)
184
+		}
185
+		if keyRole, ok := block.Headers["role"]; ok {
186
+			role = keyRole
187
+		}
188
+	}
189
+	return keyID, KeyInfo{Gun: gun, Role: role}, nil
151 190
 }
152 191
 
153 192
 func addKey(s LimitedFileStore, passphraseRetriever passphrase.Retriever, cachedKeys map[string]*cachedKey, name, role string, privKey data.PrivateKey) error {
... ...
@@ -229,50 +388,6 @@ func getKey(s LimitedFileStore, passphraseRetriever passphrase.Retriever, cached
229 229
 	return privKey, keyAlias, nil
230 230
 }
231 231
 
232
-// ListKeys returns a map of unique PublicKeys present on the KeyFileStore and
233
-// their corresponding aliases.
234
-func listKeys(s LimitedFileStore) map[string]string {
235
-	keyIDMap := make(map[string]string)
236
-
237
-	for _, f := range s.ListFiles() {
238
-		// Remove the prefix of the directory from the filename
239
-		var keyIDFull string
240
-		if strings.HasPrefix(f, notary.RootKeysSubdir+"/") {
241
-			keyIDFull = strings.TrimPrefix(f, notary.RootKeysSubdir+"/")
242
-		} else {
243
-			keyIDFull = strings.TrimPrefix(f, notary.NonRootKeysSubdir+"/")
244
-		}
245
-
246
-		keyIDFull = strings.TrimSpace(keyIDFull)
247
-
248
-		// If the key does not have a _, we'll attempt to
249
-		// read it as a PEM
250
-		underscoreIndex := strings.LastIndex(keyIDFull, "_")
251
-		if underscoreIndex == -1 {
252
-			d, err := s.Get(f)
253
-			if err != nil {
254
-				logrus.Error(err)
255
-				continue
256
-			}
257
-			block, _ := pem.Decode(d)
258
-			if block == nil {
259
-				continue
260
-			}
261
-			if role, ok := block.Headers["role"]; ok {
262
-				keyIDMap[keyIDFull] = role
263
-			}
264
-		} else {
265
-			// The keyID is the first part of the keyname
266
-			// The KeyAlias is the second part of the keyname
267
-			// in a key named abcde_root, abcde is the keyID and root is the KeyAlias
268
-			keyID := keyIDFull[:underscoreIndex]
269
-			keyAlias := keyIDFull[underscoreIndex+1:]
270
-			keyIDMap[keyID] = keyAlias
271
-		}
272
-	}
273
-	return keyIDMap
274
-}
275
-
276 232
 // RemoveKey removes the key from the keyfilestore
277 233
 func removeKey(s LimitedFileStore, cachedKeys map[string]*cachedKey, name string) error {
278 234
 	role, legacy, err := getKeyRole(s, name)
... ...
@@ -296,7 +411,7 @@ func removeKey(s LimitedFileStore, cachedKeys map[string]*cachedKey, name string
296 296
 
297 297
 // Assumes 2 subdirectories, 1 containing root keys and 1 containing tuf keys
298 298
 func getSubdir(alias string) string {
299
-	if alias == "root" {
299
+	if alias == data.CanonicalRootRole {
300 300
 		return notary.RootKeysSubdir
301 301
 	}
302 302
 	return notary.NonRootKeysSubdir
... ...
@@ -380,21 +495,3 @@ func encryptAndAddKey(s LimitedFileStore, passwd string, cachedKeys map[string]*
380 380
 	cachedKeys[name] = &cachedKey{alias: role, key: privKey}
381 381
 	return s.Add(filepath.Join(getSubdir(role), name), pemPrivKey)
382 382
 }
383
-
384
-func importKey(s LimitedFileStore, passphraseRetriever passphrase.Retriever, cachedKeys map[string]*cachedKey, alias string, pemBytes []byte) error {
385
-
386
-	if alias != data.CanonicalRootRole {
387
-		return s.Add(alias, pemBytes)
388
-	}
389
-
390
-	privKey, passphrase, err := GetPasswdDecryptBytes(
391
-		passphraseRetriever, pemBytes, "", "imported "+alias)
392
-
393
-	if err != nil {
394
-		return err
395
-	}
396
-
397
-	var name string
398
-	name = privKey.ID()
399
-	return encryptAndAddKey(s, passphrase, cachedKeys, name, alias, privKey)
400
-}
... ...
@@ -40,14 +40,14 @@ const (
40 40
 
41 41
 // KeyStore is a generic interface for private key storage
42 42
 type KeyStore interface {
43
-	// Add Key adds a key to the KeyStore, and if the key already exists,
43
+	// AddKey adds a key to the KeyStore, and if the key already exists,
44 44
 	// succeeds.  Otherwise, returns an error if it cannot add.
45
-	AddKey(name, alias string, privKey data.PrivateKey) error
46
-	GetKey(name string) (data.PrivateKey, string, error)
47
-	ListKeys() map[string]string
48
-	RemoveKey(name string) error
49
-	ExportKey(name string) ([]byte, error)
50
-	ImportKey(pemBytes []byte, alias string) error
45
+	AddKey(keyInfo KeyInfo, privKey data.PrivateKey) error
46
+	GetKey(keyID string) (data.PrivateKey, string, error)
47
+	GetKeyInfo(keyID string) (KeyInfo, error)
48
+	ListKeys() map[string]KeyInfo
49
+	RemoveKey(keyID string) error
50
+	ExportKey(keyID string) ([]byte, error)
51 51
 	Name() string
52 52
 }
53 53
 
... ...
@@ -517,7 +517,7 @@ func EncryptPrivateKey(key data.PrivateKey, role, passphrase string) ([]byte, er
517 517
 // ReadRoleFromPEM returns the value from the role PEM header, if it exists
518 518
 func ReadRoleFromPEM(pemBytes []byte) string {
519 519
 	pemBlock, _ := pem.Decode(pemBytes)
520
-	if pemBlock.Headers == nil {
520
+	if pemBlock == nil || pemBlock.Headers == nil {
521 521
 		return ""
522 522
 	}
523 523
 	role, ok := pemBlock.Headers["role"]
... ...
@@ -617,7 +617,7 @@ func (s *YubiKeyStore) setLibLoader(loader pkcs11LibLoader) {
617 617
 	s.libLoader = loader
618 618
 }
619 619
 
620
-func (s *YubiKeyStore) ListKeys() map[string]string {
620
+func (s *YubiKeyStore) ListKeys() map[string]trustmanager.KeyInfo {
621 621
 	if len(s.keys) > 0 {
622 622
 		return buildKeyMap(s.keys)
623 623
 	}
... ...
@@ -639,15 +639,15 @@ func (s *YubiKeyStore) ListKeys() map[string]string {
639 639
 }
640 640
 
641 641
 // AddKey puts a key inside the Yubikey, as well as writing it to the backup store
642
-func (s *YubiKeyStore) AddKey(keyID, role string, privKey data.PrivateKey) error {
643
-	added, err := s.addKey(keyID, role, privKey)
642
+func (s *YubiKeyStore) AddKey(keyInfo trustmanager.KeyInfo, privKey data.PrivateKey) error {
643
+	added, err := s.addKey(privKey.ID(), keyInfo.Role, privKey)
644 644
 	if err != nil {
645 645
 		return err
646 646
 	}
647
-	if added {
648
-		err = s.backupStore.AddKey(privKey.ID(), role, privKey)
647
+	if added && s.backupStore != nil {
648
+		err = s.backupStore.AddKey(keyInfo, privKey)
649 649
 		if err != nil {
650
-			defer s.RemoveKey(keyID)
650
+			defer s.RemoveKey(privKey.ID())
651 651
 			return ErrBackupFailed{err: err.Error()}
652 652
 		}
653 653
 	}
... ...
@@ -762,20 +762,9 @@ func (s *YubiKeyStore) ExportKey(keyID string) ([]byte, error) {
762 762
 	return nil, errors.New("Keys cannot be exported from a Yubikey.")
763 763
 }
764 764
 
765
-// ImportKey imports a root key into a Yubikey
766
-func (s *YubiKeyStore) ImportKey(pemBytes []byte, keyPath string) error {
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
-	}
771
-	privKey, _, err := trustmanager.GetPasswdDecryptBytes(
772
-		s.passRetriever, pemBytes, "", "imported root")
773
-	if err != nil {
774
-		logrus.Debugf("Failed to get and retrieve a key from: %s", keyPath)
775
-		return err
776
-	}
777
-	_, err = s.addKey(privKey.ID(), "root", privKey)
778
-	return err
765
+// Not yet implemented
766
+func (s *YubiKeyStore) GetKeyInfo(keyID string) (trustmanager.KeyInfo, error) {
767
+	return trustmanager.KeyInfo{}, fmt.Errorf("Not yet implemented")
779 768
 }
780 769
 
781 770
 func cleanup(ctx IPKCS11Ctx, session pkcs11.SessionHandle) {
... ...
@@ -890,10 +879,10 @@ func login(ctx IPKCS11Ctx, session pkcs11.SessionHandle, passRetriever passphras
890 890
 	return nil
891 891
 }
892 892
 
893
-func buildKeyMap(keys map[string]yubiSlot) map[string]string {
894
-	res := make(map[string]string)
893
+func buildKeyMap(keys map[string]yubiSlot) map[string]trustmanager.KeyInfo {
894
+	res := make(map[string]trustmanager.KeyInfo)
895 895
 	for k, v := range keys {
896
-		res[k] = v.role
896
+		res[k] = trustmanager.KeyInfo{Role: v.role, Gun: ""}
897 897
 	}
898 898
 	return res
899 899
 }
... ...
@@ -1,8 +1,6 @@
1 1
 package client
2 2
 
3 3
 import (
4
-	"bytes"
5
-	"crypto/sha256"
6 4
 	"encoding/json"
7 5
 	"fmt"
8 6
 	"path"
... ...
@@ -92,16 +90,16 @@ func (c *Client) update() error {
92 92
 func (c Client) checkRoot() error {
93 93
 	role := data.CanonicalRootRole
94 94
 	size := c.local.Snapshot.Signed.Meta[role].Length
95
-	hashSha256 := c.local.Snapshot.Signed.Meta[role].Hashes["sha256"]
95
+
96
+	expectedHashes := c.local.Snapshot.Signed.Meta[role].Hashes
96 97
 
97 98
 	raw, err := c.cache.GetMeta("root", size)
98 99
 	if err != nil {
99 100
 		return err
100 101
 	}
101 102
 
102
-	hash := sha256.Sum256(raw)
103
-	if !bytes.Equal(hash[:], hashSha256) {
104
-		return fmt.Errorf("Cached root sha256 did not match snapshot root sha256")
103
+	if err := data.CheckHashes(raw, expectedHashes); err != nil {
104
+		return fmt.Errorf("Cached root hashes did not match snapshot root hashes")
105 105
 	}
106 106
 
107 107
 	if int64(len(raw)) != size {
... ...
@@ -127,11 +125,19 @@ func (c *Client) downloadRoot() error {
127 127
 	// We can't read an exact size for the root metadata without risking getting stuck in the TUF update cycle
128 128
 	// since it's possible that downloading timestamp/snapshot metadata may fail due to a signature mismatch
129 129
 	var size int64 = -1
130
-	var expectedSha256 []byte
130
+
131
+	// We could not expect what the "snapshot" meta has specified.
132
+	//
133
+	// In some old clients, there is only the "sha256",
134
+	// but both "sha256" and "sha512" in the newer ones.
135
+	//
136
+	// And possibly more in the future.
137
+	var expectedHashes data.Hashes
138
+
131 139
 	if c.local.Snapshot != nil {
132 140
 		if prevRootMeta, ok := c.local.Snapshot.Signed.Meta[role]; ok {
133 141
 			size = prevRootMeta.Length
134
-			expectedSha256 = prevRootMeta.Hashes["sha256"]
142
+			expectedHashes = prevRootMeta.Hashes
135 143
 		}
136 144
 	}
137 145
 
... ...
@@ -144,8 +150,9 @@ func (c *Client) downloadRoot() error {
144 144
 	old := &data.Signed{}
145 145
 	version := 0
146 146
 
147
-	if expectedSha256 != nil {
148
-		// can only trust cache if we have an expected sha256 to trust
147
+	// Due to the same reason, we don't really know how many hashes are there.
148
+	if len(expectedHashes) != 0 {
149
+		// can only trust cache if we have an expected sha256(for example) to trust
149 150
 		cachedRoot, err = c.cache.GetMeta(role, size)
150 151
 	}
151 152
 
... ...
@@ -153,11 +160,11 @@ func (c *Client) downloadRoot() error {
153 153
 		logrus.Debug("didn't find a cached root, must download")
154 154
 		download = true
155 155
 	} else {
156
-		hash := sha256.Sum256(cachedRoot)
157
-		if !bytes.Equal(hash[:], expectedSha256) {
156
+		if err := data.CheckHashes(cachedRoot, expectedHashes); err != nil {
158 157
 			logrus.Debug("cached root's hash didn't match expected, must download")
159 158
 			download = true
160 159
 		}
160
+
161 161
 		err := json.Unmarshal(cachedRoot, old)
162 162
 		if err == nil {
163 163
 			root, err := data.RootFromSigned(old)
... ...
@@ -176,7 +183,7 @@ func (c *Client) downloadRoot() error {
176 176
 	var raw []byte
177 177
 	if download {
178 178
 		// use consistent download if we have the checksum.
179
-		raw, s, err = c.downloadSigned(role, size, expectedSha256)
179
+		raw, s, err = c.downloadSigned(role, size, expectedHashes)
180 180
 		if err != nil {
181 181
 			return err
182 182
 		}
... ...
@@ -322,8 +329,8 @@ func (c *Client) downloadSnapshot() error {
322 322
 		return tuf.ErrNotLoaded{Role: data.CanonicalTimestampRole}
323 323
 	}
324 324
 	size := c.local.Timestamp.Signed.Meta[role].Length
325
-	expectedSha256, ok := c.local.Timestamp.Signed.Meta[role].Hashes["sha256"]
326
-	if !ok {
325
+	expectedHashes := c.local.Timestamp.Signed.Meta[role].Hashes
326
+	if len(expectedHashes) == 0 {
327 327
 		return data.ErrMissingMeta{Role: "snapshot"}
328 328
 	}
329 329
 
... ...
@@ -336,11 +343,11 @@ func (c *Client) downloadSnapshot() error {
336 336
 		download = true
337 337
 	} else {
338 338
 		// file may have been tampered with on disk. Always check the hash!
339
-		genHash := sha256.Sum256(raw)
340
-		if !bytes.Equal(genHash[:], expectedSha256) {
339
+		if err := data.CheckHashes(raw, expectedHashes); err != nil {
341 340
 			logrus.Debug("hash of snapshot in cache did not match expected hash, must download")
342 341
 			download = true
343 342
 		}
343
+
344 344
 		err := json.Unmarshal(raw, old)
345 345
 		if err == nil {
346 346
 			snap, err := data.SnapshotFromSigned(old)
... ...
@@ -357,7 +364,7 @@ func (c *Client) downloadSnapshot() error {
357 357
 	}
358 358
 	var s *data.Signed
359 359
 	if download {
360
-		raw, s, err = c.downloadSigned(role, size, expectedSha256)
360
+		raw, s, err = c.downloadSigned(role, size, expectedHashes)
361 361
 		if err != nil {
362 362
 			return err
363 363
 		}
... ...
@@ -439,18 +446,19 @@ func (c *Client) downloadTargets(role string) error {
439 439
 	return nil
440 440
 }
441 441
 
442
-func (c *Client) downloadSigned(role string, size int64, expectedSha256 []byte) ([]byte, *data.Signed, error) {
443
-	rolePath := utils.ConsistentName(role, expectedSha256)
442
+func (c *Client) downloadSigned(role string, size int64, expectedHashes data.Hashes) ([]byte, *data.Signed, error) {
443
+	rolePath := utils.ConsistentName(role, expectedHashes["sha256"])
444 444
 	raw, err := c.remote.GetMeta(rolePath, size)
445 445
 	if err != nil {
446 446
 		return nil, nil, err
447 447
 	}
448
-	if expectedSha256 != nil {
449
-		genHash := sha256.Sum256(raw)
450
-		if !bytes.Equal(genHash[:], expectedSha256) {
448
+
449
+	if expectedHashes != nil {
450
+		if err := data.CheckHashes(raw, expectedHashes); err != nil {
451 451
 			return nil, nil, ErrChecksumMismatch{role: role}
452 452
 		}
453 453
 	}
454
+
454 455
 	s := &data.Signed{}
455 456
 	err = json.Unmarshal(raw, s)
456 457
 	if err != nil {
... ...
@@ -465,8 +473,8 @@ func (c Client) getTargetsFile(role string, snapshotMeta data.Files, consistent
465 465
 	if !ok {
466 466
 		return nil, data.ErrMissingMeta{Role: role}
467 467
 	}
468
-	expectedSha256, ok := snapshotMeta[role].Hashes["sha256"]
469
-	if !ok {
468
+	expectedHashes := snapshotMeta[role].Hashes
469
+	if len(expectedHashes) == 0 {
470 470
 		return nil, data.ErrMissingMeta{Role: role}
471 471
 	}
472 472
 
... ...
@@ -480,10 +488,10 @@ func (c Client) getTargetsFile(role string, snapshotMeta data.Files, consistent
480 480
 		download = true
481 481
 	} else {
482 482
 		// file may have been tampered with on disk. Always check the hash!
483
-		genHash := sha256.Sum256(raw)
484
-		if !bytes.Equal(genHash[:], expectedSha256) {
483
+		if err := data.CheckHashes(raw, expectedHashes); err != nil {
485 484
 			download = true
486 485
 		}
486
+
487 487
 		err := json.Unmarshal(raw, old)
488 488
 		if err == nil {
489 489
 			targ, err := data.TargetsFromSigned(old, role)
... ...
@@ -500,7 +508,7 @@ func (c Client) getTargetsFile(role string, snapshotMeta data.Files, consistent
500 500
 	size := snapshotMeta[role].Length
501 501
 	var s *data.Signed
502 502
 	if download {
503
-		raw, s, err = c.downloadSigned(role, size, expectedSha256)
503
+		raw, s, err = c.downloadSigned(role, size, expectedHashes)
504 504
 		if err != nil {
505 505
 			return nil, err
506 506
 		}
... ...
@@ -129,7 +129,7 @@ func (r SignedRoot) ToSigned() (*Signed, error) {
129 129
 	copy(sigs, r.Signatures)
130 130
 	return &Signed{
131 131
 		Signatures: sigs,
132
-		Signed:     signed,
132
+		Signed:     &signed,
133 133
 	}, nil
134 134
 }
135 135
 
... ...
@@ -146,7 +146,7 @@ func (r SignedRoot) MarshalJSON() ([]byte, error) {
146 146
 // that it is a valid SignedRoot
147 147
 func RootFromSigned(s *Signed) (*SignedRoot, error) {
148 148
 	r := Root{}
149
-	if err := defaultSerializer.Unmarshal(s.Signed, &r); err != nil {
149
+	if err := defaultSerializer.Unmarshal(*s.Signed, &r); err != nil {
150 150
 		return nil, err
151 151
 	}
152 152
 	if err := isValidRootStructure(r); err != nil {
... ...
@@ -2,12 +2,12 @@ package data
2 2
 
3 3
 import (
4 4
 	"bytes"
5
-	"crypto/sha256"
6 5
 	"fmt"
7 6
 	"time"
8 7
 
9 8
 	"github.com/Sirupsen/logrus"
10 9
 	"github.com/docker/go/canonical/json"
10
+	"github.com/docker/notary"
11 11
 )
12 12
 
13 13
 // SignedSnapshot is a fully unpacked snapshot.json
... ...
@@ -39,10 +39,18 @@ func isValidSnapshotStructure(s Snapshot) error {
39 39
 		// Meta is a map of FileMeta, so if the role isn't in the map it returns
40 40
 		// an empty FileMeta, which has an empty map, and you can check on keys
41 41
 		// from an empty map.
42
-		if checksum, ok := s.Meta[role].Hashes["sha256"]; !ok || len(checksum) != sha256.Size {
42
+		//
43
+		// For now sha256 is required and sha512 is not.
44
+		if _, ok := s.Meta[role].Hashes[notary.SHA256]; !ok {
43 45
 			return ErrInvalidMetadata{
44 46
 				role: CanonicalSnapshotRole,
45
-				msg:  fmt.Sprintf("missing or invalid %s sha256 checksum information", role),
47
+				msg:  fmt.Sprintf("missing %s sha256 checksum information", role),
48
+			}
49
+		}
50
+		if err := CheckValidHashStructures(s.Meta[role].Hashes); err != nil {
51
+			return ErrInvalidMetadata{
52
+				role: CanonicalSnapshotRole,
53
+				msg:  fmt.Sprintf("invalid %s checksum information, %v", role, err),
46 54
 			}
47 55
 		}
48 56
 	}
... ...
@@ -63,11 +71,11 @@ func NewSnapshot(root *Signed, targets *Signed) (*SignedSnapshot, error) {
63 63
 		logrus.Debug("Error Marshalling Root")
64 64
 		return nil, err
65 65
 	}
66
-	rootMeta, err := NewFileMeta(bytes.NewReader(rootJSON), "sha256")
66
+	rootMeta, err := NewFileMeta(bytes.NewReader(rootJSON), NotaryDefaultHashes...)
67 67
 	if err != nil {
68 68
 		return nil, err
69 69
 	}
70
-	targetsMeta, err := NewFileMeta(bytes.NewReader(targetsJSON), "sha256")
70
+	targetsMeta, err := NewFileMeta(bytes.NewReader(targetsJSON), NotaryDefaultHashes...)
71 71
 	if err != nil {
72 72
 		return nil, err
73 73
 	}
... ...
@@ -85,10 +93,6 @@ func NewSnapshot(root *Signed, targets *Signed) (*SignedSnapshot, error) {
85 85
 	}, nil
86 86
 }
87 87
 
88
-func (sp *SignedSnapshot) hashForRole(role string) []byte {
89
-	return sp.Signed.Meta[role].Hashes["sha256"]
90
-}
91
-
92 88
 // ToSigned partially serializes a SignedSnapshot for further signing
93 89
 func (sp *SignedSnapshot) ToSigned() (*Signed, error) {
94 90
 	s, err := defaultSerializer.MarshalCanonical(sp.Signed)
... ...
@@ -104,7 +108,7 @@ func (sp *SignedSnapshot) ToSigned() (*Signed, error) {
104 104
 	copy(sigs, sp.Signatures)
105 105
 	return &Signed{
106 106
 		Signatures: sigs,
107
-		Signed:     signed,
107
+		Signed:     &signed,
108 108
 	}, nil
109 109
 }
110 110
 
... ...
@@ -144,7 +148,7 @@ func (sp *SignedSnapshot) MarshalJSON() ([]byte, error) {
144 144
 // SnapshotFromSigned fully unpacks a Signed object into a SignedSnapshot
145 145
 func SnapshotFromSigned(s *Signed) (*SignedSnapshot, error) {
146 146
 	sp := Snapshot{}
147
-	if err := defaultSerializer.Unmarshal(s.Signed, &sp); err != nil {
147
+	if err := defaultSerializer.Unmarshal(*s.Signed, &sp); err != nil {
148 148
 		return nil, err
149 149
 	}
150 150
 	if err := isValidSnapshotStructure(sp); err != nil {
... ...
@@ -162,7 +162,7 @@ func (t *SignedTargets) ToSigned() (*Signed, error) {
162 162
 	copy(sigs, t.Signatures)
163 163
 	return &Signed{
164 164
 		Signatures: sigs,
165
-		Signed:     signed,
165
+		Signed:     &signed,
166 166
 	}, nil
167 167
 }
168 168
 
... ...
@@ -179,7 +179,7 @@ func (t *SignedTargets) MarshalJSON() ([]byte, error) {
179 179
 // a role name (so it can validate the SignedTargets object)
180 180
 func TargetsFromSigned(s *Signed, roleName string) (*SignedTargets, error) {
181 181
 	t := Targets{}
182
-	if err := defaultSerializer.Unmarshal(s.Signed, &t); err != nil {
182
+	if err := defaultSerializer.Unmarshal(*s.Signed, &t); err != nil {
183 183
 		return nil, err
184 184
 	}
185 185
 	if err := isValidTargetsStructure(t, roleName); err != nil {
... ...
@@ -2,11 +2,11 @@ package data
2 2
 
3 3
 import (
4 4
 	"bytes"
5
-	"crypto/sha256"
6 5
 	"fmt"
7 6
 	"time"
8 7
 
9 8
 	"github.com/docker/go/canonical/json"
9
+	"github.com/docker/notary"
10 10
 )
11 11
 
12 12
 // SignedTimestamp is a fully unpacked timestamp.json
... ...
@@ -37,10 +37,17 @@ func isValidTimestampStructure(t Timestamp) error {
37 37
 	// Meta is a map of FileMeta, so if the role isn't in the map it returns
38 38
 	// an empty FileMeta, which has an empty map, and you can check on keys
39 39
 	// from an empty map.
40
-	if cs, ok := t.Meta[CanonicalSnapshotRole].Hashes["sha256"]; !ok || len(cs) != sha256.Size {
40
+	//
41
+	// For now sha256 is required and sha512 is not.
42
+	if _, ok := t.Meta[CanonicalSnapshotRole].Hashes[notary.SHA256]; !ok {
41 43
 		return ErrInvalidMetadata{
42
-			role: CanonicalTimestampRole, msg: "missing or invalid snapshot sha256 checksum information"}
44
+			role: CanonicalTimestampRole, msg: "missing snapshot sha256 checksum information"}
43 45
 	}
46
+	if err := CheckValidHashStructures(t.Meta[CanonicalSnapshotRole].Hashes); err != nil {
47
+		return ErrInvalidMetadata{
48
+			role: CanonicalTimestampRole, msg: fmt.Sprintf("invalid snapshot checksum information, %v", err)}
49
+	}
50
+
44 51
 	return nil
45 52
 }
46 53
 
... ...
@@ -50,7 +57,7 @@ func NewTimestamp(snapshot *Signed) (*SignedTimestamp, error) {
50 50
 	if err != nil {
51 51
 		return nil, err
52 52
 	}
53
-	snapshotMeta, err := NewFileMeta(bytes.NewReader(snapshotJSON), "sha256")
53
+	snapshotMeta, err := NewFileMeta(bytes.NewReader(snapshotJSON), NotaryDefaultHashes...)
54 54
 	if err != nil {
55 55
 		return nil, err
56 56
 	}
... ...
@@ -83,7 +90,7 @@ func (ts *SignedTimestamp) ToSigned() (*Signed, error) {
83 83
 	copy(sigs, ts.Signatures)
84 84
 	return &Signed{
85 85
 		Signatures: sigs,
86
-		Signed:     signed,
86
+		Signed:     &signed,
87 87
 	}, nil
88 88
 }
89 89
 
... ...
@@ -110,7 +117,7 @@ func (ts *SignedTimestamp) MarshalJSON() ([]byte, error) {
110 110
 // SignedTimestamp
111 111
 func TimestampFromSigned(s *Signed) (*SignedTimestamp, error) {
112 112
 	ts := Timestamp{}
113
-	if err := defaultSerializer.Unmarshal(s.Signed, &ts); err != nil {
113
+	if err := defaultSerializer.Unmarshal(*s.Signed, &ts); err != nil {
114 114
 		return nil, err
115 115
 	}
116 116
 	if err := isValidTimestampStructure(ts); err != nil {
... ...
@@ -3,6 +3,7 @@ package data
3 3
 import (
4 4
 	"crypto/sha256"
5 5
 	"crypto/sha512"
6
+	"crypto/subtle"
6 7
 	"fmt"
7 8
 	"hash"
8 9
 	"io"
... ...
@@ -85,8 +86,8 @@ func ValidTUFType(typ, role string) bool {
85 85
 // used to verify signatures before fully unpacking, or to add signatures
86 86
 // before fully packing
87 87
 type Signed struct {
88
-	Signed     json.RawMessage `json:"signed"`
89
-	Signatures []Signature     `json:"signatures"`
88
+	Signed     *json.RawMessage `json:"signed"`
89
+	Signatures []Signature      `json:"signatures"`
90 90
 }
91 91
 
92 92
 // SignedCommon contains the fields common to the Signed component of all
... ...
@@ -119,12 +120,71 @@ type Files map[string]FileMeta
119 119
 // and target file
120 120
 type Hashes map[string][]byte
121 121
 
122
+// NotaryDefaultHashes contains the default supported hash algorithms.
123
+var NotaryDefaultHashes = []string{notary.SHA256, notary.SHA512}
124
+
122 125
 // FileMeta contains the size and hashes for a metadata or target file. Custom
123 126
 // data can be optionally added.
124 127
 type FileMeta struct {
125
-	Length int64           `json:"length"`
126
-	Hashes Hashes          `json:"hashes"`
127
-	Custom json.RawMessage `json:"custom,omitempty"`
128
+	Length int64            `json:"length"`
129
+	Hashes Hashes           `json:"hashes"`
130
+	Custom *json.RawMessage `json:"custom,omitempty"`
131
+}
132
+
133
+// CheckHashes verifies all the checksums specified by the "hashes" of the payload.
134
+func CheckHashes(payload []byte, hashes Hashes) error {
135
+	cnt := 0
136
+
137
+	// k, v indicate the hash algorithm and the corresponding value
138
+	for k, v := range hashes {
139
+		switch k {
140
+		case notary.SHA256:
141
+			checksum := sha256.Sum256(payload)
142
+			if subtle.ConstantTimeCompare(checksum[:], v) == 0 {
143
+				return fmt.Errorf("%s checksum mismatched", k)
144
+			}
145
+			cnt++
146
+		case notary.SHA512:
147
+			checksum := sha512.Sum512(payload)
148
+			if subtle.ConstantTimeCompare(checksum[:], v) == 0 {
149
+				return fmt.Errorf("%s checksum mismatched", k)
150
+			}
151
+			cnt++
152
+		}
153
+	}
154
+
155
+	if cnt == 0 {
156
+		return fmt.Errorf("at least one supported hash needed")
157
+	}
158
+
159
+	return nil
160
+}
161
+
162
+// CheckValidHashStructures returns an error, or nil, depending on whether
163
+// the content of the hashes is valid or not.
164
+func CheckValidHashStructures(hashes Hashes) error {
165
+	cnt := 0
166
+
167
+	for k, v := range hashes {
168
+		switch k {
169
+		case notary.SHA256:
170
+			if len(v) != sha256.Size {
171
+				return fmt.Errorf("invalid %s checksum", notary.SHA256)
172
+			}
173
+			cnt++
174
+		case notary.SHA512:
175
+			if len(v) != sha512.Size {
176
+				return fmt.Errorf("invalid %s checksum", notary.SHA512)
177
+			}
178
+			cnt++
179
+		}
180
+	}
181
+
182
+	if cnt == 0 {
183
+		return fmt.Errorf("at least one supported hash needed")
184
+	}
185
+
186
+	return nil
128 187
 }
129 188
 
130 189
 // NewFileMeta generates a FileMeta object from the reader, using the
... ...
@@ -137,12 +197,12 @@ func NewFileMeta(r io.Reader, hashAlgorithms ...string) (FileMeta, error) {
137 137
 	for _, hashAlgorithm := range hashAlgorithms {
138 138
 		var h hash.Hash
139 139
 		switch hashAlgorithm {
140
-		case "sha256":
140
+		case notary.SHA256:
141 141
 			h = sha256.New()
142
-		case "sha512":
142
+		case notary.SHA512:
143 143
 			h = sha512.New()
144 144
 		default:
145
-			return FileMeta{}, fmt.Errorf("Unknown Hash Algorithm: %s", hashAlgorithm)
145
+			return FileMeta{}, fmt.Errorf("Unknown hash algorithm: %s", hashAlgorithm)
146 146
 		}
147 147
 		hashes[hashAlgorithm] = h
148 148
 		r = io.TeeReader(r, h)
... ...
@@ -3,10 +3,7 @@ package signed
3 3
 import (
4 4
 	"crypto/rand"
5 5
 	"errors"
6
-	"io"
7
-	"io/ioutil"
8 6
 
9
-	"github.com/agl/ed25519"
10 7
 	"github.com/docker/notary/trustmanager"
11 8
 	"github.com/docker/notary/tuf/data"
12 9
 )
... ...
@@ -29,6 +26,12 @@ func NewEd25519() *Ed25519 {
29 29
 	}
30 30
 }
31 31
 
32
+// AddKey allows you to add a private key
33
+func (e *Ed25519) AddKey(role, gun string, k data.PrivateKey) error {
34
+	e.addKey(role, k)
35
+	return nil
36
+}
37
+
32 38
 // addKey allows you to add a private key
33 39
 func (e *Ed25519) addKey(role string, k data.PrivateKey) {
34 40
 	e.keys[k.ID()] = edCryptoKey{
... ...
@@ -64,7 +67,7 @@ func (e *Ed25519) ListAllKeys() map[string]string {
64 64
 }
65 65
 
66 66
 // Create generates a new key and returns the public part
67
-func (e *Ed25519) Create(role, algorithm string) (data.PublicKey, error) {
67
+func (e *Ed25519) Create(role, gun, algorithm string) (data.PublicKey, error) {
68 68
 	if algorithm != data.ED25519Key {
69 69
 		return nil, errors.New("only ED25519 supported by this cryptoservice")
70 70
 	}
... ...
@@ -102,22 +105,3 @@ func (e *Ed25519) GetPrivateKey(keyID string) (data.PrivateKey, string, error) {
102 102
 	}
103 103
 	return nil, "", trustmanager.ErrKeyNotFound{KeyID: keyID}
104 104
 }
105
-
106
-// ImportRootKey adds an Ed25519 key to the store as a root key
107
-func (e *Ed25519) ImportRootKey(r io.Reader) error {
108
-	raw, err := ioutil.ReadAll(r)
109
-	if err != nil {
110
-		return err
111
-	}
112
-	dataSize := ed25519.PublicKeySize + ed25519.PrivateKeySize
113
-	if len(raw) < dataSize || len(raw) > dataSize {
114
-		return errors.New("Wrong length of data for Ed25519 Key Import")
115
-	}
116
-	public := data.NewED25519PublicKey(raw[:ed25519.PublicKeySize])
117
-	private, err := data.NewED25519PrivateKey(*public, raw[ed25519.PublicKeySize:])
118
-	e.keys[private.ID()] = edCryptoKey{
119
-		role:    "root",
120
-		privKey: private,
121
-	}
122
-	return nil
123
-}
... ...
@@ -2,7 +2,6 @@ package signed
2 2
 
3 3
 import (
4 4
 	"github.com/docker/notary/tuf/data"
5
-	"io"
6 5
 )
7 6
 
8 7
 // KeyService provides management of keys locally. It will never
... ...
@@ -11,9 +10,10 @@ import (
11 11
 type KeyService interface {
12 12
 	// Create issues a new key pair and is responsible for loading
13 13
 	// the private key into the appropriate signing service.
14
-	// The role isn't currently used for anything, but it's here to support
15
-	// future features
16
-	Create(role, algorithm string) (data.PublicKey, error)
14
+	Create(role, gun, algorithm string) (data.PublicKey, error)
15
+
16
+	// AddKey adds a private key to the specified role and gun
17
+	AddKey(role, gun string, key data.PrivateKey) error
17 18
 
18 19
 	// GetKey retrieves the public key if present, otherwise it returns nil
19 20
 	GetKey(keyID string) data.PublicKey
... ...
@@ -30,10 +30,6 @@ type KeyService interface {
30 30
 
31 31
 	// ListAllKeys returns a map of all available signing key IDs to role
32 32
 	ListAllKeys() map[string]string
33
-
34
-	// ImportRootKey imports a root key to the highest priority keystore associated with
35
-	// the cryptoservice
36
-	ImportRootKey(source io.Reader) error
37 33
 }
38 34
 
39 35
 // CryptoService is deprecated and all instances of its use should be
... ...
@@ -22,10 +22,13 @@ import (
22 22
 
23 23
 // Sign takes a data.Signed and a key, calculated and adds the signature
24 24
 // to the data.Signed
25
+// N.B. All public keys for a role should be passed so that this function
26
+//      can correctly clean up signatures that are no longer valid.
25 27
 func Sign(service CryptoService, s *data.Signed, keys ...data.PublicKey) error {
26 28
 	logrus.Debugf("sign called with %d keys", len(keys))
27 29
 	signatures := make([]data.Signature, 0, len(s.Signatures)+1)
28 30
 	signingKeyIDs := make(map[string]struct{})
31
+	tufIDs := make(map[string]data.PublicKey)
29 32
 	ids := make([]string, 0, len(keys))
30 33
 
31 34
 	privKeys := make(map[string]data.PrivateKey)
... ...
@@ -34,6 +37,7 @@ func Sign(service CryptoService, s *data.Signed, keys ...data.PublicKey) error {
34 34
 	for _, key := range keys {
35 35
 		canonicalID, err := utils.CanonicalKeyID(key)
36 36
 		ids = append(ids, canonicalID)
37
+		tufIDs[key.ID()] = key
37 38
 		if err != nil {
38 39
 			continue
39 40
 		}
... ...
@@ -51,7 +55,7 @@ func Sign(service CryptoService, s *data.Signed, keys ...data.PublicKey) error {
51 51
 
52 52
 	// Do signing and generate list of signatures
53 53
 	for keyID, pk := range privKeys {
54
-		sig, err := pk.Sign(rand.Reader, s.Signed, nil)
54
+		sig, err := pk.Sign(rand.Reader, *s.Signed, nil)
55 55
 		if err != nil {
56 56
 			logrus.Debugf("Failed to sign with key: %s. Reason: %v", keyID, err)
57 57
 			continue
... ...
@@ -78,6 +82,20 @@ func Sign(service CryptoService, s *data.Signed, keys ...data.PublicKey) error {
78 78
 			// key is in the set of key IDs for which a signature has been created
79 79
 			continue
80 80
 		}
81
+		var (
82
+			k  data.PublicKey
83
+			ok bool
84
+		)
85
+		if k, ok = tufIDs[sig.KeyID]; !ok {
86
+			// key is no longer a valid signing key
87
+			continue
88
+		}
89
+		if err := VerifySignature(*s.Signed, sig, k); err != nil {
90
+			// signature is no longer valid
91
+			continue
92
+		}
93
+		// keep any signatures that still represent valid keys and are
94
+		// themselves valid
81 95
 		signatures = append(signatures, sig)
82 96
 	}
83 97
 	s.Signatures = signatures
... ...
@@ -2,6 +2,7 @@ package signed
2 2
 
3 3
 import (
4 4
 	"errors"
5
+	"fmt"
5 6
 	"strings"
6 7
 	"time"
7 8
 
... ...
@@ -28,7 +29,7 @@ func VerifyRoot(s *data.Signed, minVersion int, keys map[string]data.PublicKey)
28 28
 	}
29 29
 
30 30
 	var decoded map[string]interface{}
31
-	if err := json.Unmarshal(s.Signed, &decoded); err != nil {
31
+	if err := json.Unmarshal(*s.Signed, &decoded); err != nil {
32 32
 		return err
33 33
 	}
34 34
 	msg, err := json.MarshalCanonical(decoded)
... ...
@@ -72,7 +73,7 @@ func Verify(s *data.Signed, role data.BaseRole, minVersion int) error {
72 72
 
73 73
 func verifyMeta(s *data.Signed, role string, minVersion int) error {
74 74
 	sm := &data.SignedCommon{}
75
-	if err := json.Unmarshal(s.Signed, sm); err != nil {
75
+	if err := json.Unmarshal(*s.Signed, sm); err != nil {
76 76
 		return err
77 77
 	}
78 78
 	if !data.ValidTUFType(sm.Type, role) {
... ...
@@ -108,7 +109,7 @@ func VerifySignatures(s *data.Signed, roleData data.BaseRole) error {
108 108
 	// remarshal the signed part so we can verify the signature, since the signature has
109 109
 	// to be of a canonically marshalled signed object
110 110
 	var decoded map[string]interface{}
111
-	if err := json.Unmarshal(s.Signed, &decoded); err != nil {
111
+	if err := json.Unmarshal(*s.Signed, &decoded); err != nil {
112 112
 		return err
113 113
 	}
114 114
 	msg, err := json.MarshalCanonical(decoded)
... ...
@@ -124,16 +125,8 @@ func VerifySignatures(s *data.Signed, roleData data.BaseRole) error {
124 124
 			logrus.Debugf("continuing b/c keyid lookup was nil: %s\n", sig.KeyID)
125 125
 			continue
126 126
 		}
127
-		// method lookup is consistent due to Unmarshal JSON doing lower case for us.
128
-		method := sig.Method
129
-		verifier, ok := Verifiers[method]
130
-		if !ok {
131
-			logrus.Debugf("continuing b/c signing method is not supported: %s\n", sig.Method)
132
-			continue
133
-		}
134
-
135
-		if err := verifier.Verify(key, sig.Signature, msg); err != nil {
136
-			logrus.Debugf("continuing b/c signature was invalid\n")
127
+		if err := VerifySignature(msg, sig, key); err != nil {
128
+			logrus.Debugf("continuing b/c %s", err.Error())
137 129
 			continue
138 130
 		}
139 131
 		valid[sig.KeyID] = struct{}{}
... ...
@@ -145,3 +138,18 @@ func VerifySignatures(s *data.Signed, roleData data.BaseRole) error {
145 145
 
146 146
 	return nil
147 147
 }
148
+
149
+// VerifySignature checks a single signature and public key against a payload
150
+func VerifySignature(msg []byte, sig data.Signature, pk data.PublicKey) error {
151
+	// method lookup is consistent due to Unmarshal JSON doing lower case for us.
152
+	method := sig.Method
153
+	verifier, ok := Verifiers[method]
154
+	if !ok {
155
+		return fmt.Errorf("signing method is not supported: %s\n", sig.Method)
156
+	}
157
+
158
+	if err := verifier.Verify(pk, sig.Signature, msg); err != nil {
159
+		return fmt.Errorf("signature was invalid\n")
160
+	}
161
+	return nil
162
+}
... ...
@@ -575,8 +575,9 @@ func (tr Repo) TargetDelegations(role, path string) []*data.Role {
575 575
 // exist or if there are no signing keys.
576 576
 func (tr *Repo) VerifyCanSign(roleName string) error {
577 577
 	var (
578
-		role data.BaseRole
579
-		err  error
578
+		role            data.BaseRole
579
+		err             error
580
+		canonicalKeyIDs []string
580 581
 	)
581 582
 	// we only need the BaseRole part of a delegation because we're just
582 583
 	// checking KeyIDs
... ...
@@ -597,6 +598,7 @@ func (tr *Repo) VerifyCanSign(roleName string) error {
597 597
 		check := []string{keyID}
598 598
 		if canonicalID, err := utils.CanonicalKeyID(k); err == nil {
599 599
 			check = append(check, canonicalID)
600
+			canonicalKeyIDs = append(canonicalKeyIDs, canonicalID)
600 601
 		}
601 602
 		for _, id := range check {
602 603
 			p, _, err := tr.cryptoService.GetPrivateKey(id)
... ...
@@ -605,7 +607,7 @@ func (tr *Repo) VerifyCanSign(roleName string) error {
605 605
 			}
606 606
 		}
607 607
 	}
608
-	return signed.ErrNoKeys{KeyIDs: role.ListKeyIDs()}
608
+	return signed.ErrNoKeys{KeyIDs: canonicalKeyIDs}
609 609
 }
610 610
 
611 611
 // used for walking the targets/delegations tree, potentially modifying the underlying SignedTargets for the repo
... ...
@@ -760,7 +762,7 @@ func (tr *Repo) UpdateSnapshot(role string, s *data.Signed) error {
760 760
 	if err != nil {
761 761
 		return err
762 762
 	}
763
-	meta, err := data.NewFileMeta(bytes.NewReader(jsonData), "sha256")
763
+	meta, err := data.NewFileMeta(bytes.NewReader(jsonData), data.NotaryDefaultHashes...)
764 764
 	if err != nil {
765 765
 		return err
766 766
 	}
... ...
@@ -775,7 +777,7 @@ func (tr *Repo) UpdateTimestamp(s *data.Signed) error {
775 775
 	if err != nil {
776 776
 		return err
777 777
 	}
778
-	meta, err := data.NewFileMeta(bytes.NewReader(jsonData), "sha256")
778
+	meta, err := data.NewFileMeta(bytes.NewReader(jsonData), data.NotaryDefaultHashes...)
779 779
 	if err != nil {
780 780
 		return err
781 781
 	}
... ...
@@ -917,12 +919,7 @@ func (tr *Repo) SignTimestamp(expires time.Time) (*data.Signed, error) {
917 917
 }
918 918
 
919 919
 func (tr Repo) sign(signedData *data.Signed, role data.BaseRole) (*data.Signed, error) {
920
-	ks := role.ListKeys()
921
-	if len(ks) < 1 {
922
-		return nil, signed.ErrNoKeys{}
923
-	}
924
-	err := signed.Sign(tr.cryptoService, signedData, ks...)
925
-	if err != nil {
920
+	if err := signed.Sign(tr.cryptoService, signedData, role.ListKeys()...); err != nil {
926 921
 		return nil, err
927 922
 	}
928 923
 	return signedData, nil
... ...
@@ -1,7 +1,6 @@
1 1
 package utils
2 2
 
3 3
 import (
4
-	"bytes"
5 4
 	"crypto/sha256"
6 5
 	"crypto/sha512"
7 6
 	"crypto/tls"
... ...
@@ -34,24 +33,6 @@ func Upload(url string, body io.Reader) (*http.Response, error) {
34 34
 	return client.Post(url, "application/json", body)
35 35
 }
36 36
 
37
-// ValidateTarget ensures that the data read from reader matches
38
-// the known metadata
39
-func ValidateTarget(r io.Reader, m *data.FileMeta) error {
40
-	h := sha256.New()
41
-	length, err := io.Copy(h, r)
42
-	if err != nil {
43
-		return err
44
-	}
45
-	if length != m.Length {
46
-		return fmt.Errorf("Size of downloaded target did not match targets entry.\nExpected: %d\nReceived: %d\n", m.Length, length)
47
-	}
48
-	hashDigest := h.Sum(nil)
49
-	if bytes.Compare(m.Hashes["sha256"], hashDigest[:]) != 0 {
50
-		return fmt.Errorf("Hash of downloaded target did not match targets entry.\nExpected: %x\nReceived: %x\n", m.Hashes["sha256"], hashDigest)
51
-	}
52
-	return nil
53
-}
54
-
55 37
 // StrSliceContains checks if the given string appears in the slice
56 38
 func StrSliceContains(ss []string, s string) bool {
57 39
 	for _, v := range ss {