Browse code

Bump notary version to v0.3.0-RC1

Signed-off-by: cyli <cyli@twistedmatrix.com>

cyli authored on 2016/04/26 02:21:28
Showing 48 changed files
... ...
@@ -186,7 +186,7 @@ RUN set -x \
186 186
 	&& rm -rf "$GOPATH"
187 187
 
188 188
 # Install notary and notary-server
189
-ENV NOTARY_VERSION docker-v1.11-3
189
+ENV NOTARY_VERSION v0.3.0-RC1
190 190
 RUN set -x \
191 191
 	&& export GO15VENDOREXPERIMENT=1 \
192 192
 	&& export GOPATH="$(mktemp -d)" \
... ...
@@ -117,7 +117,7 @@ RUN set -x \
117 117
 	&& rm -rf "$GOPATH"
118 118
 
119 119
 # Install notary and notary-server
120
-ENV NOTARY_VERSION docker-v1.11-3
120
+ENV NOTARY_VERSION v0.3.0-RC1
121 121
 RUN set -x \
122 122
 	&& export GO15VENDOREXPERIMENT=1 \
123 123
 	&& export GOPATH="$(mktemp -d)" \
... ...
@@ -128,7 +128,7 @@ RUN set -x \
128 128
 	&& rm -rf "$GOPATH"
129 129
 
130 130
 # Install notary and notary-server
131
-ENV NOTARY_VERSION docker-v1.11-3
131
+ENV NOTARY_VERSION v0.3.0-RC1
132 132
 RUN set -x \
133 133
 	&& export GO15VENDOREXPERIMENT=1 \
134 134
 	&& export GOPATH="$(mktemp -d)" \
... ...
@@ -141,7 +141,7 @@ RUN set -x \
141 141
 	&& rm -rf "$GOPATH"
142 142
 
143 143
 # Install notary and notary-server
144
-ENV NOTARY_VERSION docker-v1.11-3
144
+ENV NOTARY_VERSION v0.3.0-RC1
145 145
 RUN set -x \
146 146
 	&& export GOPATH="$(mktemp -d)" \
147 147
 	&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
... ...
@@ -130,7 +130,7 @@ RUN set -x \
130 130
 	&& rm -rf "$GOPATH"
131 131
 
132 132
 # Install notary and notary-server
133
-ENV NOTARY_VERSION docker-v1.11-3
133
+ENV NOTARY_VERSION v0.3.0-RC1
134 134
 RUN set -x \
135 135
 	&& export GO15VENDOREXPERIMENT=1 \
136 136
 	&& export GOPATH="$(mktemp -d)" \
... ...
@@ -53,7 +53,7 @@ clone git github.com/docker/distribution 9ec0d742d69f77caa4dd5f49ceb70c3067d39f3
53 53
 clone git github.com/vbatts/tar-split v0.9.11
54 54
 
55 55
 # get desired notary commit, might also need to be updated in Dockerfile
56
-clone git github.com/docker/notary docker-v1.11-3
56
+clone git github.com/docker/notary v0.3.0-RC1
57 57
 
58 58
 clone git google.golang.org/grpc a22b6611561e9f0a3e0919690dd2caf48f14c517 https://github.com/grpc/grpc-go.git
59 59
 clone git github.com/miekg/pkcs11 df8ae6ca730422dba20c768ff38ef7d79077a59f
... ...
@@ -1,15 +1,35 @@
1
-FROM golang:1.6.0
1
+FROM golang:1.6.1
2 2
 
3 3
 RUN apt-get update && apt-get install -y \
4
+	curl \
5
+	clang \
4 6
 	libltdl-dev \
5 7
 	libsqlite3-dev \
8
+	patch \
9
+	tar \
10
+	xz-utils \
6 11
 	--no-install-recommends \
7 12
 	&& rm -rf /var/lib/apt/lists/*
8 13
 
9
-RUN go get golang.org/x/tools/cmd/vet \
10
-	&& go get golang.org/x/tools/cmd/cover \
11
-	&& go get github.com/tools/godep
14
+RUN go get golang.org/x/tools/cmd/cover
12 15
 
13
-COPY . /go/src/github.com/docker/notary
16
+# Configure the container for OSX cross compilation
17
+ENV OSX_SDK MacOSX10.11.sdk
18
+ENV OSX_CROSS_COMMIT 8aa9b71a394905e6c5f4b59e2b97b87a004658a4
19
+RUN set -x \
20
+	&& export OSXCROSS_PATH="/osxcross" \
21
+	&& git clone https://github.com/tpoechtrager/osxcross.git $OSXCROSS_PATH \
22
+	&& ( cd $OSXCROSS_PATH && git checkout -q $OSX_CROSS_COMMIT) \
23
+	&& curl -sSL https://s3.dockerproject.org/darwin/v2/${OSX_SDK}.tar.xz -o "${OSXCROSS_PATH}/tarballs/${OSX_SDK}.tar.xz" \
24
+	&& UNATTENDED=yes OSX_VERSION_MIN=10.6 ${OSXCROSS_PATH}/build.sh > /dev/null
25
+ENV PATH /osxcross/target/bin:$PATH
14 26
 
15
-WORKDIR /go/src/github.com/docker/notary
27
+ENV NOTARYDIR /go/src/github.com/docker/notary
28
+
29
+COPY . ${NOTARYDIR}
30
+
31
+ENV GOPATH ${NOTARYDIR}/Godeps/_workspace:$GOPATH
32
+
33
+WORKDIR ${NOTARYDIR}
34
+
35
+# Note this cannot use alpine because of the MacOSX Cross SDK: the cctools there uses sys/cdefs.h and that cannot be used in alpine: http://wiki.musl-libc.org/wiki/FAQ#Q:_I.27m_trying_to_compile_something_against_musl_and_I_get_error_messages_about_sys.2Fcdefs.h
... ...
@@ -13,14 +13,18 @@ endif
13 13
 CTIMEVAR=-X $(NOTARY_PKG)/version.GitCommit=$(GITCOMMIT) -X $(NOTARY_PKG)/version.NotaryVersion=$(NOTARY_VERSION)
14 14
 GO_LDFLAGS=-ldflags "-w $(CTIMEVAR)"
15 15
 GO_LDFLAGS_STATIC=-ldflags "-w $(CTIMEVAR) -extldflags -static"
16
-GOOSES = darwin freebsd linux
17
-GOARCHS = amd64
16
+GOOSES = darwin linux
18 17
 NOTARY_BUILDTAGS ?= pkcs11
19
-GO_EXC = go
20 18
 NOTARYDIR := /go/src/github.com/docker/notary
21 19
 
20
+GO_VERSION := $(shell go version | grep "1\.[6-9]\(\.[0-9]+\)*")
21
+# check to make sure we have the right version
22
+ifeq ($(strip $(GO_VERSION)),)
23
+$(error Bad Go version - please install Go >= 1.6)
24
+endif
25
+
22 26
 # 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"}}' ./... | grep -v /vendor/ | xargs go list -e -f '{{if not .Standard}}{{.ImportPath}}{{end}}' | grep -q pkcs11)
27
+GO_LIST_PKCS11 := $(shell go list -tags "${NOTARY_BUILDTAGS}" -e -f '{{join .Deps "\n"}}' ./... | grep -v /vendor/ | xargs go list -e -f '{{if not .Standard}}{{.ImportPath}}{{end}}' | grep -q pkcs11)
24 28
 ifeq ($(GO_LIST_PKCS11),)
25 29
 $(info pkcs11 import was not found anywhere without a build tag, yay)
26 30
 else
... ...
@@ -34,7 +38,7 @@ _space := $(empty) $(empty)
34 34
 COVERDIR=.cover
35 35
 COVERPROFILE?=$(COVERDIR)/cover.out
36 36
 COVERMODE=count
37
-PKGS ?= $(shell go list ./... | grep -v /vendor/ | tr '\n' ' ')
37
+PKGS ?= $(shell go list -tags "${NOTARY_BUILDTAGS}" ./... | grep -v /vendor/ | tr '\n' ' ')
38 38
 
39 39
 GO_VERSION = $(shell go version | awk '{print $$3}')
40 40
 
... ...
@@ -53,15 +57,15 @@ version/version.go:
53 53
 
54 54
 ${PREFIX}/bin/notary-server: NOTARY_VERSION $(shell find . -type f -name '*.go')
55 55
 	@echo "+ $@"
56
-	@godep go build -tags ${NOTARY_BUILDTAGS} -o $@ ${GO_LDFLAGS} ./cmd/notary-server
56
+	@go build -tags ${NOTARY_BUILDTAGS} -o $@ ${GO_LDFLAGS} ./cmd/notary-server
57 57
 
58 58
 ${PREFIX}/bin/notary: NOTARY_VERSION $(shell find . -type f -name '*.go')
59 59
 	@echo "+ $@"
60
-	@godep go build -tags ${NOTARY_BUILDTAGS} -o $@ ${GO_LDFLAGS} ./cmd/notary
60
+	@go build -tags ${NOTARY_BUILDTAGS} -o $@ ${GO_LDFLAGS} ./cmd/notary
61 61
 
62 62
 ${PREFIX}/bin/notary-signer: NOTARY_VERSION $(shell find . -type f -name '*.go')
63 63
 	@echo "+ $@"
64
-	@godep go build -tags ${NOTARY_BUILDTAGS} -o $@ ${GO_LDFLAGS} ./cmd/notary-signer
64
+	@go build -tags ${NOTARY_BUILDTAGS} -o $@ ${GO_LDFLAGS} ./cmd/notary-signer
65 65
 
66 66
 ifeq ($(shell uname -s),Darwin)
67 67
 ${PREFIX}/bin/static/notary-server:
... ...
@@ -72,11 +76,11 @@ ${PREFIX}/bin/static/notary-signer:
72 72
 else
73 73
 ${PREFIX}/bin/static/notary-server: NOTARY_VERSION $(shell find . -type f -name '*.go')
74 74
 	@echo "+ $@"
75
-	@godep go build -tags ${NOTARY_BUILDTAGS} -o $@ ${GO_LDFLAGS_STATIC} ./cmd/notary-server
75
+	@go build -tags ${NOTARY_BUILDTAGS} -o $@ ${GO_LDFLAGS_STATIC} ./cmd/notary-server
76 76
 
77 77
 ${PREFIX}/bin/static/notary-signer: NOTARY_VERSION $(shell find . -type f -name '*.go')
78 78
 	@echo "+ $@"
79
-	@godep go build -tags ${NOTARY_BUILDTAGS} -o $@ ${GO_LDFLAGS_STATIC} ./cmd/notary-signer
79
+	@go build -tags ${NOTARY_BUILDTAGS} -o $@ ${GO_LDFLAGS_STATIC} ./cmd/notary-signer
80 80
 endif
81 81
 
82 82
 vet:
... ...
@@ -94,7 +98,7 @@ fmt:
94 94
 
95 95
 lint:
96 96
 	@echo "+ $@"
97
-	@test -z "$$(golint ./... | grep -v .pb. | grep -v vendor/ | tee /dev/stderr)"
97
+	@test -z "$(shell find . -type f -name "*.go" -not -path "./vendor/*" -not -name "*.pb.*" -exec golint {} \; | tee /dev/stderr)"
98 98
 
99 99
 # Requires that the following:
100 100
 # go get -u github.com/client9/misspell/cmd/misspell
... ...
@@ -126,6 +130,9 @@ test-full: vet lint
126 126
 	@echo
127 127
 	go test -tags "${NOTARY_BUILDTAGS}" $(TESTOPTS) -v $(PKGS)
128 128
 
129
+integration:
130
+	buildscripts/integrationtest.sh development.yml
131
+
129 132
 protos:
130 133
 	@protoc --go_out=plugins=grpc:. proto/*.proto
131 134
 
... ...
@@ -136,7 +143,7 @@ protos:
136 136
 # be run first
137 137
 
138 138
 define gocover
139
-$(GO_EXC) test $(OPTS) $(TESTOPTS) -covermode="$(COVERMODE)" -coverprofile="$(COVERDIR)/$(subst /,-,$(1)).$(subst $(_space),.,$(NOTARY_BUILDTAGS)).coverage.txt" "$(1)" || exit 1;
139
+go test $(OPTS) $(TESTOPTS) -covermode="$(COVERMODE)" -coverprofile="$(COVERDIR)/$(subst /,-,$(1)).$(subst $(_space),.,$(NOTARY_BUILDTAGS)).coverage.txt" "$(1)" || exit 1;
140 140
 endef
141 141
 
142 142
 gen-cover:
... ...
@@ -146,15 +153,13 @@ gen-cover:
146 146
 
147 147
 # Generates the cover binaries and runs them all in serial, so this can be used
148 148
 # run all tests with a yubikey without any problems
149
-cover: GO_EXC := go
150
-       OPTS = -tags "${NOTARY_BUILDTAGS}" -coverpkg "$(shell ./coverpkg.sh $(1) $(NOTARY_PKG))"
149
+cover: OPTS = -tags "${NOTARY_BUILDTAGS}" -coverpkg "$(shell ./coverpkg.sh $(1) $(NOTARY_PKG))"
151 150
 cover: gen-cover covmerge
152 151
 	@go tool cover -html="$(COVERPROFILE)"
153 152
 
154 153
 # Generates the cover binaries and runs them all in serial, so this can be used
155 154
 # run all tests with a yubikey without any problems
156 155
 ci: OPTS = -tags "${NOTARY_BUILDTAGS}" -race -coverpkg "$(shell ./coverpkg.sh $(1) $(NOTARY_PKG))"
157
-    GO_EXC := godep go
158 156
 # Codecov knows how to merge multiple coverage files, so covmerge is not needed
159 157
 ci: gen-cover
160 158
 
... ...
@@ -168,21 +173,15 @@ covmerge:
168 168
 clean-protos:
169 169
 	@rm proto/*.pb.go
170 170
 
171
+client: ${PREFIX}/bin/notary
172
+	@echo "+ $@"
173
+
171 174
 binaries: ${PREFIX}/bin/notary-server ${PREFIX}/bin/notary ${PREFIX}/bin/notary-signer
172 175
 	@echo "+ $@"
173 176
 
174 177
 static: ${PREFIX}/bin/static/notary-server ${PREFIX}/bin/static/notary-signer
175 178
 	@echo "+ $@"
176 179
 
177
-define template
178
-mkdir -p ${PREFIX}/cross/$(1)/$(2);
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
-endef
181
-
182
-cross:
183
-	$(foreach GOARCH,$(GOARCHS),$(foreach GOOS,$(GOOSES),$(call template,$(GOOS),$(GOARCH))))
184
-
185
-
186 180
 notary-dockerfile:
187 181
 	@docker build --rm --force-rm -t notary .
188 182
 
... ...
@@ -197,6 +196,10 @@ docker-images: notary-dockerfile server-dockerfile signer-dockerfile
197 197
 shell: notary-dockerfile
198 198
 	docker run --rm -it -v $(CURDIR)/cross:$(NOTARYDIR)/cross -v $(CURDIR)/bin:$(NOTARYDIR)/bin notary bash
199 199
 
200
+cross: notary-dockerfile
201
+	@rm -rf $(CURDIR)/cross
202
+	docker run --rm -v $(CURDIR)/cross:$(NOTARYDIR)/cross -e NOTARY_BUILDTAGS=$(NOTARY_BUILDTAGS) notary buildscripts/cross.sh $(GOOSES)
203
+
200 204
 
201 205
 clean:
202 206
 	@echo "+ $@"
... ...
@@ -1,8 +1,9 @@
1
-# Notary 
1
+# Notary
2 2
 [![Circle CI](https://circleci.com/gh/docker/notary/tree/master.svg?style=shield)](https://circleci.com/gh/docker/notary/tree/master) [![CodeCov](https://codecov.io/github/docker/notary/coverage.svg?branch=master)](https://codecov.io/github/docker/notary)
3 3
 
4 4
 The Notary project comprises a [server](cmd/notary-server) and a [client](cmd/notary) for running and interacting
5
-with trusted collections.
5
+with trusted collections.  Please see the [service architecture](docs/service_architecture.md) documentation
6
+for more information.
6 7
 
7 8
 
8 9
 Notary aims to make the internet more secure by making it easy for people to
... ...
@@ -21,7 +22,7 @@ received content.
21 21
 
22 22
 ## Goals
23 23
 
24
-Notary is based on [The Update Framework](http://theupdateframework.com/), a secure general design for the problem of software distribution and updates. By using TUF, notary achieves a number of key advantages:
24
+Notary is based on [The Update Framework](https://www.theupdateframework.com/), a secure general design for the problem of software distribution and updates. By using TUF, notary achieves a number of key advantages:
25 25
 
26 26
 * **Survivable Key Compromise**: Content publishers must manage keys in order to sign their content. Signing keys may be compromised or lost so systems must be designed in order to be flexible and recoverable in the case of key compromise. TUF's notion of key roles is utilized to separate responsibilities across a hierarchy of keys such that loss of any particular key (except the root role) by itself is not fatal to the security of the system.
27 27
 * **Freshness Guarantees**: Replay attacks are a common problem in designing secure systems, where previously valid payloads are replayed to trick another system. The same problem exists in the software update systems, where old signed can be presented as the most recent. notary makes use of timestamping on publishing so that consumers can know that they are receiving the most up to date content. This is particularly important when dealing with software update where old vulnerable versions could be used to attack users.
... ...
@@ -30,165 +31,60 @@ Notary is based on [The Update Framework](http://theupdateframework.com/), a sec
30 30
 * **Use of Existing Distribution**: Notary's trust guarantees are not tied at all to particular distribution channels from which content is delivered. Therefore, trust can be added to any existing content delivery mechanism.
31 31
 * **Untrusted Mirrors and Transport**: All of the notary metadata can be mirrored and distributed via arbitrary channels.
32 32
 
33
-# Notary CLI
33
+## Security
34 34
 
35
-Notary is a tool for publishing and managing trusted collections of content. Publishers can digitally sign collections and consumers can verify integrity and origin of content. This ability is built on a straightforward key management and signing interface to create signed collections and configure trusted publishers.
35
+Please see our [service architecture docs](docs/service_architecture.md#threat-model) for more information about our threat model, which details the varying survivability and severities for key compromise as well as mitigations.
36 36
 
37
-## Using Notary
38
-Lets try using notary.
37
+Our last security audit was on July 31, 2015 by NCC ([results](docs/resources/ncc_docker_notary_audit_2015_07_31.pdf)).
39 38
 
40
-Prerequisites:
41
-
42
-- Requirements from the [Compiling Notary Server](#compiling-notary-server) section (such as go 1.5.1)
43
-- [docker and docker-compose](http://docs.docker.com/compose/install/)
44
-- [Notary server configuration](#configuring-notary-server)
45
-
46
-As setup, let's build notary and then start up a local notary-server (don't forget to add `127.0.0.1 notary-server` to your `/etc/hosts`, or if using docker-machine, add `$(docker-machine ip) notary-server`).
47
-
48
-```sh
49
-make binaries
50
-docker-compose build
51
-docker-compose up -d
52
-```
53
-
54
-Note: In order to have notary use the local notary server and development root CA we can load the local development configuration by appending `-c cmd/notary/config.json` to every command. If you would rather not have to use `-c` on every command, copy `cmd/notary/config.json and cmd/notary/root-ca.crt` to `~/.notary`.
55
-
56
-
57
-First, let's initiate a notary collection called `example.com/scripts`
39
+Any security vulnerabilities can be reported to security@docker.com.
58 40
 
59
-```sh
60
-notary init example.com/scripts
61
-```
41
+# Getting started with the Notary CLI
62 42
 
63
-Now, look at the keys you created as a result of initialization
64
-```sh
65
-notary key list
66
-```
43
+Please get the Notary Client CLI binary from [the official releases page](https://github.com/docker/notary/releases) or you can [build one yourself](#building-notary).
44
+The version of Notary server and signer should be greater than or equal to Notary CLI's version to ensure feature compatibility (ex: CLI version 0.2, server/signer version >= 0.2), and all official releases are associated with GitHub tags.
67 45
 
68
-Cool, now add a local file `install.sh` and call it `v1`
69
-```sh
70
-notary add example.com/scripts v1 install.sh
71
-```
46
+To use the Notary CLI with Docker hub images, please have a look at our
47
+[getting started docs](docs/getting_started.md).
72 48
 
73
-Wouldn't it be nice if others could know that you've signed this content? Use `publish` to publish your collection to your default notary-server
74
-```sh
75
-notary publish example.com/scripts
76
-```
49
+For more advanced usage, please see the
50
+[advanced usage docs](docs/advanced_usage.md).
77 51
 
78
-Now, others can pull your trusted collection
79
-```sh
80
-notary list example.com/scripts
81
-```
52
+To use the CLI against a local Notary server rather than against Docker Hub:
82 53
 
83
-More importantly, they can verify the content of your script by using `notary verify`:
84
-```sh
85
-curl example.com/install.sh | notary verify example.com/scripts v1 | sh
86
-```
54
+1. Please ensure that you have [docker and docker-compose](http://docs.docker.com/compose/install/) installed.
55
+1. `git clone https://github.com/docker/notary.git` and from the cloned repository path,
56
+    start up a local Notary server and signer and copy the config file and testing certs to your
57
+    local notary config directory:
87 58
 
88
-# Notary Server
59
+    ```sh
60
+    $ docker-compose build
61
+    $ docker-compose up -d
62
+    $ mkdir -p ~/.notary && cp cmd/notary/config.json cmd/notary/root-ca.crt ~/.notary
63
+    ```
89 64
 
90
-Notary Server manages TUF data over an HTTP API compatible with the
91
-[notary client](cmd/notary).
65
+1. Add `127.0.0.1 notary-server` to your `/etc/hosts`, or if using docker-machine,
66
+    add `$(docker-machine ip) notary-server`).
92 67
 
93
-It may be configured to use either JWT or HTTP Basic Auth for authentication.
94
-Currently it only supports MySQL for storage of the TUF data, we intend to
95
-expand this to other storage options.
68
+You can run through the examples in the
69
+[getting started docs](docs/getting_started.md) and
70
+[advanced usage docs](docs/advanced_usage.md), but
71
+without the `-s` (server URL) argument to the `notary` command since the server
72
+URL is specified already in the configuration, file you copied.
96 73
 
97
-## Setup for Development
74
+You can also leave off the `-d ~/.docker/trust` argument if you do not care
75
+to use `notary` with Docker images.
98 76
 
99
-The notary repository comes with Dockerfiles and a docker-compose file
100
-to facilitate development. Simply run the following commands to start
101
-a notary server with a temporary MySQL database in containers:
102 77
 
103
-```
104
-$ docker-compose build
105
-$ docker-compose up
106
-```
107
-
108
-If you are on Mac OSX with boot2docker or kitematic, you'll need to
109
-update your hosts file such that the name `notary` is associated with
110
-the IP address of your VM (for boot2docker, this can be determined
111
-by running `boot2docker ip`, with kitematic, `echo $DOCKER_HOST` should
112
-show the IP of the VM). If you are using the default Linux setup,
113
-you need to add `127.0.0.1 notary` to your hosts file.
114
-
115
-## Successfully connecting over TLS
116
-
117
-By default notary-server runs with TLS with certificates signed by a local
118
-CA. In order to be able to successfully connect to it using
119
-either `curl` or `openssl`, you will have to use the root CA file in `fixtures/root-ca.crt`.
120
-
121
-OpenSSL example:
122
-
123
-`openssl s_client -connect notary-server:4443 -CAfile fixtures/root-ca.crt`
124
-
125
-## Compiling Notary Server
78
+## Building Notary
126 79
 
127 80
 Prerequisites:
128 81
 
129
-- Go = 1.5.1
82
+- Go >= 1.6.1
130 83
 - [godep](https://github.com/tools/godep) installed
131 84
 - libtool development headers installed
85
+    - Ubuntu: `apt-get install libtool-dev`
86
+    - CentOS/RedHat: `yum install libtool-ltdl-devel`
87
+    - Mac OS ([Homebrew](http://brew.sh/)): `brew install libtool`
132 88
 
133
-Install dependencies by running `godep restore`.
134
-
135
-From the root of this git repository, run `make binaries`. This will
136
-compile the `notary`, `notary-server`, and `notary-signer` applications and
137
-place them in a `bin` directory at the root of the git repository (the `bin`
138
-directory is ignored by the .gitignore file).
139
-
140
-`notary-signer` depends upon `pkcs11`, which requires that libtool headers be installed (`libtool-dev` on Ubuntu, `libtool-ltdl-devel` on CentOS/RedHat). If you are using Mac OS, you can `brew install libtool`, and run `make binaries` with the following environment variables (assuming a standard installation of Homebrew):
141
-
142
-```sh
143
-export CPATH=/usr/local/include:${CPATH}
144
-export LIBRARY_PATH=/usr/local/lib:${LIBRARY_PATH}
145
-```
146
-
147
-## Running Notary Server
148
-
149
-The `notary-server` application has the following usage:
150
-
151
-```
152
-$ bin/notary-server --help
153
-usage: bin/notary-serve
154
-  -config="": Path to configuration file
155
-  -debug=false: Enable the debugging server on localhost:8080
156
-```
157
-
158
-## Configuring Notary Server
159
-
160
-The configuration file must be a json file with the following format:
161
-
162
-```json
163
-{
164
-    "server": {
165
-        "addr": ":4443",
166
-        "tls_cert_file": "./fixtures/notary-server.crt",
167
-        "tls_key_file": "./fixtures/notary-server.key"
168
-    },
169
-    "logging": {
170
-        "level": 5
171
-    }
172
-}
173
-```
174
-
175
-The pem and key provided in fixtures are purely for local development and
176
-testing. For production, you must create your own keypair and certificate,
177
-either via the CA of your choice, or a self signed certificate.
178
-
179
-If using the pem and key provided in fixtures, either:
180
-- Add `fixtures/root-ca.crt` to your trusted root certificates
181
-- Use the default configuration for notary client that loads the CA root for you by using the flag `-c ./cmd/notary/config.json`
182
-- Disable TLS verification by adding the following option notary configuration file in `~/.notary/config.json`:
183
-
184
-            "skipTLSVerify": true
185
-
186
-Otherwise, you will see TLS errors or X509 errors upon initializing the
187
-notary collection:
188
-
189
-```
190
-$ notary list diogomonica.com/openvpn
191
-* fatal: Get https://notary-server:4443/v2/: x509: certificate signed by unknown authority
192
-$ notary list diogomonica.com/openvpn -c cmd/notary/config.json
193
-latest b1df2ad7cbc19f06f08b69b4bcd817649b509f3e5420cdd2245a85144288e26d 4056
194
-```
89
+Run `make binaries`, which creates the Notary Client CLI binary at `bin/notary`.
195 90
deleted file mode 100644
... ...
@@ -1,280 +0,0 @@
1
-package certs
2
-
3
-import (
4
-	"crypto/x509"
5
-	"errors"
6
-	"fmt"
7
-	"time"
8
-
9
-	"github.com/Sirupsen/logrus"
10
-	"github.com/docker/notary/trustmanager"
11
-	"github.com/docker/notary/tuf/data"
12
-	"github.com/docker/notary/tuf/signed"
13
-)
14
-
15
-// ErrValidationFail is returned when there is no valid trusted certificates
16
-// being served inside of the roots.json
17
-type ErrValidationFail struct {
18
-	Reason string
19
-}
20
-
21
-// ErrValidationFail is returned when there is no valid trusted certificates
22
-// being served inside of the roots.json
23
-func (err ErrValidationFail) Error() string {
24
-	return fmt.Sprintf("could not validate the path to a trusted root: %s", err.Reason)
25
-}
26
-
27
-// ErrRootRotationFail is returned when we fail to do a full root key rotation
28
-// by either failing to add the new root certificate, or delete the old ones
29
-type ErrRootRotationFail struct {
30
-	Reason string
31
-}
32
-
33
-// ErrRootRotationFail is returned when we fail to do a full root key rotation
34
-// by either failing to add the new root certificate, or delete the old ones
35
-func (err ErrRootRotationFail) Error() string {
36
-	return fmt.Sprintf("could not rotate trust to a new trusted root: %s", err.Reason)
37
-}
38
-
39
-/*
40
-ValidateRoot receives a new root, validates its correctness and attempts to
41
-do root key rotation if needed.
42
-
43
-First we list the current trusted certificates we have for a particular GUN. If
44
-that list is non-empty means that we've already seen this repository before, and
45
-have a list of trusted certificates for it. In this case, we use this list of
46
-certificates to attempt to validate this root file.
47
-
48
-If the previous validation succeeds, or in the case where we found no trusted
49
-certificates for this particular GUN, we check the integrity of the root by
50
-making sure that it is validated by itself. This means that we will attempt to
51
-validate the root data with the certificates that are included in the root keys
52
-themselves.
53
-
54
-If this last steps succeeds, we attempt to do root rotation, by ensuring that
55
-we only trust the certificates that are present in the new root.
56
-
57
-This mechanism of operation is essentially Trust On First Use (TOFU): if we
58
-have never seen a certificate for a particular CN, we trust it. If later we see
59
-a different certificate for that certificate, we return an ErrValidationFailed error.
60
-
61
-Note that since we only allow trust data to be downloaded over an HTTPS channel
62
-we are using the current public PKI to validate the first download of the certificate
63
-adding an extra layer of security over the normal (SSH style) trust model.
64
-We shall call this: TOFUS.
65
-*/
66
-func ValidateRoot(certStore trustmanager.X509Store, root *data.Signed, gun string) error {
67
-	logrus.Debugf("entered ValidateRoot with dns: %s", gun)
68
-	signedRoot, err := data.RootFromSigned(root)
69
-	if err != nil {
70
-		return err
71
-	}
72
-
73
-	// Retrieve all the leaf certificates in root for which the CN matches the GUN
74
-	allValidCerts, err := validRootLeafCerts(signedRoot, gun)
75
-	if err != nil {
76
-		logrus.Debugf("error retrieving valid leaf certificates for: %s, %v", gun, err)
77
-		return &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"}
78
-	}
79
-
80
-	// Retrieve all the trusted certificates that match this gun
81
-	certsForCN, err := certStore.GetCertificatesByCN(gun)
82
-	if err != nil {
83
-		// If the error that we get back is different than ErrNoCertificatesFound
84
-		// we couldn't check if there are any certificates with this CN already
85
-		// trusted. Let's take the conservative approach and return a failed validation
86
-		if _, ok := err.(*trustmanager.ErrNoCertificatesFound); !ok {
87
-			logrus.Debugf("error retrieving trusted certificates for: %s, %v", gun, err)
88
-			return &ErrValidationFail{Reason: "unable to retrieve trusted certificates"}
89
-		}
90
-	}
91
-
92
-	// If we have certificates that match this specific GUN, let's make sure to
93
-	// use them first to validate that this new root is valid.
94
-	if len(certsForCN) != 0 {
95
-		logrus.Debugf("found %d valid root certificates for %s", len(certsForCN), gun)
96
-		err = signed.VerifyRoot(root, 0, trustmanager.CertsToKeys(certsForCN))
97
-		if err != nil {
98
-			logrus.Debugf("failed to verify TUF data for: %s, %v", gun, err)
99
-			return &ErrValidationFail{Reason: "failed to validate data with current trusted certificates"}
100
-		}
101
-	} else {
102
-		logrus.Debugf("found no currently valid root certificates for %s", gun)
103
-	}
104
-
105
-	// Validate the integrity of the new root (does it have valid signatures)
106
-	err = signed.VerifyRoot(root, 0, trustmanager.CertsToKeys(allValidCerts))
107
-	if err != nil {
108
-		logrus.Debugf("failed to verify TUF data for: %s, %v", gun, err)
109
-		return &ErrValidationFail{Reason: "failed to validate integrity of roots"}
110
-	}
111
-
112
-	// Getting here means A) we had trusted certificates and both the
113
-	// old and new validated this root; or B) we had no trusted certificates but
114
-	// the new set of certificates has integrity (self-signed)
115
-	logrus.Debugf("entering root certificate rotation for: %s", gun)
116
-
117
-	// Do root certificate rotation: we trust only the certs present in the new root
118
-	// First we add all the new certificates (even if they already exist)
119
-	for _, cert := range allValidCerts {
120
-		err := certStore.AddCert(cert)
121
-		if err != nil {
122
-			// If the error is already exists we don't fail the rotation
123
-			if _, ok := err.(*trustmanager.ErrCertExists); ok {
124
-				logrus.Debugf("ignoring certificate addition to: %s", gun)
125
-				continue
126
-			}
127
-			logrus.Debugf("error adding new trusted certificate for: %s, %v", gun, err)
128
-		}
129
-	}
130
-
131
-	// Now we delete old certificates that aren't present in the new root
132
-	for certID, cert := range certsToRemove(certsForCN, allValidCerts) {
133
-		logrus.Debugf("removing certificate with certID: %s", certID)
134
-		err = certStore.RemoveCert(cert)
135
-		if err != nil {
136
-			logrus.Debugf("failed to remove trusted certificate with keyID: %s, %v", certID, err)
137
-			return &ErrRootRotationFail{Reason: "failed to rotate root keys"}
138
-		}
139
-	}
140
-
141
-	logrus.Debugf("Root validation succeeded for %s", gun)
142
-	return nil
143
-}
144
-
145
-// validRootLeafCerts returns a list of non-exipired, non-sha1 certificates whose
146
-// Common-Names match the provided GUN
147
-func validRootLeafCerts(root *data.SignedRoot, gun string) ([]*x509.Certificate, error) {
148
-	// Get a list of all of the leaf certificates present in root
149
-	allLeafCerts, _ := parseAllCerts(root)
150
-	var validLeafCerts []*x509.Certificate
151
-
152
-	// Go through every leaf certificate and check that the CN matches the gun
153
-	for _, cert := range allLeafCerts {
154
-		// Validate that this leaf certificate has a CN that matches the exact gun
155
-		if cert.Subject.CommonName != gun {
156
-			logrus.Debugf("error leaf certificate CN: %s doesn't match the given GUN: %s",
157
-				cert.Subject.CommonName, gun)
158
-			continue
159
-		}
160
-		// Make sure the certificate is not expired
161
-		if time.Now().After(cert.NotAfter) {
162
-			logrus.Debugf("error leaf certificate is expired")
163
-			continue
164
-		}
165
-
166
-		// We don't allow root certificates that use SHA1
167
-		if cert.SignatureAlgorithm == x509.SHA1WithRSA ||
168
-			cert.SignatureAlgorithm == x509.DSAWithSHA1 ||
169
-			cert.SignatureAlgorithm == x509.ECDSAWithSHA1 {
170
-
171
-			logrus.Debugf("error certificate uses deprecated hashing algorithm (SHA1)")
172
-			continue
173
-		}
174
-
175
-		validLeafCerts = append(validLeafCerts, cert)
176
-	}
177
-
178
-	if len(validLeafCerts) < 1 {
179
-		logrus.Debugf("didn't find any valid leaf certificates for %s", gun)
180
-		return nil, errors.New("no valid leaf certificates found in any of the root keys")
181
-	}
182
-
183
-	logrus.Debugf("found %d valid leaf certificates for %s", len(validLeafCerts), gun)
184
-	return validLeafCerts, nil
185
-}
186
-
187
-// parseAllCerts returns two maps, one with all of the leafCertificates and one
188
-// with all the intermediate certificates found in signedRoot
189
-func parseAllCerts(signedRoot *data.SignedRoot) (map[string]*x509.Certificate, map[string][]*x509.Certificate) {
190
-	leafCerts := make(map[string]*x509.Certificate)
191
-	intCerts := make(map[string][]*x509.Certificate)
192
-
193
-	// Before we loop through all root keys available, make sure any exist
194
-	rootRoles, ok := signedRoot.Signed.Roles["root"]
195
-	if !ok {
196
-		logrus.Debugf("tried to parse certificates from invalid root signed data")
197
-		return nil, nil
198
-	}
199
-
200
-	logrus.Debugf("found the following root keys: %v", rootRoles.KeyIDs)
201
-	// Iterate over every keyID for the root role inside of roots.json
202
-	for _, keyID := range rootRoles.KeyIDs {
203
-		// check that the key exists in the signed root keys map
204
-		key, ok := signedRoot.Signed.Keys[keyID]
205
-		if !ok {
206
-			logrus.Debugf("error while getting data for keyID: %s", keyID)
207
-			continue
208
-		}
209
-
210
-		// Decode all the x509 certificates that were bundled with this
211
-		// Specific root key
212
-		decodedCerts, err := trustmanager.LoadCertBundleFromPEM(key.Public())
213
-		if err != nil {
214
-			logrus.Debugf("error while parsing root certificate with keyID: %s, %v", keyID, err)
215
-			continue
216
-		}
217
-
218
-		// Get all non-CA certificates in the decoded certificates
219
-		leafCertList := trustmanager.GetLeafCerts(decodedCerts)
220
-
221
-		// If we got no leaf certificates or we got more than one, fail
222
-		if len(leafCertList) != 1 {
223
-			logrus.Debugf("invalid chain due to leaf certificate missing or too many leaf certificates for keyID: %s", keyID)
224
-			continue
225
-		}
226
-
227
-		// Get the ID of the leaf certificate
228
-		leafCert := leafCertList[0]
229
-		leafID, err := trustmanager.FingerprintCert(leafCert)
230
-		if err != nil {
231
-			logrus.Debugf("error while fingerprinting root certificate with keyID: %s, %v", keyID, err)
232
-			continue
233
-		}
234
-
235
-		// Store the leaf cert in the map
236
-		leafCerts[leafID] = leafCert
237
-
238
-		// Get all the remainder certificates marked as a CA to be used as intermediates
239
-		intermediateCerts := trustmanager.GetIntermediateCerts(decodedCerts)
240
-		intCerts[leafID] = intermediateCerts
241
-	}
242
-
243
-	return leafCerts, intCerts
244
-}
245
-
246
-// certsToRemove returns all the certifificates from oldCerts that aren't present
247
-// in newCerts
248
-func certsToRemove(oldCerts, newCerts []*x509.Certificate) map[string]*x509.Certificate {
249
-	certsToRemove := make(map[string]*x509.Certificate)
250
-
251
-	// If no newCerts were provided
252
-	if len(newCerts) == 0 {
253
-		return certsToRemove
254
-	}
255
-
256
-	// Populate a map with all the IDs from newCert
257
-	var newCertMap = make(map[string]struct{})
258
-	for _, cert := range newCerts {
259
-		certID, err := trustmanager.FingerprintCert(cert)
260
-		if err != nil {
261
-			logrus.Debugf("error while fingerprinting root certificate with keyID: %s, %v", certID, err)
262
-			continue
263
-		}
264
-		newCertMap[certID] = struct{}{}
265
-	}
266
-
267
-	// Iterate over all the old certificates and check to see if we should remove them
268
-	for _, cert := range oldCerts {
269
-		certID, err := trustmanager.FingerprintCert(cert)
270
-		if err != nil {
271
-			logrus.Debugf("error while fingerprinting root certificate with certID: %s, %v", certID, err)
272
-			continue
273
-		}
274
-		if _, ok := newCertMap[certID]; !ok {
275
-			certsToRemove[certID] = cert
276
-		}
277
-	}
278
-
279
-	return certsToRemove
280
-}
... ...
@@ -3,10 +3,18 @@ machine:
3 3
   pre:
4 4
   # Install gvm
5 5
     - bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/1.0.22/binscripts/gvm-installer)
6
+  # Upgrade docker
7
+    - sudo curl -L -o /usr/bin/docker 'https://s3-external-1.amazonaws.com/circle-downloads/docker-1.9.1-circleci'
8
+    - sudo chmod 0755 /usr/bin/docker
6 9
 
7 10
   post:
8 11
   # Install many go versions
9
-    - gvm install go1.6 -B --name=stable
12
+    - gvm install go1.6.1 -B --name=stable
13
+  # upgrade compose
14
+    - sudo pip install --upgrade docker-compose
15
+
16
+  services:
17
+    - docker
10 18
 
11 19
   environment:
12 20
   # Convenient shortcuts to "common" locations
... ...
@@ -32,16 +40,13 @@ dependencies:
32 32
       cp -R "$CHECKOUT" "$BASE_STABLE"
33 33
 
34 34
   override:
35
-  # Install dependencies for every copied clone/go version
36
-    - gvm use stable && go get github.com/tools/godep:
37
-        pwd: $BASE_STABLE
38
-
39
-  post:
40
-  # For the stable go version, additionally install linting and misspell tools
35
+   # don't use circleci's default dependency installation step of `go get -d -u ./...`
36
+   # since we already vendor everything; additionally install linting and misspell tools
41 37
     - >
42 38
       gvm use stable &&
43 39
       go get github.com/golang/lint/golint &&
44 40
       go get -u github.com/client9/misspell/cmd/misspell
41
+
45 42
 test:
46 43
   pre:
47 44
   # Output the go versions we are going to test
... ...
@@ -70,13 +75,13 @@ test:
70 70
   override:
71 71
   # Test stable, and report
72 72
   # hacking this to be parallel
73
-    - case $CIRCLE_NODE_INDEX in 0) gvm use stable && NOTARY_BUILDTAGS=pkcs11 make ci ;; 1) gvm use stable && NOTARY_BUILDTAGS=none make ci ;; esac:
73
+    - case $CIRCLE_NODE_INDEX in 0) gvm use stable && NOTARY_BUILDTAGS=pkcs11 make ci ;; 1) gvm use stable && NOTARY_BUILDTAGS=none make ci ;; 2) gvm use stable && make integration ;; esac:
74 74
         parallel: true
75 75
         timeout: 600
76 76
         pwd: $BASE_STABLE
77 77
 
78 78
   post:
79 79
   # Report to codecov.io
80
-    - bash <(curl -s https://codecov.io/bash):
80
+    - case $CIRCLE_NODE_INDEX in 0) bash <(curl -s https://codecov.io/bash) ;; 1) bash <(curl -s https://codecov.io/bash) ;; esac:
81 81
         parallel: true
82 82
         pwd: $BASE_STABLE
... ...
@@ -2,6 +2,7 @@ package client
2 2
 
3 3
 import (
4 4
 	"bytes"
5
+	"crypto/x509"
5 6
 	"encoding/json"
6 7
 	"fmt"
7 8
 	"io/ioutil"
... ...
@@ -14,10 +15,10 @@ import (
14 14
 
15 15
 	"github.com/Sirupsen/logrus"
16 16
 	"github.com/docker/notary"
17
-	"github.com/docker/notary/certs"
18 17
 	"github.com/docker/notary/client/changelist"
19 18
 	"github.com/docker/notary/cryptoservice"
20 19
 	"github.com/docker/notary/trustmanager"
20
+	"github.com/docker/notary/trustpinning"
21 21
 	"github.com/docker/notary/tuf"
22 22
 	tufclient "github.com/docker/notary/tuf/client"
23 23
 	"github.com/docker/notary/tuf/data"
... ...
@@ -87,13 +88,14 @@ type NotaryRepository struct {
87 87
 	tufRepo       *tuf.Repo
88 88
 	roundTrip     http.RoundTripper
89 89
 	CertStore     trustmanager.X509Store
90
+	trustPinning  trustpinning.TrustPinConfig
90 91
 }
91 92
 
92 93
 // repositoryFromKeystores is a helper function for NewNotaryRepository that
93 94
 // takes some basic NotaryRepository parameters as well as keystores (in order
94 95
 // of usage preference), and returns a NotaryRepository.
95 96
 func repositoryFromKeystores(baseDir, gun, baseURL string, rt http.RoundTripper,
96
-	keyStores []trustmanager.KeyStore) (*NotaryRepository, error) {
97
+	keyStores []trustmanager.KeyStore, trustPin trustpinning.TrustPinConfig) (*NotaryRepository, error) {
97 98
 
98 99
 	certPath := filepath.Join(baseDir, notary.TrustedCertsDir)
99 100
 	certStore, err := trustmanager.NewX509FilteredFileStore(
... ...
@@ -114,6 +116,7 @@ func repositoryFromKeystores(baseDir, gun, baseURL string, rt http.RoundTripper,
114 114
 		CryptoService: cryptoService,
115 115
 		roundTrip:     rt,
116 116
 		CertStore:     certStore,
117
+		trustPinning:  trustPin,
117 118
 	}
118 119
 
119 120
 	fileStore, err := store.NewFilesystemStore(
... ...
@@ -159,8 +162,29 @@ func NewTarget(targetName string, targetPath string) (*Target, error) {
159 159
 	return &Target{Name: targetName, Hashes: meta.Hashes, Length: meta.Length}, nil
160 160
 }
161 161
 
162
+func rootCertKey(gun string, privKey data.PrivateKey) (*x509.Certificate, data.PublicKey, error) {
163
+	// Hard-coded policy: the generated certificate expires in 10 years.
164
+	startTime := time.Now()
165
+	cert, err := cryptoservice.GenerateCertificate(
166
+		privKey, gun, startTime, startTime.Add(notary.Year*10))
167
+	if err != nil {
168
+		return nil, nil, err
169
+	}
170
+
171
+	x509PublicKey := trustmanager.CertToKey(cert)
172
+	if x509PublicKey == nil {
173
+		return nil, nil, fmt.Errorf(
174
+			"cannot use regenerated certificate: format %s", cert.PublicKeyAlgorithm)
175
+	}
176
+
177
+	return cert, x509PublicKey, nil
178
+}
179
+
162 180
 // Initialize creates a new repository by using rootKey as the root Key for the
163
-// TUF repository.
181
+// TUF repository. The server must be reachable (and is asked to generate a
182
+// timestamp key and possibly other serverManagedRoles), but the created repository
183
+// result is only stored on local disk, not published to the server. To do that,
184
+// use r.Publish() eventually.
164 185
 func (r *NotaryRepository) Initialize(rootKeyID string, serverManagedRoles ...string) error {
165 186
 	privKey, _, err := r.CryptoService.GetPrivateKey(rootKeyID)
166 187
 	if err != nil {
... ...
@@ -194,31 +218,12 @@ func (r *NotaryRepository) Initialize(rootKeyID string, serverManagedRoles ...st
194 194
 		}
195 195
 	}
196 196
 
197
-	// Hard-coded policy: the generated certificate expires in 10 years.
198
-	startTime := time.Now()
199
-	rootCert, err := cryptoservice.GenerateCertificate(
200
-		privKey, r.gun, startTime, startTime.AddDate(10, 0, 0))
201
-
197
+	rootCert, rootKey, err := rootCertKey(r.gun, privKey)
202 198
 	if err != nil {
203 199
 		return err
204 200
 	}
205 201
 	r.CertStore.AddCert(rootCert)
206 202
 
207
-	// The root key gets stored in the TUF metadata X509 encoded, linking
208
-	// the tuf root.json to our X509 PKI.
209
-	// If the key is RSA, we store it as type RSAx509, if it is ECDSA we store it
210
-	// as ECDSAx509 to allow the gotuf verifiers to correctly decode the
211
-	// key on verification of signatures.
212
-	var rootKey data.PublicKey
213
-	switch privKey.Algorithm() {
214
-	case data.RSAKey:
215
-		rootKey = data.NewRSAx509PublicKey(trustmanager.CertToPEM(rootCert))
216
-	case data.ECDSAKey:
217
-		rootKey = data.NewECDSAx509PublicKey(trustmanager.CertToPEM(rootCert))
218
-	default:
219
-		return fmt.Errorf("invalid format for root key: %s", privKey.Algorithm())
220
-	}
221
-
222 203
 	var (
223 204
 		rootRole = data.NewBaseRole(
224 205
 			data.CanonicalRootRole,
... ...
@@ -341,9 +346,12 @@ func addChange(cl *changelist.FileChangelist, c changelist.Change, roles ...stri
341 341
 
342 342
 // AddTarget creates new changelist entries to add a target to the given roles
343 343
 // in the repository when the changelist gets applied at publish time.
344
-// If roles are unspecified, the default role is "targets".
344
+// If roles are unspecified, the default role is "targets"
345 345
 func (r *NotaryRepository) AddTarget(target *Target, roles ...string) error {
346 346
 
347
+	if len(target.Hashes) == 0 {
348
+		return fmt.Errorf("no hashes specified for target \"%s\"", target.Name)
349
+	}
347 350
 	cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
348 351
 	if err != nil {
349 352
 		return err
... ...
@@ -386,7 +394,7 @@ func (r *NotaryRepository) RemoveTarget(targetName string, roles ...string) erro
386 386
 // subtree and also the "targets/x" subtree, as we will defer parsing it until
387 387
 // we explicitly reach it in our iteration of the provided list of roles.
388 388
 func (r *NotaryRepository) ListTargets(roles ...string) ([]*TargetWithRole, error) {
389
-	_, err := r.Update(false)
389
+	err := r.Update(false)
390 390
 	if err != nil {
391 391
 		return nil, err
392 392
 	}
... ...
@@ -432,8 +440,7 @@ func (r *NotaryRepository) ListTargets(roles ...string) ([]*TargetWithRole, erro
432 432
 // will be returned
433 433
 // See the IMPORTANT section on ListTargets above. Those roles also apply here.
434 434
 func (r *NotaryRepository) GetTargetByName(name string, roles ...string) (*TargetWithRole, error) {
435
-	_, err := r.Update(false)
436
-	if err != nil {
435
+	if err := r.Update(false); err != nil {
437 436
 		return nil, err
438 437
 	}
439 438
 
... ...
@@ -460,9 +467,8 @@ func (r *NotaryRepository) GetTargetByName(name string, roles ...string) (*Targe
460 460
 			}
461 461
 			return nil
462 462
 		}
463
-		err = r.tufRepo.WalkTargets(name, role, getTargetVisitorFunc, skipRoles...)
464 463
 		// Check that we didn't error, and that we assigned to our target
465
-		if err == nil && foundTarget {
464
+		if err := r.tufRepo.WalkTargets(name, role, getTargetVisitorFunc, skipRoles...); err == nil && foundTarget {
466 465
 			return &TargetWithRole{Target: Target{Name: name, Hashes: resultMeta.Hashes, Length: resultMeta.Length}, Role: resultRoleName}, nil
467 466
 		}
468 467
 	}
... ...
@@ -491,8 +497,7 @@ type RoleWithSignatures struct {
491 491
 // This represents the latest metadata for each role in this repo
492 492
 func (r *NotaryRepository) ListRoles() ([]RoleWithSignatures, error) {
493 493
 	// Update to latest repo state
494
-	_, err := r.Update(false)
495
-	if err != nil {
494
+	if err := r.Update(false); err != nil {
496 495
 		return nil, err
497 496
 	}
498 497
 
... ...
@@ -514,9 +519,8 @@ func (r *NotaryRepository) ListRoles() ([]RoleWithSignatures, error) {
514 514
 		case data.CanonicalTimestampRole:
515 515
 			roleWithSig.Signatures = r.tufRepo.Timestamp.Signatures
516 516
 		default:
517
-			// If the role isn't a delegation, we should error -- this is only possible if we have invalid state
518 517
 			if !data.IsDelegation(role.Name) {
519
-				return nil, data.ErrInvalidRole{Role: role.Name, Reason: "invalid role name"}
518
+				continue
520 519
 			}
521 520
 			if _, ok := r.tufRepo.Targets[role.Name]; ok {
522 521
 				// We'll only find a signature if we've published any targets with this delegation
... ...
@@ -552,8 +556,7 @@ func (r *NotaryRepository) Publish() error {
552 552
 func (r *NotaryRepository) publish(cl changelist.Changelist) error {
553 553
 	var initialPublish bool
554 554
 	// update first before publishing
555
-	_, err := r.Update(true)
556
-	if err != nil {
555
+	if err := r.Update(true); err != nil {
557 556
 		// If the remote is not aware of the repo, then this is being published
558 557
 		// for the first time.  Try to load from disk instead for publishing.
559 558
 		if _, ok := err.(ErrRepositoryNotExist); ok {
... ...
@@ -578,8 +581,7 @@ func (r *NotaryRepository) publish(cl changelist.Changelist) error {
578 578
 		}
579 579
 	}
580 580
 	// apply the changelist to the repo
581
-	err = applyChangelist(r.tufRepo, cl)
582
-	if err != nil {
581
+	if err := applyChangelist(r.tufRepo, cl); err != nil {
583 582
 		logrus.Debug("Error applying changelist")
584 583
 		return err
585 584
 	}
... ...
@@ -631,7 +633,7 @@ func (r *NotaryRepository) publish(cl changelist.Changelist) error {
631 631
 	if err == nil {
632 632
 		// Only update the snapshot if we've successfully signed it.
633 633
 		updatedFiles[data.CanonicalSnapshotRole] = snapshotJSON
634
-	} else if _, ok := err.(signed.ErrNoKeys); ok {
634
+	} else if signErr, ok := err.(signed.ErrInsufficientSignatures); ok && signErr.FoundKeys == 0 {
635 635
 		// If signing fails due to us not having the snapshot key, then
636 636
 		// assume the server is going to sign, and do not include any snapshot
637 637
 		// data.
... ...
@@ -650,16 +652,17 @@ func (r *NotaryRepository) publish(cl changelist.Changelist) error {
650 650
 	return remote.SetMultiMeta(updatedFiles)
651 651
 }
652 652
 
653
-// bootstrapRepo loads the repository from the local file system.  This attempts
654
-// to load metadata for all roles.  Since server snapshots are supported,
655
-// if the snapshot metadata fails to load, that's ok.
653
+// bootstrapRepo loads the repository from the local file system (i.e.
654
+// a not yet published repo or a possibly obsolete local copy) into
655
+// r.tufRepo.  This attempts to load metadata for all roles.  Since server
656
+// snapshots are supported, if the snapshot metadata fails to load, that's ok.
656 657
 // This can also be unified with some cache reading tools from tuf/client.
657 658
 // This assumes that bootstrapRepo is only used by Publish() or RotateKey()
658 659
 func (r *NotaryRepository) bootstrapRepo() error {
659 660
 	tufRepo := tuf.NewRepo(r.CryptoService)
660 661
 
661 662
 	logrus.Debugf("Loading trusted collection.")
662
-	rootJSON, err := r.fileStore.GetMeta("root", -1)
663
+	rootJSON, err := r.fileStore.GetMeta(data.CanonicalRootRole, -1)
663 664
 	if err != nil {
664 665
 		return err
665 666
 	}
... ...
@@ -672,7 +675,7 @@ func (r *NotaryRepository) bootstrapRepo() error {
672 672
 	if err != nil {
673 673
 		return err
674 674
 	}
675
-	targetsJSON, err := r.fileStore.GetMeta("targets", -1)
675
+	targetsJSON, err := r.fileStore.GetMeta(data.CanonicalTargetsRole, -1)
676 676
 	if err != nil {
677 677
 		return err
678 678
 	}
... ...
@@ -681,9 +684,9 @@ func (r *NotaryRepository) bootstrapRepo() error {
681 681
 	if err != nil {
682 682
 		return err
683 683
 	}
684
-	tufRepo.SetTargets("targets", targets)
684
+	tufRepo.SetTargets(data.CanonicalTargetsRole, targets)
685 685
 
686
-	snapshotJSON, err := r.fileStore.GetMeta("snapshot", -1)
686
+	snapshotJSON, err := r.fileStore.GetMeta(data.CanonicalSnapshotRole, -1)
687 687
 	if err == nil {
688 688
 		snapshot := &data.SignedSnapshot{}
689 689
 		err = json.Unmarshal(snapshotJSON, snapshot)
... ...
@@ -700,6 +703,8 @@ func (r *NotaryRepository) bootstrapRepo() error {
700 700
 	return nil
701 701
 }
702 702
 
703
+// saveMetadata saves contents of r.tufRepo onto the local disk, creating
704
+// signatures as necessary, possibly prompting for passphrases.
703 705
 func (r *NotaryRepository) saveMetadata(ignoreSnapshot bool) error {
704 706
 	logrus.Debugf("Saving changes to Trusted Collection.")
705 707
 
... ...
@@ -714,7 +719,7 @@ func (r *NotaryRepository) saveMetadata(ignoreSnapshot bool) error {
714 714
 
715 715
 	targetsToSave := make(map[string][]byte)
716 716
 	for t := range r.tufRepo.Targets {
717
-		signedTargets, err := r.tufRepo.SignTargets(t, data.DefaultExpires("targets"))
717
+		signedTargets, err := r.tufRepo.SignTargets(t, data.DefaultExpires(data.CanonicalTargetsRole))
718 718
 		if err != nil {
719 719
 			return err
720 720
 		}
... ...
@@ -756,25 +761,24 @@ func (r *NotaryRepository) errRepositoryNotExist() error {
756 756
 
757 757
 // Update bootstraps a trust anchor (root.json) before updating all the
758 758
 // metadata from the repo.
759
-func (r *NotaryRepository) Update(forWrite bool) (*tufclient.Client, error) {
759
+func (r *NotaryRepository) Update(forWrite bool) error {
760 760
 	c, err := r.bootstrapClient(forWrite)
761 761
 	if err != nil {
762 762
 		if _, ok := err.(store.ErrMetaNotFound); ok {
763
-			return nil, r.errRepositoryNotExist()
763
+			return r.errRepositoryNotExist()
764 764
 		}
765
-		return nil, err
765
+		return err
766 766
 	}
767
-	err = c.Update()
768
-	if err != nil {
767
+	if err := c.Update(); err != nil {
769 768
 		// notFound.Resource may include a checksum so when the role is root,
770 769
 		// it will be root.json or root.<checksum>.json. Therefore best we can
771 770
 		// do it match a "root." prefix
772 771
 		if notFound, ok := err.(store.ErrMetaNotFound); ok && strings.HasPrefix(notFound.Resource, data.CanonicalRootRole+".") {
773
-			return nil, r.errRepositoryNotExist()
772
+			return r.errRepositoryNotExist()
774 773
 		}
775
-		return nil, err
774
+		return err
776 775
 	}
777
-	return c, nil
776
+	return nil
778 777
 }
779 778
 
780 779
 // bootstrapClient attempts to bootstrap a root.json to be used as the trust
... ...
@@ -782,6 +786,20 @@ func (r *NotaryRepository) Update(forWrite bool) (*tufclient.Client, error) {
782 782
 // we should always attempt to contact the server to determine if the repository
783 783
 // is initialized or not. If set to true, we will always attempt to download
784 784
 // and return an error if the remote repository errors.
785
+//
786
+// Partially populates r.tufRepo with this root metadata (only; use
787
+// tufclient.Client.Update to load the rest).
788
+//
789
+// As another side effect, r.CertManager's list of trusted certificates
790
+// is updated with data from the loaded root.json.
791
+//
792
+// Fails if the remote server is reachable and does not know the repo
793
+// (i.e. before the first r.Publish()), in which case the error is
794
+// store.ErrMetaNotFound, or if the root metadata (from whichever source is used)
795
+// is not trusted.
796
+//
797
+// Returns a tufclient.Client for the remote server, which may not be actually
798
+// operational (if the URL is invalid but a root.json is cached).
785 799
 func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Client, error) {
786 800
 	var (
787 801
 		rootJSON   []byte
... ...
@@ -791,7 +809,7 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl
791 791
 	// try to read root from cache first. We will trust this root
792 792
 	// until we detect a problem during update which will cause
793 793
 	// us to download a new root and perform a rotation.
794
-	rootJSON, cachedRootErr := r.fileStore.GetMeta("root", -1)
794
+	rootJSON, cachedRootErr := r.fileStore.GetMeta(data.CanonicalRootRole, -1)
795 795
 
796 796
 	if cachedRootErr == nil {
797 797
 		signedRoot, cachedRootErr = r.validateRoot(rootJSON)
... ...
@@ -806,7 +824,7 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl
806 806
 
807 807
 		// if remote store successfully set up, try and get root from remote
808 808
 		// We don't have any local data to determine the size of root, so try the maximum (though it is restricted at 100MB)
809
-		tmpJSON, err := remote.GetMeta("root", -1)
809
+		tmpJSON, err := remote.GetMeta(data.CanonicalRootRole, -1)
810 810
 		if err != nil {
811 811
 			// we didn't have a root in cache and were unable to load one from
812 812
 			// the server. Nothing we can do but error.
... ...
@@ -820,7 +838,7 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl
820 820
 				return nil, err
821 821
 			}
822 822
 
823
-			err = r.fileStore.SetMeta("root", tmpJSON)
823
+			err = r.fileStore.SetMeta(data.CanonicalRootRole, tmpJSON)
824 824
 			if err != nil {
825 825
 				// if we can't write cache we should still continue, just log error
826 826
 				logrus.Errorf("could not save root to cache: %s", err.Error())
... ...
@@ -860,7 +878,7 @@ func (r *NotaryRepository) validateRoot(rootJSON []byte) (*data.SignedRoot, erro
860 860
 		return nil, err
861 861
 	}
862 862
 
863
-	err = certs.ValidateRoot(r.CertStore, root, r.gun)
863
+	err = trustpinning.ValidateRoot(r.CertStore, root, r.gun, r.trustPinning)
864 864
 	if err != nil {
865 865
 		return nil, err
866 866
 	}
... ...
@@ -872,25 +890,19 @@ func (r *NotaryRepository) validateRoot(rootJSON []byte) (*data.SignedRoot, erro
872 872
 // creates and adds one new key or delegates managing the key to the server.
873 873
 // These changes are staged in a changelist until publish is called.
874 874
 func (r *NotaryRepository) RotateKey(role string, serverManagesKey bool) error {
875
-	switch {
876
-	// We currently support locally or remotely managing snapshot keys...
877
-	case role == data.CanonicalSnapshotRole:
878
-		break
879
-
880
-	// locally managing targets keys only
881
-	case role == data.CanonicalTargetsRole && !serverManagesKey:
882
-		break
883
-	case role == data.CanonicalTargetsRole && serverManagesKey:
884
-		return ErrInvalidRemoteRole{Role: data.CanonicalTargetsRole}
885
-
886
-	// and remotely managing timestamp keys only
887
-	case role == data.CanonicalTimestampRole && serverManagesKey:
888
-		break
889
-	case role == data.CanonicalTimestampRole && !serverManagesKey:
890
-		return ErrInvalidLocalRole{Role: data.CanonicalTimestampRole}
875
+	// We currently support remotely managing timestamp and snapshot keys
876
+	canBeRemoteKey := role == data.CanonicalTimestampRole || role == data.CanonicalSnapshotRole
877
+	// And locally managing root, targets, and snapshot keys
878
+	canBeLocalKey := (role == data.CanonicalSnapshotRole || role == data.CanonicalTargetsRole ||
879
+		role == data.CanonicalRootRole)
891 880
 
892
-	default:
881
+	switch {
882
+	case !data.ValidRole(role) || data.IsDelegation(role):
893 883
 		return fmt.Errorf("notary does not currently permit rotating the %s key", role)
884
+	case serverManagesKey && !canBeRemoteKey:
885
+		return ErrInvalidRemoteRole{Role: role}
886
+	case !serverManagesKey && !canBeLocalKey:
887
+		return ErrInvalidLocalRole{Role: role}
894 888
 	}
895 889
 
896 890
 	var (
... ...
@@ -911,6 +923,18 @@ func (r *NotaryRepository) RotateKey(role string, serverManagesKey bool) error {
911 911
 		return fmt.Errorf(errFmtMsg, err)
912 912
 	}
913 913
 
914
+	// if this is a root role, generate a root cert for the public key
915
+	if role == data.CanonicalRootRole {
916
+		privKey, _, err := r.CryptoService.GetPrivateKey(pubKey.ID())
917
+		if err != nil {
918
+			return err
919
+		}
920
+		_, pubKey, err = rootCertKey(r.gun, privKey)
921
+		if err != nil {
922
+			return err
923
+		}
924
+	}
925
+
914 926
 	cl := changelist.NewMemChangelist()
915 927
 	if err := r.rootFileKeyChange(cl, role, changelist.ActionCreate, pubKey); err != nil {
916 928
 		return err
... ...
@@ -240,7 +240,7 @@ func newDeleteDelegationChange(name string, content []byte) *changelist.TufChang
240 240
 // Also converts key IDs to canonical key IDs to keep consistent with signing prompts
241 241
 func (r *NotaryRepository) GetDelegationRoles() ([]*data.Role, error) {
242 242
 	// Update state of the repo to latest
243
-	if _, err := r.Update(false); err != nil {
243
+	if err := r.Update(false); err != nil {
244 244
 		return nil, err
245 245
 	}
246 246
 
... ...
@@ -8,13 +8,14 @@ import (
8 8
 
9 9
 	"github.com/docker/notary/passphrase"
10 10
 	"github.com/docker/notary/trustmanager"
11
+	"github.com/docker/notary/trustpinning"
11 12
 )
12 13
 
13 14
 // NewNotaryRepository is a helper method that returns a new notary repository.
14 15
 // It takes the base directory under where all the trust files will be stored
15 16
 // (usually ~/.docker/trust/).
16 17
 func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper,
17
-	retriever passphrase.Retriever) (
18
+	retriever passphrase.Retriever, trustPinning trustpinning.TrustPinConfig) (
18 19
 	*NotaryRepository, error) {
19 20
 
20 21
 	fileKeyStore, err := trustmanager.NewKeyFileStore(baseDir, retriever)
... ...
@@ -23,5 +24,5 @@ func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper,
23 23
 	}
24 24
 
25 25
 	return repositoryFromKeystores(baseDir, gun, baseURL, rt,
26
-		[]trustmanager.KeyStore{fileKeyStore})
26
+		[]trustmanager.KeyStore{fileKeyStore}, trustPinning)
27 27
 }
... ...
@@ -9,13 +9,14 @@ import (
9 9
 	"github.com/docker/notary/passphrase"
10 10
 	"github.com/docker/notary/trustmanager"
11 11
 	"github.com/docker/notary/trustmanager/yubikey"
12
+	"github.com/docker/notary/trustpinning"
12 13
 )
13 14
 
14 15
 // NewNotaryRepository is a helper method that returns a new notary repository.
15 16
 // It takes the base directory under where all the trust files will be stored
16 17
 // (usually ~/.docker/trust/).
17 18
 func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper,
18
-	retriever passphrase.Retriever) (
19
+	retriever passphrase.Retriever, trustPinning trustpinning.TrustPinConfig) (
19 20
 	*NotaryRepository, error) {
20 21
 
21 22
 	fileKeyStore, err := trustmanager.NewKeyFileStore(baseDir, retriever)
... ...
@@ -24,10 +25,10 @@ func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper,
24 24
 	}
25 25
 
26 26
 	keyStores := []trustmanager.KeyStore{fileKeyStore}
27
-	yubiKeyStore, _ := yubikey.NewYubiKeyStore(fileKeyStore, retriever)
27
+	yubiKeyStore, _ := yubikey.NewYubiStore(fileKeyStore, retriever)
28 28
 	if yubiKeyStore != nil {
29 29
 		keyStores = []trustmanager.KeyStore{yubiKeyStore, fileKeyStore}
30 30
 	}
31 31
 
32
-	return repositoryFromKeystores(baseDir, gun, baseURL, rt, keyStores)
32
+	return repositoryFromKeystores(baseDir, gun, baseURL, rt, keyStores, trustPinning)
33 33
 }
... ...
@@ -20,6 +20,8 @@ const (
20 20
 	PubCertPerms = 0755
21 21
 	// Sha256HexSize is how big a Sha256 hex is in number of characters
22 22
 	Sha256HexSize = 64
23
+	// Sha512HexSize is how big a Sha512 hex is in number of characters
24
+	Sha512HexSize = 128
23 25
 	// SHA256 is the name of SHA256 hash algorithm
24 26
 	SHA256 = "sha256"
25 27
 	// SHA512 is the name of SHA512 hash algorithm
... ...
@@ -49,6 +51,11 @@ const (
49 49
 	// (one year, in seconds, since one year is forever in terms of internet
50 50
 	// content)
51 51
 	CacheMaxAgeLimit = 1 * Year
52
+
53
+	MySQLBackend     = "mysql"
54
+	MemoryBackend    = "memory"
55
+	SQLiteBackend    = "sqlite3"
56
+	RethinkDBBackend = "rethinkdb"
52 57
 )
53 58
 
54 59
 // NotaryDefaultExpiries is the construct used to configure the default expiry times of
... ...
@@ -21,13 +21,6 @@ func GenerateCertificate(rootKey data.PrivateKey, gun string, startTime, endTime
21 21
 	return generateCertificate(signer, gun, startTime, endTime)
22 22
 }
23 23
 
24
-// GenerateTestingCertificate generates a non-expired X509 Certificate from a template, given a GUN.
25
-// Good enough for tests where expiration does not really matter; do not use if you care about the policy.
26
-func GenerateTestingCertificate(signer crypto.Signer, gun string) (*x509.Certificate, error) {
27
-	startTime := time.Now()
28
-	return generateCertificate(signer, gun, startTime, startTime.AddDate(10, 0, 0))
29
-}
30
-
31 24
 func generateCertificate(signer crypto.Signer, gun string, startTime, endTime time.Time) (*x509.Certificate, error) {
32 25
 	template, err := trustmanager.NewCertificate(gun, startTime, endTime)
33 26
 	if err != nil {
34 27
new file mode 100644
... ...
@@ -0,0 +1,113 @@
0
+version: "2"
1
+services:
2
+    server:
3
+      build:
4
+        context: .
5
+        dockerfile: server.Dockerfile
6
+      volumes:
7
+        - ./fixtures/rethinkdb:/tls
8
+      networks:
9
+        - rdb
10
+      links:
11
+        - rdb-proxy:rdb-proxy.rdb
12
+        - signer
13
+      environment:
14
+        - SERVICE_NAME=notary_server
15
+      ports:
16
+        - "8080"
17
+        - "4443:4443"
18
+      entrypoint: /usr/bin/env sh
19
+      command: -c "sh migrations/rethink_migrate.sh && notary-server -config=fixtures/server-config.rethink.json"
20
+      depends_on:
21
+        - rdb-proxy
22
+    signer:
23
+      build:
24
+        context: .
25
+        dockerfile: signer.Dockerfile
26
+      volumes:
27
+        - ./fixtures/rethinkdb:/tls
28
+      networks:
29
+        rdb:
30
+            aliases:
31
+                - notarysigner
32
+      links:
33
+        - rdb-proxy:rdb-proxy.rdb
34
+      environment:
35
+        - SERVICE_NAME=notary_signer
36
+      entrypoint: /usr/bin/env sh
37
+      command: -c "sh migrations/rethink_migrate.sh && notary-signer -config=fixtures/signer-config.rethink.json"
38
+      depends_on:
39
+        - rdb-proxy
40
+    rdb-01:
41
+      image: jlhawn/rethinkdb-tls
42
+      volumes:
43
+        - ./fixtures/rethinkdb:/tls
44
+        - rdb-01-data:/var/data
45
+      networks:
46
+        rdb:
47
+          aliases:
48
+            - rdb
49
+            - rdb.rdb
50
+            - rdb-01.rdb
51
+      command: "--bind all --no-http-admin --server-name rdb_01 --canonical-address rdb-01.rdb --directory /var/data/rethinkdb --join rdb.rdb --driver-tls --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
52
+    rdb-02:
53
+      image: jlhawn/rethinkdb-tls
54
+      volumes:
55
+        - ./fixtures/rethinkdb:/tls
56
+        - rdb-02-data:/var/data
57
+      networks:
58
+        rdb:
59
+          aliases:
60
+            - rdb
61
+            - rdb.rdb
62
+            - rdb-02.rdb
63
+      command: "--bind all --no-http-admin --server-name rdb_02 --canonical-address rdb-02.rdb --directory /var/data/rethinkdb --join rdb.rdb --driver-tls --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
64
+    rdb-03:
65
+      image: jlhawn/rethinkdb-tls
66
+      volumes:
67
+        - ./fixtures/rethinkdb:/tls
68
+        - rdb-03-data:/var/data
69
+      networks:
70
+        rdb:
71
+          aliases:
72
+            - rdb
73
+            - rdb.rdb
74
+            - rdb-03.rdb
75
+      command: "--bind all --no-http-admin --server-name rdb_03 --canonical-address rdb-03.rdb --directory /var/data/rethinkdb --join rdb.rdb --driver-tls --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
76
+    rdb-proxy:
77
+      image: jlhawn/rethinkdb-tls
78
+      ports:
79
+        - "8080:8080"
80
+      volumes:
81
+        - ./fixtures/rethinkdb:/tls
82
+      networks:
83
+        rdb:
84
+          aliases:
85
+            - rdb-proxy
86
+            - rdb-proxy.rdp
87
+      command: "proxy --bind all --join rdb.rdb --web-tls --web-tls-key /tls/key.pem --web-tls-cert /tls/cert.pem --driver-tls --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
88
+      depends_on:
89
+        - rdb-01
90
+        - rdb-02
91
+        - rdb-03
92
+    client:
93
+      volumes:
94
+        - ./test_output:/test_output
95
+      networks:
96
+        - rdb
97
+      build:
98
+        context: .
99
+        dockerfile: Dockerfile
100
+      links:
101
+        - server:notary-server
102
+      command: buildscripts/testclient.sh
103
+volumes:
104
+    rdb-01-data:
105
+        external: false
106
+    rdb-02-data:
107
+        external: false
108
+    rdb-03-data:
109
+        external: false
110
+networks:
111
+    rdb:
112
+        external: false
0 113
\ No newline at end of file
1 114
new file mode 100644
... ...
@@ -0,0 +1,36 @@
0
+server:
1
+  build: .
2
+  dockerfile: server.Dockerfile
3
+  links:
4
+    - mysql
5
+    - signer
6
+    - signer:notarysigner
7
+  environment:
8
+    - SERVICE_NAME=notary_server
9
+  entrypoint: /usr/bin/env sh
10
+  command: -c "./migrations/migrate.sh && notary-server -config=fixtures/server-config.json"
11
+signer:
12
+  build: .
13
+  dockerfile: signer.Dockerfile
14
+  links:
15
+    - mysql
16
+  environment:
17
+    - SERVICE_NAME=notary_signer
18
+  entrypoint: /usr/bin/env sh
19
+  command: -c "./migrations/migrate.sh && notary-signer -config=fixtures/signer-config.json"
20
+mysql:
21
+  volumes:
22
+    - ./notarymysql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
23
+  image: mariadb:10.1.10
24
+  environment:
25
+    - TERM=dumb
26
+    - MYSQL_ALLOW_EMPTY_PASSWORD="true"
27
+  command: mysqld --innodb_file_per_table
28
+client:
29
+  volumes:
30
+    - ./test_output:/test_output
31
+  build: .
32
+  dockerfile: Dockerfile
33
+  links:
34
+    - server:notary-server
35
+  command: buildscripts/testclient.sh
0 36
new file mode 100644
... ...
@@ -0,0 +1,102 @@
0
+version: "2"
1
+services:
2
+    server:
3
+      build: 
4
+        context: .
5
+        dockerfile: server.Dockerfile
6
+      volumes:
7
+        - ./fixtures/rethinkdb:/tls
8
+      networks:
9
+        - rdb
10
+      links:
11
+        - rdb-proxy:rdb-proxy.rdb
12
+        - signer
13
+      environment:
14
+        - SERVICE_NAME=notary_server
15
+      ports:
16
+        - "8080"
17
+        - "4443:4443"
18
+      entrypoint: /usr/bin/env sh
19
+      command: -c "sh migrations/rethink_migrate.sh && notary-server -config=fixtures/server-config.rethink.json"
20
+      depends_on:
21
+        - rdb-proxy
22
+    signer:
23
+      build: 
24
+        context: .
25
+        dockerfile: signer.Dockerfile
26
+      volumes:
27
+        - ./fixtures/rethinkdb:/tls
28
+      networks:
29
+        rdb:
30
+            aliases:
31
+                - notarysigner
32
+      links:
33
+        - rdb-proxy:rdb-proxy.rdb
34
+      environment:
35
+        - SERVICE_NAME=notary_signer
36
+      entrypoint: /usr/bin/env sh
37
+      command: -c "sh migrations/rethink_migrate.sh && notary-signer -config=fixtures/signer-config.rethink.json"
38
+      depends_on:
39
+        - rdb-proxy
40
+    rdb-01:
41
+      image: jlhawn/rethinkdb-tls
42
+      volumes:
43
+        - ./fixtures/rethinkdb:/tls
44
+        - rdb-01-data:/var/data
45
+      networks:
46
+        rdb:
47
+          aliases:
48
+            - rdb
49
+            - rdb.rdb
50
+            - rdb-01.rdb
51
+      command: "--bind all --no-http-admin --server-name rdb_01 --canonical-address rdb-01.rdb --directory /var/data/rethinkdb --join rdb.rdb --driver-tls --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
52
+    rdb-02:
53
+      image: jlhawn/rethinkdb-tls
54
+      volumes:
55
+        - ./fixtures/rethinkdb:/tls
56
+        - rdb-02-data:/var/data
57
+      networks:
58
+        rdb:
59
+          aliases:
60
+            - rdb
61
+            - rdb.rdb
62
+            - rdb-02.rdb
63
+      command: "--bind all --no-http-admin --server-name rdb_02 --canonical-address rdb-02.rdb --directory /var/data/rethinkdb --join rdb.rdb --driver-tls --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
64
+    rdb-03:
65
+      image: jlhawn/rethinkdb-tls
66
+      volumes:
67
+        - ./fixtures/rethinkdb:/tls
68
+        - rdb-03-data:/var/data
69
+      networks:
70
+        rdb:
71
+          aliases:
72
+            - rdb
73
+            - rdb.rdb
74
+            - rdb-03.rdb
75
+      command: "--bind all --no-http-admin --server-name rdb_03 --canonical-address rdb-03.rdb --directory /var/data/rethinkdb --join rdb.rdb --driver-tls --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
76
+    rdb-proxy:
77
+      image: jlhawn/rethinkdb-tls
78
+      ports:
79
+        - "8080:8080"
80
+      volumes:
81
+        - ./fixtures/rethinkdb:/tls
82
+      networks:
83
+        rdb:
84
+          aliases:
85
+            - rdb-proxy
86
+            - rdb-proxy.rdp
87
+      command: "proxy --bind all --join rdb.rdb --web-tls --web-tls-key /tls/key.pem --web-tls-cert /tls/cert.pem --driver-tls --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
88
+      depends_on:
89
+        - rdb-01
90
+        - rdb-02
91
+        - rdb-03
92
+volumes:
93
+    rdb-01-data:
94
+        external: false
95
+    rdb-02-data:
96
+        external: false
97
+    rdb-03-data:
98
+        external: false
99
+networks:
100
+    rdb:
101
+        external: false
... ...
@@ -10,7 +10,7 @@ server:
10 10
   ports:
11 11
     - "8080"
12 12
     - "4443:4443"
13
-  entrypoint: /bin/bash
13
+  entrypoint: /usr/bin/env sh
14 14
   command: -c "./migrations/migrate.sh && notary-server -config=fixtures/server-config.json"
15 15
 signer:
16 16
   build: .
... ...
@@ -19,7 +19,7 @@ signer:
19 19
     - mysql
20 20
   environment:
21 21
     - SERVICE_NAME=notary_signer
22
-  entrypoint: /bin/bash
22
+  entrypoint: /usr/bin/env sh
23 23
   command: -c "./migrations/migrate.sh && notary-signer -config=fixtures/signer-config.json"
24 24
 mysql:
25 25
   volumes:
... ...
@@ -1,28 +1,25 @@
1
-FROM golang:1.6.0
1
+FROM golang:1.6.1-alpine
2 2
 MAINTAINER David Lawrence "david.lawrence@docker.com"
3 3
 
4
-RUN apt-get update && apt-get install -y \
5
-    libltdl-dev \
6
-    --no-install-recommends \
7
-    && rm -rf /var/lib/apt/lists/*
4
+RUN apk add --update git gcc libc-dev && rm -rf /var/cache/apk/*
8 5
 
9
-EXPOSE 4443
10
-
11
-# Install DB migration tool
6
+# Install SQL DB migration tool
12 7
 RUN go get github.com/mattes/migrate
13 8
 
14 9
 ENV NOTARYPKG github.com/docker/notary
15 10
 
16 11
 # Copy the local repo to the expected go path
17
-COPY . /go/src/github.com/docker/notary
12
+COPY . /go/src/${NOTARYPKG}
18 13
 
19 14
 WORKDIR /go/src/${NOTARYPKG}
20 15
 
16
+EXPOSE 4443
17
+
21 18
 # Install notary-server
22 19
 RUN go install \
23 20
     -tags pkcs11 \
24 21
     -ldflags "-w -X ${NOTARYPKG}/version.GitCommit=`git rev-parse --short HEAD` -X ${NOTARYPKG}/version.NotaryVersion=`cat NOTARY_VERSION`" \
25
-    ${NOTARYPKG}/cmd/notary-server
22
+    ${NOTARYPKG}/cmd/notary-server && apk del git gcc libc-dev
26 23
 
27 24
 ENTRYPOINT [ "notary-server" ]
28 25
 CMD [ "-config=fixtures/server-config-local.json" ]
... ...
@@ -1,30 +1,28 @@
1
-FROM golang:1.6.0
1
+FROM golang:1.6.1-alpine
2 2
 MAINTAINER David Lawrence "david.lawrence@docker.com"
3 3
 
4
-RUN apt-get update && apt-get install -y \
5
-    libltdl-dev \
6
-    --no-install-recommends \
7
-    && rm -rf /var/lib/apt/lists/*
4
+RUN apk add --update git gcc libc-dev && rm -rf /var/cache/apk/*
8 5
 
9
-EXPOSE 4444
10
-
11
-# Install DB migration tool
6
+# Install SQL DB migration tool
12 7
 RUN go get github.com/mattes/migrate
13 8
 
14 9
 ENV NOTARYPKG github.com/docker/notary
15
-ENV NOTARY_SIGNER_DEFAULT_ALIAS="timestamp_1"
16
-ENV NOTARY_SIGNER_TIMESTAMP_1="testpassword"
17 10
 
18 11
 # Copy the local repo to the expected go path
19
-COPY . /go/src/github.com/docker/notary
12
+COPY . /go/src/${NOTARYPKG}
20 13
 
21 14
 WORKDIR /go/src/${NOTARYPKG}
22 15
 
16
+ENV NOTARY_SIGNER_DEFAULT_ALIAS="timestamp_1"
17
+ENV NOTARY_SIGNER_TIMESTAMP_1="testpassword"
18
+
19
+EXPOSE 4444
20
+
23 21
 # Install notary-signer
24 22
 RUN go install \
25 23
     -tags pkcs11 \
26 24
     -ldflags "-w -X ${NOTARYPKG}/version.GitCommit=`git rev-parse --short HEAD` -X ${NOTARYPKG}/version.NotaryVersion=`cat NOTARY_VERSION`" \
27
-    ${NOTARYPKG}/cmd/notary-signer
25
+    ${NOTARYPKG}/cmd/notary-signer && apk del git gcc libc-dev
28 26
 
29 27
 ENTRYPOINT [ "notary-signer" ]
30 28
 CMD [ "-config=fixtures/signer-config-local.json" ]
... ...
@@ -15,14 +15,12 @@ type SimpleFileStore struct {
15 15
 	perms   os.FileMode
16 16
 }
17 17
 
18
-// NewSimpleFileStore creates a directory with 755 permissions
19
-func NewSimpleFileStore(baseDir string, fileExt string) (*SimpleFileStore, error) {
18
+// NewFileStore creates a fully configurable file store
19
+func NewFileStore(baseDir, fileExt string, perms os.FileMode) (*SimpleFileStore, error) {
20 20
 	baseDir = filepath.Clean(baseDir)
21
-
22
-	if err := CreateDirectory(baseDir); err != nil {
21
+	if err := createDirectory(baseDir, perms); err != nil {
23 22
 		return nil, err
24 23
 	}
25
-
26 24
 	if !strings.HasPrefix(fileExt, ".") {
27 25
 		fileExt = "." + fileExt
28 26
 	}
... ...
@@ -30,25 +28,20 @@ func NewSimpleFileStore(baseDir string, fileExt string) (*SimpleFileStore, error
30 30
 	return &SimpleFileStore{
31 31
 		baseDir: baseDir,
32 32
 		fileExt: fileExt,
33
-		perms:   visible,
33
+		perms:   perms,
34 34
 	}, nil
35 35
 }
36 36
 
37
-// NewPrivateSimpleFileStore creates a directory with 700 permissions
38
-func NewPrivateSimpleFileStore(baseDir string, fileExt string) (*SimpleFileStore, error) {
39
-	if err := CreatePrivateDirectory(baseDir); err != nil {
40
-		return nil, err
41
-	}
42
-
43
-	if !strings.HasPrefix(fileExt, ".") {
44
-		fileExt = "." + fileExt
45
-	}
37
+// NewSimpleFileStore is a convenience wrapper to create a world readable,
38
+// owner writeable filestore
39
+func NewSimpleFileStore(baseDir, fileExt string) (*SimpleFileStore, error) {
40
+	return NewFileStore(baseDir, fileExt, visible)
41
+}
46 42
 
47
-	return &SimpleFileStore{
48
-		baseDir: baseDir,
49
-		fileExt: fileExt,
50
-		perms:   private,
51
-	}, nil
43
+// NewPrivateSimpleFileStore is a wrapper to create an owner readable/writeable
44
+// _only_ filestore
45
+func NewPrivateSimpleFileStore(baseDir, fileExt string) (*SimpleFileStore, error) {
46
+	return NewFileStore(baseDir, fileExt, private)
52 47
 }
53 48
 
54 49
 // Add writes data to a file with a given name
... ...
@@ -71,24 +64,6 @@ func (f *SimpleFileStore) Remove(name string) error {
71 71
 	return os.Remove(filePath)
72 72
 }
73 73
 
74
-// RemoveDir removes the directory identified by name
75
-func (f *SimpleFileStore) RemoveDir(name string) error {
76
-	dirPath := filepath.Join(f.baseDir, name)
77
-
78
-	// Check to see if directory exists
79
-	fi, err := os.Stat(dirPath)
80
-	if err != nil {
81
-		return err
82
-	}
83
-
84
-	// Check to see if it is a directory
85
-	if !fi.IsDir() {
86
-		return fmt.Errorf("directory not found: %s", name)
87
-	}
88
-
89
-	return os.RemoveAll(dirPath)
90
-}
91
-
92 74
 // Get returns the data given a file name
93 75
 func (f *SimpleFileStore) Get(name string) ([]byte, error) {
94 76
 	filePath, err := f.GetPath(name)
... ...
@@ -119,12 +94,6 @@ func (f *SimpleFileStore) ListFiles() []string {
119 119
 	return f.list(f.baseDir)
120 120
 }
121 121
 
122
-// ListDir lists all the files inside of a directory identified by a name
123
-func (f *SimpleFileStore) ListDir(name string) []string {
124
-	fullPath := filepath.Join(f.baseDir, name)
125
-	return f.list(fullPath)
126
-}
127
-
128 122
 // list lists all the files in a directory given a full path. Ignores symlinks.
129 123
 func (f *SimpleFileStore) list(path string) []string {
130 124
 	files := make([]string, 0, 0)
... ...
@@ -170,16 +139,6 @@ func (f *SimpleFileStore) BaseDir() string {
170 170
 	return f.baseDir
171 171
 }
172 172
 
173
-// CreateDirectory uses createDirectory to create a chmod 755 Directory
174
-func CreateDirectory(dir string) error {
175
-	return createDirectory(dir, visible)
176
-}
177
-
178
-// CreatePrivateDirectory uses createDirectory to create a chmod 700 Directory
179
-func CreatePrivateDirectory(dir string) error {
180
-	return createDirectory(dir, private)
181
-}
182
-
183 173
 // createDirectory receives a string of the path to a directory.
184 174
 // It does not support passing files, so the caller has to remove
185 175
 // the filename by doing filepath.Dir(full_path_to_file)
... ...
@@ -62,7 +62,7 @@ func NewKeyFileStore(baseDir string, passphraseRetriever passphrase.Retriever) (
62 62
 	return keyStore, nil
63 63
 }
64 64
 
65
-func generateKeyInfoMap(s LimitedFileStore) map[string]KeyInfo {
65
+func generateKeyInfoMap(s Storage) map[string]KeyInfo {
66 66
 	keyInfoMap := make(map[string]KeyInfo)
67 67
 	for _, keyPath := range s.ListFiles() {
68 68
 		d, err := s.Get(keyPath)
... ...
@@ -309,7 +309,7 @@ func KeyInfoFromPEM(pemBytes []byte, filename string) (string, KeyInfo, error) {
309 309
 	return keyID, KeyInfo{Gun: gun, Role: role}, nil
310 310
 }
311 311
 
312
-func addKey(s LimitedFileStore, passphraseRetriever passphrase.Retriever, cachedKeys map[string]*cachedKey, name, role string, privKey data.PrivateKey) error {
312
+func addKey(s Storage, passphraseRetriever passphrase.Retriever, cachedKeys map[string]*cachedKey, name, role string, privKey data.PrivateKey) error {
313 313
 
314 314
 	var (
315 315
 		chosenPassphrase string
... ...
@@ -338,7 +338,7 @@ func addKey(s LimitedFileStore, passphraseRetriever passphrase.Retriever, cached
338 338
 // both in the newer format PEM headers, and also in the legacy filename
339 339
 // format. It returns: the role, whether it was found in the legacy format
340 340
 // (true == legacy), and an error
341
-func getKeyRole(s LimitedFileStore, keyID string) (string, bool, error) {
341
+func getKeyRole(s Storage, keyID string) (string, bool, error) {
342 342
 	name := strings.TrimSpace(strings.TrimSuffix(filepath.Base(keyID), filepath.Ext(keyID)))
343 343
 
344 344
 	for _, file := range s.ListFiles() {
... ...
@@ -361,11 +361,11 @@ func getKeyRole(s LimitedFileStore, keyID string) (string, bool, error) {
361 361
 		}
362 362
 	}
363 363
 
364
-	return "", false, &ErrKeyNotFound{KeyID: keyID}
364
+	return "", false, ErrKeyNotFound{KeyID: keyID}
365 365
 }
366 366
 
367 367
 // GetKey returns the PrivateKey given a KeyID
368
-func getKey(s LimitedFileStore, passphraseRetriever passphrase.Retriever, cachedKeys map[string]*cachedKey, name string) (data.PrivateKey, string, error) {
368
+func getKey(s Storage, passphraseRetriever passphrase.Retriever, cachedKeys map[string]*cachedKey, name string) (data.PrivateKey, string, error) {
369 369
 	cachedKeyEntry, ok := cachedKeys[name]
370 370
 	if ok {
371 371
 		return cachedKeyEntry.key, cachedKeyEntry.alias, nil
... ...
@@ -389,7 +389,7 @@ func getKey(s LimitedFileStore, passphraseRetriever passphrase.Retriever, cached
389 389
 }
390 390
 
391 391
 // RemoveKey removes the key from the keyfilestore
392
-func removeKey(s LimitedFileStore, cachedKeys map[string]*cachedKey, name string) error {
392
+func removeKey(s Storage, cachedKeys map[string]*cachedKey, name string) error {
393 393
 	role, legacy, err := getKeyRole(s, name)
394 394
 	if err != nil {
395 395
 		return err
... ...
@@ -419,7 +419,7 @@ func getSubdir(alias string) string {
419 419
 
420 420
 // Given a key ID, gets the bytes and alias belonging to that key if the key
421 421
 // exists
422
-func getRawKey(s LimitedFileStore, name string) ([]byte, string, error) {
422
+func getRawKey(s Storage, name string) ([]byte, string, error) {
423 423
 	role, legacy, err := getKeyRole(s, name)
424 424
 	if err != nil {
425 425
 		return nil, "", err
... ...
@@ -475,7 +475,7 @@ func GetPasswdDecryptBytes(passphraseRetriever passphrase.Retriever, pemBytes []
475 475
 	return privKey, passwd, nil
476 476
 }
477 477
 
478
-func encryptAndAddKey(s LimitedFileStore, passwd string, cachedKeys map[string]*cachedKey, name, role string, privKey data.PrivateKey) error {
478
+func encryptAndAddKey(s Storage, passwd string, cachedKeys map[string]*cachedKey, name, role string, privKey data.PrivateKey) error {
479 479
 
480 480
 	var (
481 481
 		pemPrivKey []byte
... ...
@@ -43,6 +43,8 @@ type KeyStore interface {
43 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 45
 	AddKey(keyInfo KeyInfo, privKey data.PrivateKey) error
46
+	// Should fail with ErrKeyNotFound if the keystore is operating normally
47
+	// and knows that it does not store the requested key.
46 48
 	GetKey(keyID string) (data.PrivateKey, string, error)
47 49
 	GetKeyInfo(keyID string) (KeyInfo, error)
48 50
 	ListKeys() map[string]KeyInfo
... ...
@@ -5,7 +5,7 @@ import (
5 5
 	"sync"
6 6
 )
7 7
 
8
-// MemoryFileStore is an implementation of LimitedFileStore that keeps
8
+// MemoryFileStore is an implementation of Storage that keeps
9 9
 // the contents in memory.
10 10
 type MemoryFileStore struct {
11 11
 	sync.Mutex
... ...
@@ -17,8 +17,8 @@ var (
17 17
 	ErrPathOutsideStore = errors.New("path outside file store")
18 18
 )
19 19
 
20
-// LimitedFileStore implements the bare bones primitives (no hierarchy)
21
-type LimitedFileStore interface {
20
+// Storage implements the bare bones primitives (no hierarchy)
21
+type Storage interface {
22 22
 	// Add writes a file to the specified location, returning an error if this
23 23
 	// is not possible (reasons may include permissions errors). The path is cleaned
24 24
 	// before being made absolute against the store's base dir.
... ...
@@ -37,16 +37,6 @@ type LimitedFileStore interface {
37 37
 
38 38
 	// ListFiles returns a list of paths relative to the base directory of the
39 39
 	// filestore. Any of these paths must be retrievable via the
40
-	// LimitedFileStore.Get method.
40
+	// Storage.Get method.
41 41
 	ListFiles() []string
42 42
 }
43
-
44
-// FileStore is the interface for full-featured FileStores
45
-type FileStore interface {
46
-	LimitedFileStore
47
-
48
-	RemoveDir(directoryName string) error
49
-	GetPath(fileName string) (string, error)
50
-	ListDir(directoryName string) []string
51
-	BaseDir() string
52
-}
... ...
@@ -15,7 +15,7 @@ type X509FileStore struct {
15 15
 	fileMap        map[CertID]string
16 16
 	fingerprintMap map[CertID]*x509.Certificate
17 17
 	nameMap        map[string][]CertID
18
-	fileStore      FileStore
18
+	fileStore      Storage
19 19
 }
20 20
 
21 21
 // NewX509FileStore returns a new X509FileStore.
... ...
@@ -88,11 +88,7 @@ func (s *X509FileStore) addNamedCert(cert *x509.Certificate) error {
88 88
 	certBytes := CertToPEM(cert)
89 89
 
90 90
 	// Save the file to disk if not already there.
91
-	filePath, err := s.fileStore.GetPath(fileName)
92
-	if err != nil {
93
-		return err
94
-	}
95
-	if _, err := os.Stat(filePath); os.IsNotExist(err) {
91
+	if _, err = s.fileStore.Get(fileName); os.IsNotExist(err) {
96 92
 		if err := s.fileStore.Add(fileName, certBytes); err != nil {
97 93
 			return err
98 94
 		}
... ...
@@ -246,7 +242,7 @@ func (s *X509FileStore) GetCertificatesByCN(cn string) ([]*x509.Certificate, err
246 246
 // as part of the roots list. This never allows the use of system roots, returning
247 247
 // an error if there are no root CAs.
248 248
 func (s *X509FileStore) GetVerifyOptions(dnsName string) (x509.VerifyOptions, error) {
249
-	// If we have no Certificates loaded return error (we don't want to rever to using
249
+	// If we have no Certificates loaded return error (we don't want to revert to using
250 250
 	// system CAs).
251 251
 	if len(s.fingerprintMap) == 0 {
252 252
 		return x509.VerifyOptions{}, errors.New("no root CAs available")
... ...
@@ -188,7 +188,7 @@ func (s *X509MemStore) GetCertificatesByCN(cn string) ([]*x509.Certificate, erro
188 188
 // as part of the roots list. This never allows the use of system roots, returning
189 189
 // an error if there are no root CAs.
190 190
 func (s *X509MemStore) GetVerifyOptions(dnsName string) (x509.VerifyOptions, error) {
191
-	// If we have no Certificates loaded return error (we don't want to rever to using
191
+	// If we have no Certificates loaded return error (we don't want to revert to using
192 192
 	// system CAs).
193 193
 	if len(s.fingerprintMap) == 0 {
194 194
 		return x509.VerifyOptions{}, errors.New("no root CAs available")
... ...
@@ -1,6 +1,7 @@
1 1
 package trustmanager
2 2
 
3 3
 import (
4
+	"bytes"
4 5
 	"crypto/ecdsa"
5 6
 	"crypto/elliptic"
6 7
 	"crypto/rand"
... ...
@@ -57,13 +58,24 @@ func GetCertFromURL(urlStr string) (*x509.Certificate, error) {
57 57
 	return cert, nil
58 58
 }
59 59
 
60
-// CertToPEM is an utility function returns a PEM encoded x509 Certificate
60
+// CertToPEM is a utility function returns a PEM encoded x509 Certificate
61 61
 func CertToPEM(cert *x509.Certificate) []byte {
62 62
 	pemCert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
63 63
 
64 64
 	return pemCert
65 65
 }
66 66
 
67
+// CertChainToPEM is a utility function returns a PEM encoded chain of x509 Certificates, in the order they are passed
68
+func CertChainToPEM(certChain []*x509.Certificate) ([]byte, error) {
69
+	var pemBytes bytes.Buffer
70
+	for _, cert := range certChain {
71
+		if err := pem.Encode(&pemBytes, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}); err != nil {
72
+			return nil, err
73
+		}
74
+	}
75
+	return pemBytes.Bytes(), nil
76
+}
77
+
67 78
 // LoadCertFromPEM returns the first certificate found in a bunch of bytes or error
68 79
 // if nothing is found. Taken from https://golang.org/src/crypto/x509/cert_pool.go#L85.
69 80
 func LoadCertFromPEM(pemBytes []byte) (*x509.Certificate, error) {
... ...
@@ -224,20 +236,19 @@ func ParsePEMPrivateKey(pemBytes []byte, passphrase string) (data.PrivateKey, er
224 224
 		return nil, errors.New("no valid private key found")
225 225
 	}
226 226
 
227
-	switch block.Type {
228
-	case "RSA PRIVATE KEY":
229
-		var privKeyBytes []byte
230
-		var err error
231
-
232
-		if x509.IsEncryptedPEMBlock(block) {
233
-			privKeyBytes, err = x509.DecryptPEMBlock(block, []byte(passphrase))
234
-			if err != nil {
235
-				return nil, errors.New("could not decrypt private key")
236
-			}
237
-		} else {
238
-			privKeyBytes = block.Bytes
227
+	var privKeyBytes []byte
228
+	var err error
229
+	if x509.IsEncryptedPEMBlock(block) {
230
+		privKeyBytes, err = x509.DecryptPEMBlock(block, []byte(passphrase))
231
+		if err != nil {
232
+			return nil, errors.New("could not decrypt private key")
239 233
 		}
234
+	} else {
235
+		privKeyBytes = block.Bytes
236
+	}
240 237
 
238
+	switch block.Type {
239
+	case "RSA PRIVATE KEY":
241 240
 		rsaPrivKey, err := x509.ParsePKCS1PrivateKey(privKeyBytes)
242 241
 		if err != nil {
243 242
 			return nil, fmt.Errorf("could not parse DER encoded key: %v", err)
... ...
@@ -250,18 +261,6 @@ func ParsePEMPrivateKey(pemBytes []byte, passphrase string) (data.PrivateKey, er
250 250
 
251 251
 		return tufRSAPrivateKey, nil
252 252
 	case "EC PRIVATE KEY":
253
-		var privKeyBytes []byte
254
-		var err error
255
-
256
-		if x509.IsEncryptedPEMBlock(block) {
257
-			privKeyBytes, err = x509.DecryptPEMBlock(block, []byte(passphrase))
258
-			if err != nil {
259
-				return nil, errors.New("could not decrypt private key")
260
-			}
261
-		} else {
262
-			privKeyBytes = block.Bytes
263
-		}
264
-
265 253
 		ecdsaPrivKey, err := x509.ParseECPrivateKey(privKeyBytes)
266 254
 		if err != nil {
267 255
 			return nil, fmt.Errorf("could not parse DER encoded private key: %v", err)
... ...
@@ -277,18 +276,6 @@ func ParsePEMPrivateKey(pemBytes []byte, passphrase string) (data.PrivateKey, er
277 277
 		// We serialize ED25519 keys by concatenating the private key
278 278
 		// to the public key and encoding with PEM. See the
279 279
 		// ED25519ToPrivateKey function.
280
-		var privKeyBytes []byte
281
-		var err error
282
-
283
-		if x509.IsEncryptedPEMBlock(block) {
284
-			privKeyBytes, err = x509.DecryptPEMBlock(block, []byte(passphrase))
285
-			if err != nil {
286
-				return nil, errors.New("could not decrypt private key")
287
-			}
288
-		} else {
289
-			privKeyBytes = block.Bytes
290
-		}
291
-
292 280
 		tufECDSAPrivateKey, err := ED25519ToPrivateKey(privKeyBytes)
293 281
 		if err != nil {
294 282
 			return nil, fmt.Errorf("could not convert ecdsa.PrivateKey to data.PrivateKey: %v", err)
... ...
@@ -544,12 +531,34 @@ func CertToKey(cert *x509.Certificate) data.PublicKey {
544 544
 	}
545 545
 }
546 546
 
547
-// CertsToKeys transforms each of the input certificates into it's corresponding
547
+// CertsToKeys transforms each of the input certificate chains into its corresponding
548 548
 // PublicKey
549
-func CertsToKeys(certs []*x509.Certificate) map[string]data.PublicKey {
549
+func CertsToKeys(leafCerts []*x509.Certificate, intCerts map[string][]*x509.Certificate) map[string]data.PublicKey {
550 550
 	keys := make(map[string]data.PublicKey)
551
-	for _, cert := range certs {
552
-		newKey := CertToKey(cert)
551
+	for _, leafCert := range leafCerts {
552
+		certBundle := []*x509.Certificate{leafCert}
553
+		certID, err := FingerprintCert(leafCert)
554
+		if err != nil {
555
+			continue
556
+		}
557
+		if intCertsForLeafs, ok := intCerts[certID]; ok {
558
+			certBundle = append(certBundle, intCertsForLeafs...)
559
+		}
560
+		certChainPEM, err := CertChainToPEM(certBundle)
561
+		if err != nil {
562
+			continue
563
+		}
564
+		var newKey data.PublicKey
565
+		// Use the leaf cert's public key algorithm for typing
566
+		switch leafCert.PublicKeyAlgorithm {
567
+		case x509.RSA:
568
+			newKey = data.NewRSAx509PublicKey(certChainPEM)
569
+		case x509.ECDSA:
570
+			newKey = data.NewECDSAx509PublicKey(certChainPEM)
571
+		default:
572
+			logrus.Debugf("Unknown key type parsed from certificate: %v", leafCert.PublicKeyAlgorithm)
573
+			continue
574
+		}
553 575
 		keys[newKey.ID()] = newKey
554 576
 	}
555 577
 	return keys
... ...
@@ -581,6 +590,7 @@ func NewCertificate(gun string, startTime, endTime time.Time) (*x509.Certificate
581 581
 // X509PublicKeyID returns a public key ID as a string, given a
582 582
 // data.PublicKey that contains an X509 Certificate
583 583
 func X509PublicKeyID(certPubKey data.PublicKey) (string, error) {
584
+	// Note that this only loads the first certificate from the public key
584 585
 	cert, err := LoadCertFromPEM(certPubKey.Public())
585 586
 	if err != nil {
586 587
 		return "", err
... ...
@@ -25,14 +25,22 @@ import (
25 25
 )
26 26
 
27 27
 const (
28
-	USER_PIN    = "123456"
29
-	SO_USER_PIN = "010203040506070801020304050607080102030405060708"
30
-	numSlots    = 4 // number of slots in the yubikey
31
-
32
-	KeymodeNone      = 0
33
-	KeymodeTouch     = 1 // touch enabled
34
-	KeymodePinOnce   = 2 // require pin entry once
35
-	KeymodePinAlways = 4 // require pin entry all the time
28
+	// UserPin is the user pin of a yubikey (in PIV parlance, is the PIN)
29
+	UserPin = "123456"
30
+	// SOUserPin is the "Security Officer" user pin - this is the PIV management
31
+	// (MGM) key, which is different than the admin pin of the Yubikey PGP interface
32
+	// (which in PIV parlance is the PUK, and defaults to 12345678)
33
+	SOUserPin = "010203040506070801020304050607080102030405060708"
34
+	numSlots  = 4 // number of slots in the yubikey
35
+
36
+	// KeymodeNone means that no touch or PIN is required to sign with the yubikey
37
+	KeymodeNone = 0
38
+	// KeymodeTouch means that only touch is required to sign with the yubikey
39
+	KeymodeTouch = 1
40
+	// KeymodePinOnce means that the pin entry is required once the first time to sign with the yubikey
41
+	KeymodePinOnce = 2
42
+	// KeymodePinAlways means that pin entry is required every time to sign with the yubikey
43
+	KeymodePinAlways = 4
36 44
 
37 45
 	// the key size, when importing a key into yubikey, MUST be 32 bytes
38 46
 	ecdsaPrivateKeySize = 32
... ...
@@ -95,6 +103,8 @@ func init() {
95 95
 	}
96 96
 }
97 97
 
98
+// ErrBackupFailed is returned when a YubiStore fails to back up a key that
99
+// is added
98 100
 type ErrBackupFailed struct {
99 101
 	err string
100 102
 }
... ...
@@ -103,6 +113,17 @@ func (err ErrBackupFailed) Error() string {
103 103
 	return fmt.Sprintf("Failed to backup private key to: %s", err.err)
104 104
 }
105 105
 
106
+// An error indicating that the HSM is not present (as opposed to failing),
107
+// i.e. that we can confidently claim that the key is not stored in the HSM
108
+// without notifying the user about a missing or failing HSM.
109
+type errHSMNotPresent struct {
110
+	err string
111
+}
112
+
113
+func (err errHSMNotPresent) Error() string {
114
+	return err.err
115
+}
116
+
106 117
 type yubiSlot struct {
107 118
 	role   string
108 119
 	slotID []byte
... ...
@@ -116,10 +137,13 @@ type YubiPrivateKey struct {
116 116
 	libLoader     pkcs11LibLoader
117 117
 }
118 118
 
119
-type YubikeySigner struct {
119
+// YubiKeySigner wraps a YubiPrivateKey and implements the crypto.Signer interface
120
+type yubikeySigner struct {
120 121
 	YubiPrivateKey
121 122
 }
122 123
 
124
+// NewYubiPrivateKey returns a YubiPrivateKey, which implements the data.PrivateKey
125
+// interface except that the private material is inacessible
123 126
 func NewYubiPrivateKey(slot []byte, pubKey data.ECDSAPublicKey,
124 127
 	passRetriever passphrase.Retriever) *YubiPrivateKey {
125 128
 
... ...
@@ -131,7 +155,8 @@ func NewYubiPrivateKey(slot []byte, pubKey data.ECDSAPublicKey,
131 131
 	}
132 132
 }
133 133
 
134
-func (ys *YubikeySigner) Public() crypto.PublicKey {
134
+// Public is a required method of the crypto.Signer interface
135
+func (ys *yubikeySigner) Public() crypto.PublicKey {
135 136
 	publicKey, err := x509.ParsePKIXPublicKey(ys.YubiPrivateKey.Public())
136 137
 	if err != nil {
137 138
 		return nil
... ...
@@ -147,7 +172,7 @@ func (y *YubiPrivateKey) setLibLoader(loader pkcs11LibLoader) {
147 147
 // CryptoSigner returns a crypto.Signer tha wraps the YubiPrivateKey. Needed for
148 148
 // Certificate generation only
149 149
 func (y *YubiPrivateKey) CryptoSigner() crypto.Signer {
150
-	return &YubikeySigner{YubiPrivateKey: *y}
150
+	return &yubikeySigner{YubiPrivateKey: *y}
151 151
 }
152 152
 
153 153
 // Private is not implemented in hardware  keys
... ...
@@ -157,10 +182,14 @@ func (y *YubiPrivateKey) Private() []byte {
157 157
 	return nil
158 158
 }
159 159
 
160
+// SignatureAlgorithm returns which algorithm this key uses to sign - currently
161
+// hardcoded to ECDSA
160 162
 func (y YubiPrivateKey) SignatureAlgorithm() data.SigAlgorithm {
161 163
 	return data.ECDSASignature
162 164
 }
163 165
 
166
+// Sign is a required method of the crypto.Signer interface and the data.PrivateKey
167
+// interface
164 168
 func (y *YubiPrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) ([]byte, error) {
165 169
 	ctx, session, err := SetupHSMEnv(pkcs11Lib, y.libLoader)
166 170
 	if err != nil {
... ...
@@ -204,7 +233,7 @@ func addECDSAKey(
204 204
 ) error {
205 205
 	logrus.Debugf("Attempting to add key to yubikey with ID: %s", privKey.ID())
206 206
 
207
-	err := login(ctx, session, passRetriever, pkcs11.CKU_SO, SO_USER_PIN)
207
+	err := login(ctx, session, passRetriever, pkcs11.CKU_SO, SOUserPin)
208 208
 	if err != nil {
209 209
 		return err
210 210
 	}
... ...
@@ -317,7 +346,7 @@ func getECDSAKey(ctx IPKCS11Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byt
317 317
 
318 318
 // Sign returns a signature for a given signature request
319 319
 func sign(ctx IPKCS11Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byte, passRetriever passphrase.Retriever, payload []byte) ([]byte, error) {
320
-	err := login(ctx, session, passRetriever, pkcs11.CKU_USER, USER_PIN)
320
+	err := login(ctx, session, passRetriever, pkcs11.CKU_USER, UserPin)
321 321
 	if err != nil {
322 322
 		return nil, fmt.Errorf("error logging in: %v", err)
323 323
 	}
... ...
@@ -376,7 +405,7 @@ func sign(ctx IPKCS11Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byte, pass
376 376
 }
377 377
 
378 378
 func yubiRemoveKey(ctx IPKCS11Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byte, passRetriever passphrase.Retriever, keyID string) error {
379
-	err := login(ctx, session, passRetriever, pkcs11.CKU_SO, SO_USER_PIN)
379
+	err := login(ctx, session, passRetriever, pkcs11.CKU_SO, SOUserPin)
380 380
 	if err != nil {
381 381
 		return err
382 382
 	}
... ...
@@ -584,20 +613,20 @@ func getNextEmptySlot(ctx IPKCS11Ctx, session pkcs11.SessionHandle) ([]byte, err
584 584
 	return nil, errors.New("Yubikey has no available slots.")
585 585
 }
586 586
 
587
-// YubiKeyStore is a KeyStore for private keys inside a Yubikey
588
-type YubiKeyStore struct {
587
+// YubiStore is a KeyStore for private keys inside a Yubikey
588
+type YubiStore struct {
589 589
 	passRetriever passphrase.Retriever
590 590
 	keys          map[string]yubiSlot
591 591
 	backupStore   trustmanager.KeyStore
592 592
 	libLoader     pkcs11LibLoader
593 593
 }
594 594
 
595
-// NewYubiKeyStore returns a YubiKeyStore, given a backup key store to write any
595
+// NewYubiStore returns a YubiStore, given a backup key store to write any
596 596
 // generated keys to (usually a KeyFileStore)
597
-func NewYubiKeyStore(backupStore trustmanager.KeyStore, passphraseRetriever passphrase.Retriever) (
598
-	*YubiKeyStore, error) {
597
+func NewYubiStore(backupStore trustmanager.KeyStore, passphraseRetriever passphrase.Retriever) (
598
+	*YubiStore, error) {
599 599
 
600
-	s := &YubiKeyStore{
600
+	s := &YubiStore{
601 601
 		passRetriever: passphraseRetriever,
602 602
 		keys:          make(map[string]yubiSlot),
603 603
 		backupStore:   backupStore,
... ...
@@ -609,15 +638,16 @@ func NewYubiKeyStore(backupStore trustmanager.KeyStore, passphraseRetriever pass
609 609
 
610 610
 // Name returns a user friendly name for the location this store
611 611
 // keeps its data
612
-func (s YubiKeyStore) Name() string {
612
+func (s YubiStore) Name() string {
613 613
 	return "yubikey"
614 614
 }
615 615
 
616
-func (s *YubiKeyStore) setLibLoader(loader pkcs11LibLoader) {
616
+func (s *YubiStore) setLibLoader(loader pkcs11LibLoader) {
617 617
 	s.libLoader = loader
618 618
 }
619 619
 
620
-func (s *YubiKeyStore) ListKeys() map[string]trustmanager.KeyInfo {
620
+// ListKeys returns a list of keys in the yubikey store
621
+func (s *YubiStore) ListKeys() map[string]trustmanager.KeyInfo {
621 622
 	if len(s.keys) > 0 {
622 623
 		return buildKeyMap(s.keys)
623 624
 	}
... ...
@@ -639,7 +669,7 @@ func (s *YubiKeyStore) ListKeys() map[string]trustmanager.KeyInfo {
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(keyInfo trustmanager.KeyInfo, privKey data.PrivateKey) error {
642
+func (s *YubiStore) AddKey(keyInfo trustmanager.KeyInfo, privKey data.PrivateKey) error {
643 643
 	added, err := s.addKey(privKey.ID(), keyInfo.Role, privKey)
644 644
 	if err != nil {
645 645
 		return err
... ...
@@ -656,7 +686,7 @@ func (s *YubiKeyStore) AddKey(keyInfo trustmanager.KeyInfo, privKey data.Private
656 656
 
657 657
 // Only add if we haven't seen the key already.  Return whether the key was
658 658
 // added.
659
-func (s *YubiKeyStore) addKey(keyID, role string, privKey data.PrivateKey) (
659
+func (s *YubiStore) addKey(keyID, role string, privKey data.PrivateKey) (
660 660
 	bool, error) {
661 661
 
662 662
 	// We only allow adding root keys for now
... ...
@@ -702,17 +732,20 @@ func (s *YubiKeyStore) addKey(keyID, role string, privKey data.PrivateKey) (
702 702
 
703 703
 // GetKey retrieves a key from the Yubikey only (it does not look inside the
704 704
 // backup store)
705
-func (s *YubiKeyStore) GetKey(keyID string) (data.PrivateKey, string, error) {
705
+func (s *YubiStore) GetKey(keyID string) (data.PrivateKey, string, error) {
706 706
 	ctx, session, err := SetupHSMEnv(pkcs11Lib, s.libLoader)
707 707
 	if err != nil {
708 708
 		logrus.Debugf("Failed to initialize PKCS11 environment: %s", err.Error())
709
+		if _, ok := err.(errHSMNotPresent); ok {
710
+			err = trustmanager.ErrKeyNotFound{KeyID: keyID}
711
+		}
709 712
 		return nil, "", err
710 713
 	}
711 714
 	defer cleanup(ctx, session)
712 715
 
713 716
 	key, ok := s.keys[keyID]
714 717
 	if !ok {
715
-		return nil, "", errors.New("no matching keys found inside of yubikey")
718
+		return nil, "", trustmanager.ErrKeyNotFound{KeyID: keyID}
716 719
 	}
717 720
 
718 721
 	pubKey, alias, err := getECDSAKey(ctx, session, key.slotID)
... ...
@@ -734,7 +767,7 @@ func (s *YubiKeyStore) GetKey(keyID string) (data.PrivateKey, string, error) {
734 734
 
735 735
 // RemoveKey deletes a key from the Yubikey only (it does not remove it from the
736 736
 // backup store)
737
-func (s *YubiKeyStore) RemoveKey(keyID string) error {
737
+func (s *YubiStore) RemoveKey(keyID string) error {
738 738
 	ctx, session, err := SetupHSMEnv(pkcs11Lib, s.libLoader)
739 739
 	if err != nil {
740 740
 		logrus.Debugf("Failed to initialize PKCS11 environment: %s", err.Error())
... ...
@@ -757,13 +790,13 @@ func (s *YubiKeyStore) RemoveKey(keyID string) error {
757 757
 }
758 758
 
759 759
 // ExportKey doesn't work, because you can't export data from a Yubikey
760
-func (s *YubiKeyStore) ExportKey(keyID string) ([]byte, error) {
761
-	logrus.Debugf("Attempting to export: %s key inside of YubiKeyStore", keyID)
760
+func (s *YubiStore) ExportKey(keyID string) ([]byte, error) {
761
+	logrus.Debugf("Attempting to export: %s key inside of YubiStore", keyID)
762 762
 	return nil, errors.New("Keys cannot be exported from a Yubikey.")
763 763
 }
764 764
 
765
-// Not yet implemented
766
-func (s *YubiKeyStore) GetKeyInfo(keyID string) (trustmanager.KeyInfo, error) {
765
+// GetKeyInfo is not yet implemented
766
+func (s *YubiStore) GetKeyInfo(keyID string) (trustmanager.KeyInfo, error) {
767 767
 	return trustmanager.KeyInfo{}, fmt.Errorf("Not yet implemented")
768 768
 }
769 769
 
... ...
@@ -788,7 +821,7 @@ func SetupHSMEnv(libraryPath string, libLoader pkcs11LibLoader) (
788 788
 	IPKCS11Ctx, pkcs11.SessionHandle, error) {
789 789
 
790 790
 	if libraryPath == "" {
791
-		return nil, 0, fmt.Errorf("no library found.")
791
+		return nil, 0, errHSMNotPresent{err: "no library found"}
792 792
 	}
793 793
 	p := libLoader(libraryPath)
794 794
 
... ...
@@ -798,8 +831,7 @@ func SetupHSMEnv(libraryPath string, libLoader pkcs11LibLoader) (
798 798
 
799 799
 	if err := p.Initialize(); err != nil {
800 800
 		defer finalizeAndDestroy(p)
801
-		return nil, 0, fmt.Errorf(
802
-			"found library %s, but initialize error %s", libraryPath, err.Error())
801
+		return nil, 0, fmt.Errorf("found library %s, but initialize error %s", libraryPath, err.Error())
803 802
 	}
804 803
 
805 804
 	slots, err := p.GetSlotList(true)
... ...
@@ -829,8 +861,8 @@ func SetupHSMEnv(libraryPath string, libLoader pkcs11LibLoader) (
829 829
 	return p, session, nil
830 830
 }
831 831
 
832
-// YubikeyAccessible returns true if a Yubikey can be accessed
833
-func YubikeyAccessible() bool {
832
+// IsAccessible returns true if a Yubikey can be accessed
833
+func IsAccessible() bool {
834 834
 	if pkcs11Lib == "" {
835 835
 		return false
836 836
 	}
837 837
new file mode 100644
... ...
@@ -0,0 +1,348 @@
0
+package trustpinning
1
+
2
+import (
3
+	"crypto/x509"
4
+	"errors"
5
+	"fmt"
6
+	"strings"
7
+	"time"
8
+
9
+	"github.com/Sirupsen/logrus"
10
+	"github.com/docker/notary/trustmanager"
11
+	"github.com/docker/notary/tuf/data"
12
+	"github.com/docker/notary/tuf/signed"
13
+)
14
+
15
+// ErrValidationFail is returned when there is no valid trusted certificates
16
+// being served inside of the roots.json
17
+type ErrValidationFail struct {
18
+	Reason string
19
+}
20
+
21
+// ErrValidationFail is returned when there is no valid trusted certificates
22
+// being served inside of the roots.json
23
+func (err ErrValidationFail) Error() string {
24
+	return fmt.Sprintf("could not validate the path to a trusted root: %s", err.Reason)
25
+}
26
+
27
+// ErrRootRotationFail is returned when we fail to do a full root key rotation
28
+// by either failing to add the new root certificate, or delete the old ones
29
+type ErrRootRotationFail struct {
30
+	Reason string
31
+}
32
+
33
+// ErrRootRotationFail is returned when we fail to do a full root key rotation
34
+// by either failing to add the new root certificate, or delete the old ones
35
+func (err ErrRootRotationFail) Error() string {
36
+	return fmt.Sprintf("could not rotate trust to a new trusted root: %s", err.Reason)
37
+}
38
+
39
+func prettyFormatCertIDs(certs []*x509.Certificate) string {
40
+	ids := make([]string, 0, len(certs))
41
+	for _, cert := range certs {
42
+		id, err := trustmanager.FingerprintCert(cert)
43
+		if err != nil {
44
+			id = fmt.Sprintf("[Error %s]", err)
45
+		}
46
+		ids = append(ids, id)
47
+	}
48
+	return strings.Join(ids, ", ")
49
+}
50
+
51
+/*
52
+ValidateRoot receives a new root, validates its correctness and attempts to
53
+do root key rotation if needed.
54
+
55
+First we list the current trusted certificates we have for a particular GUN. If
56
+that list is non-empty means that we've already seen this repository before, and
57
+have a list of trusted certificates for it. In this case, we use this list of
58
+certificates to attempt to validate this root file.
59
+
60
+If the previous validation succeeds, we check the integrity of the root by
61
+making sure that it is validated by itself. This means that we will attempt to
62
+validate the root data with the certificates that are included in the root keys
63
+themselves.
64
+
65
+However, if we do not have any current trusted certificates for this GUN, we
66
+check if there are any pinned certificates specified in the trust_pinning section
67
+of the notary client config.  If this section specifies a Certs section with this
68
+GUN, we attempt to validate that the certificates present in the downloaded root
69
+file match the pinned ID.
70
+
71
+If the Certs section is empty for this GUN, we check if the trust_pinning
72
+section specifies a CA section specified in the config for this GUN.  If so, we check
73
+that the specified CA is valid and has signed a certificate included in the downloaded
74
+root file.  The specified CA can be a prefix for this GUN.
75
+
76
+If both the Certs and CA configs do not match this GUN, we fall back to the TOFU
77
+section in the config: if true, we trust certificates specified in the root for
78
+this GUN. If later we see a different certificate for that certificate, we return
79
+an ErrValidationFailed error.
80
+
81
+Note that since we only allow trust data to be downloaded over an HTTPS channel
82
+we are using the current public PKI to validate the first download of the certificate
83
+adding an extra layer of security over the normal (SSH style) trust model.
84
+We shall call this: TOFUS.
85
+
86
+Validation failure at any step will result in an ErrValidationFailed error.
87
+*/
88
+func ValidateRoot(certStore trustmanager.X509Store, root *data.Signed, gun string, trustPinning TrustPinConfig) error {
89
+	logrus.Debugf("entered ValidateRoot with dns: %s", gun)
90
+	signedRoot, err := data.RootFromSigned(root)
91
+	if err != nil {
92
+		return err
93
+	}
94
+
95
+	rootRole, err := signedRoot.BuildBaseRole(data.CanonicalRootRole)
96
+	if err != nil {
97
+		return err
98
+	}
99
+
100
+	// Retrieve all the leaf and intermediate certificates in root for which the CN matches the GUN
101
+	allLeafCerts, allIntCerts := parseAllCerts(signedRoot)
102
+	certsFromRoot, err := validRootLeafCerts(allLeafCerts, gun)
103
+	if err != nil {
104
+		logrus.Debugf("error retrieving valid leaf certificates for: %s, %v", gun, err)
105
+		return &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"}
106
+	}
107
+
108
+	// Retrieve all the trusted certificates that match this gun
109
+	trustedCerts, err := certStore.GetCertificatesByCN(gun)
110
+	if err != nil {
111
+		// If the error that we get back is different than ErrNoCertificatesFound
112
+		// we couldn't check if there are any certificates with this CN already
113
+		// trusted. Let's take the conservative approach and return a failed validation
114
+		if _, ok := err.(*trustmanager.ErrNoCertificatesFound); !ok {
115
+			logrus.Debugf("error retrieving trusted certificates for: %s, %v", gun, err)
116
+			return &ErrValidationFail{Reason: "unable to retrieve trusted certificates"}
117
+		}
118
+	}
119
+	// If we have certificates that match this specific GUN, let's make sure to
120
+	// use them first to validate that this new root is valid.
121
+	if len(trustedCerts) != 0 {
122
+		logrus.Debugf("found %d valid root certificates for %s: %s", len(trustedCerts), gun,
123
+			prettyFormatCertIDs(trustedCerts))
124
+		err = signed.VerifySignatures(
125
+			root, data.BaseRole{Keys: trustmanager.CertsToKeys(trustedCerts, allIntCerts), Threshold: 1})
126
+		if err != nil {
127
+			logrus.Debugf("failed to verify TUF data for: %s, %v", gun, err)
128
+			return &ErrValidationFail{Reason: "failed to validate data with current trusted certificates"}
129
+		}
130
+	} else {
131
+		logrus.Debugf("found no currently valid root certificates for %s, using trust_pinning config to bootstrap trust", gun)
132
+		trustPinCheckFunc, err := NewTrustPinChecker(trustPinning, gun)
133
+		if err != nil {
134
+			return &ErrValidationFail{Reason: err.Error()}
135
+		}
136
+
137
+		validPinnedCerts := []*x509.Certificate{}
138
+		for _, cert := range certsFromRoot {
139
+			certID, err := trustmanager.FingerprintCert(cert)
140
+			if err != nil {
141
+				continue
142
+			}
143
+			if ok := trustPinCheckFunc(cert, allIntCerts[certID]); !ok {
144
+				continue
145
+			}
146
+			validPinnedCerts = append(validPinnedCerts, cert)
147
+		}
148
+		if len(validPinnedCerts) == 0 {
149
+			return &ErrValidationFail{Reason: "unable to match any certificates to trust_pinning config"}
150
+		}
151
+		certsFromRoot = validPinnedCerts
152
+	}
153
+
154
+	// Validate the integrity of the new root (does it have valid signatures)
155
+	// Note that certsFromRoot is guaranteed to be unchanged only if we had prior cert data for this GUN or enabled TOFUS
156
+	// If we attempted to pin a certain certificate or CA, certsFromRoot could have been pruned accordingly
157
+	err = signed.VerifySignatures(root, data.BaseRole{
158
+		Keys: trustmanager.CertsToKeys(certsFromRoot, allIntCerts), Threshold: rootRole.Threshold})
159
+	if err != nil {
160
+		logrus.Debugf("failed to verify TUF data for: %s, %v", gun, err)
161
+		return &ErrValidationFail{Reason: "failed to validate integrity of roots"}
162
+	}
163
+
164
+	// Getting here means:
165
+	// A) we had trusted certificates and both the old and new validated this root.
166
+	// or
167
+	// B) we had no trusted certificates but the new set of certificates has integrity (self-signed).
168
+	logrus.Debugf("entering root certificate rotation for: %s", gun)
169
+
170
+	// Do root certificate rotation: we trust only the certs present in the new root
171
+	// First we add all the new certificates (even if they already exist)
172
+	for _, cert := range certsFromRoot {
173
+		err := certStore.AddCert(cert)
174
+		if err != nil {
175
+			// If the error is already exists we don't fail the rotation
176
+			if _, ok := err.(*trustmanager.ErrCertExists); ok {
177
+				logrus.Debugf("ignoring certificate addition to: %s", gun)
178
+				continue
179
+			}
180
+			logrus.Debugf("error adding new trusted certificate for: %s, %v", gun, err)
181
+		}
182
+	}
183
+
184
+	// Now we delete old certificates that aren't present in the new root
185
+	oldCertsToRemove, err := certsToRemove(trustedCerts, certsFromRoot)
186
+	if err != nil {
187
+		logrus.Debugf("inconsistency when removing old certificates: %v", err)
188
+		return err
189
+	}
190
+	for certID, cert := range oldCertsToRemove {
191
+		logrus.Debugf("removing certificate with certID: %s", certID)
192
+		err = certStore.RemoveCert(cert)
193
+		if err != nil {
194
+			logrus.Debugf("failed to remove trusted certificate with keyID: %s, %v", certID, err)
195
+			return &ErrRootRotationFail{Reason: "failed to rotate root keys"}
196
+		}
197
+	}
198
+
199
+	logrus.Debugf("Root validation succeeded for %s", gun)
200
+	return nil
201
+}
202
+
203
+// validRootLeafCerts returns a list of non-expired, non-sha1 certificates
204
+// found in root whose Common-Names match the provided GUN. Note that this
205
+// "validity" alone does not imply any measure of trust.
206
+func validRootLeafCerts(allLeafCerts map[string]*x509.Certificate, gun string) ([]*x509.Certificate, error) {
207
+	var validLeafCerts []*x509.Certificate
208
+
209
+	// Go through every leaf certificate and check that the CN matches the gun
210
+	for _, cert := range allLeafCerts {
211
+		// Validate that this leaf certificate has a CN that matches the exact gun
212
+		if cert.Subject.CommonName != gun {
213
+			logrus.Debugf("error leaf certificate CN: %s doesn't match the given GUN: %s",
214
+				cert.Subject.CommonName, gun)
215
+			continue
216
+		}
217
+		// Make sure the certificate is not expired
218
+		if time.Now().After(cert.NotAfter) {
219
+			logrus.Debugf("error leaf certificate is expired")
220
+			continue
221
+		}
222
+
223
+		// We don't allow root certificates that use SHA1
224
+		if cert.SignatureAlgorithm == x509.SHA1WithRSA ||
225
+			cert.SignatureAlgorithm == x509.DSAWithSHA1 ||
226
+			cert.SignatureAlgorithm == x509.ECDSAWithSHA1 {
227
+
228
+			logrus.Debugf("error certificate uses deprecated hashing algorithm (SHA1)")
229
+			continue
230
+		}
231
+
232
+		validLeafCerts = append(validLeafCerts, cert)
233
+	}
234
+
235
+	if len(validLeafCerts) < 1 {
236
+		logrus.Debugf("didn't find any valid leaf certificates for %s", gun)
237
+		return nil, errors.New("no valid leaf certificates found in any of the root keys")
238
+	}
239
+
240
+	logrus.Debugf("found %d valid leaf certificates for %s: %s", len(validLeafCerts), gun,
241
+		prettyFormatCertIDs(validLeafCerts))
242
+	return validLeafCerts, nil
243
+}
244
+
245
+// parseAllCerts returns two maps, one with all of the leafCertificates and one
246
+// with all the intermediate certificates found in signedRoot
247
+func parseAllCerts(signedRoot *data.SignedRoot) (map[string]*x509.Certificate, map[string][]*x509.Certificate) {
248
+	leafCerts := make(map[string]*x509.Certificate)
249
+	intCerts := make(map[string][]*x509.Certificate)
250
+
251
+	// Before we loop through all root keys available, make sure any exist
252
+	rootRoles, ok := signedRoot.Signed.Roles["root"]
253
+	if !ok {
254
+		logrus.Debugf("tried to parse certificates from invalid root signed data")
255
+		return nil, nil
256
+	}
257
+
258
+	logrus.Debugf("found the following root keys: %v", rootRoles.KeyIDs)
259
+	// Iterate over every keyID for the root role inside of roots.json
260
+	for _, keyID := range rootRoles.KeyIDs {
261
+		// check that the key exists in the signed root keys map
262
+		key, ok := signedRoot.Signed.Keys[keyID]
263
+		if !ok {
264
+			logrus.Debugf("error while getting data for keyID: %s", keyID)
265
+			continue
266
+		}
267
+
268
+		// Decode all the x509 certificates that were bundled with this
269
+		// Specific root key
270
+		decodedCerts, err := trustmanager.LoadCertBundleFromPEM(key.Public())
271
+		if err != nil {
272
+			logrus.Debugf("error while parsing root certificate with keyID: %s, %v", keyID, err)
273
+			continue
274
+		}
275
+
276
+		// Get all non-CA certificates in the decoded certificates
277
+		leafCertList := trustmanager.GetLeafCerts(decodedCerts)
278
+
279
+		// If we got no leaf certificates or we got more than one, fail
280
+		if len(leafCertList) != 1 {
281
+			logrus.Debugf("invalid chain due to leaf certificate missing or too many leaf certificates for keyID: %s", keyID)
282
+			continue
283
+		}
284
+		// If we found a leaf certificate, assert that the cert bundle started with a leaf
285
+		if decodedCerts[0].IsCA {
286
+			logrus.Debugf("invalid chain due to leaf certificate not being first certificate for keyID: %s", keyID)
287
+			continue
288
+		}
289
+
290
+		// Get the ID of the leaf certificate
291
+		leafCert := leafCertList[0]
292
+		leafID, err := trustmanager.FingerprintCert(leafCert)
293
+		if err != nil {
294
+			logrus.Debugf("error while fingerprinting root certificate with keyID: %s, %v", keyID, err)
295
+			continue
296
+		}
297
+
298
+		// Store the leaf cert in the map
299
+		leafCerts[leafID] = leafCert
300
+
301
+		// Get all the remainder certificates marked as a CA to be used as intermediates
302
+		intermediateCerts := trustmanager.GetIntermediateCerts(decodedCerts)
303
+		intCerts[leafID] = intermediateCerts
304
+	}
305
+
306
+	return leafCerts, intCerts
307
+}
308
+
309
+// certsToRemove returns all the certificates from oldCerts that aren't present
310
+// in newCerts.  Note that newCerts should never be empty, else this function will error.
311
+// We expect newCerts to come from validateRootLeafCerts, which does not return empty sets.
312
+func certsToRemove(oldCerts, newCerts []*x509.Certificate) (map[string]*x509.Certificate, error) {
313
+	certsToRemove := make(map[string]*x509.Certificate)
314
+
315
+	// Populate a map with all the IDs from newCert
316
+	var newCertMap = make(map[string]struct{})
317
+	for _, cert := range newCerts {
318
+		certID, err := trustmanager.FingerprintCert(cert)
319
+		if err != nil {
320
+			logrus.Debugf("error while fingerprinting root certificate with keyID: %s, %v", certID, err)
321
+			continue
322
+		}
323
+		newCertMap[certID] = struct{}{}
324
+	}
325
+
326
+	// We don't want to "rotate" certificates to an empty set, nor keep old certificates if the
327
+	// new root does not trust them.  newCerts should come from validRootLeafCerts, which refuses
328
+	// to return an empty set, and they should all be fingerprintable, so this should never happen
329
+	// - fail just to be sure.
330
+	if len(newCertMap) == 0 {
331
+		return nil, &ErrRootRotationFail{Reason: "internal error, got no certificates to rotate to"}
332
+	}
333
+
334
+	// Iterate over all the old certificates and check to see if we should remove them
335
+	for _, cert := range oldCerts {
336
+		certID, err := trustmanager.FingerprintCert(cert)
337
+		if err != nil {
338
+			logrus.Debugf("error while fingerprinting root certificate with certID: %s, %v", certID, err)
339
+			continue
340
+		}
341
+		if _, ok := newCertMap[certID]; !ok {
342
+			certsToRemove[certID] = cert
343
+		}
344
+	}
345
+
346
+	return certsToRemove, nil
347
+}
0 348
new file mode 100644
... ...
@@ -0,0 +1,118 @@
0
+package trustpinning
1
+
2
+import (
3
+	"crypto/x509"
4
+	"fmt"
5
+	"github.com/docker/notary/trustmanager"
6
+	"github.com/docker/notary/tuf/utils"
7
+	"strings"
8
+)
9
+
10
+// TrustPinConfig represents the configuration under the trust_pinning section of the config file
11
+// This struct represents the preferred way to bootstrap trust for this repository
12
+type TrustPinConfig struct {
13
+	CA          map[string]string
14
+	Certs       map[string][]string
15
+	DisableTOFU bool
16
+}
17
+
18
+type trustPinChecker struct {
19
+	gun           string
20
+	config        TrustPinConfig
21
+	pinnedCAPool  *x509.CertPool
22
+	pinnedCertIDs []string
23
+}
24
+
25
+// CertChecker is a function type that will be used to check leaf certs against pinned trust
26
+type CertChecker func(leafCert *x509.Certificate, intCerts []*x509.Certificate) bool
27
+
28
+// NewTrustPinChecker returns a new certChecker function from a TrustPinConfig for a GUN
29
+func NewTrustPinChecker(trustPinConfig TrustPinConfig, gun string) (CertChecker, error) {
30
+	t := trustPinChecker{gun: gun, config: trustPinConfig}
31
+	// Determine the mode, and if it's even valid
32
+	if pinnedCerts, ok := trustPinConfig.Certs[gun]; ok {
33
+		t.pinnedCertIDs = pinnedCerts
34
+		return t.certsCheck, nil
35
+	}
36
+
37
+	if caFilepath, err := getPinnedCAFilepathByPrefix(gun, trustPinConfig); err == nil {
38
+		// Try to add the CA certs from its bundle file to our certificate store,
39
+		// and use it to validate certs in the root.json later
40
+		caCerts, err := trustmanager.LoadCertBundleFromFile(caFilepath)
41
+		if err != nil {
42
+			return nil, fmt.Errorf("could not load root cert from CA path")
43
+		}
44
+		// Now only consider certificates that are direct children from this CA cert chain
45
+		caRootPool := x509.NewCertPool()
46
+		for _, caCert := range caCerts {
47
+			if err = trustmanager.ValidateCertificate(caCert); err != nil {
48
+				continue
49
+			}
50
+			caRootPool.AddCert(caCert)
51
+		}
52
+		// If we didn't have any valid CA certs, error out
53
+		if len(caRootPool.Subjects()) == 0 {
54
+			return nil, fmt.Errorf("invalid CA certs provided")
55
+		}
56
+		t.pinnedCAPool = caRootPool
57
+		return t.caCheck, nil
58
+	}
59
+
60
+	if !trustPinConfig.DisableTOFU {
61
+		return t.tofusCheck, nil
62
+	}
63
+	return nil, fmt.Errorf("invalid trust pinning specified")
64
+}
65
+
66
+func (t trustPinChecker) certsCheck(leafCert *x509.Certificate, intCerts []*x509.Certificate) bool {
67
+	// reconstruct the leaf + intermediate cert chain, which is bundled as {leaf, intermediates...},
68
+	// in order to get the matching id in the root file
69
+	leafCertID, err := trustmanager.FingerprintCert(leafCert)
70
+	if err != nil {
71
+		return false
72
+	}
73
+	rootKeys := trustmanager.CertsToKeys([]*x509.Certificate{leafCert}, map[string][]*x509.Certificate{leafCertID: intCerts})
74
+	for keyID := range rootKeys {
75
+		if utils.StrSliceContains(t.pinnedCertIDs, keyID) {
76
+			return true
77
+		}
78
+	}
79
+	return false
80
+}
81
+
82
+func (t trustPinChecker) caCheck(leafCert *x509.Certificate, intCerts []*x509.Certificate) bool {
83
+	// Use intermediate certificates included in the root TUF metadata for our validation
84
+	caIntPool := x509.NewCertPool()
85
+	for _, intCert := range intCerts {
86
+		caIntPool.AddCert(intCert)
87
+	}
88
+	// Attempt to find a valid certificate chain from the leaf cert to CA root
89
+	// Use this certificate if such a valid chain exists (possibly using intermediates)
90
+	if _, err := leafCert.Verify(x509.VerifyOptions{Roots: t.pinnedCAPool, Intermediates: caIntPool}); err == nil {
91
+		return true
92
+	}
93
+	return false
94
+}
95
+
96
+func (t trustPinChecker) tofusCheck(leafCert *x509.Certificate, intCerts []*x509.Certificate) bool {
97
+	return true
98
+}
99
+
100
+// Will return the CA filepath corresponding to the most specific (longest) entry in the map that is still a prefix
101
+// of the provided gun.  Returns an error if no entry matches this GUN as a prefix.
102
+func getPinnedCAFilepathByPrefix(gun string, t TrustPinConfig) (string, error) {
103
+	specificGUN := ""
104
+	specificCAFilepath := ""
105
+	foundCA := false
106
+	for gunPrefix, caFilepath := range t.CA {
107
+		if strings.HasPrefix(gun, gunPrefix) && len(gunPrefix) >= len(specificGUN) {
108
+			specificGUN = gunPrefix
109
+			specificCAFilepath = caFilepath
110
+			foundCA = true
111
+		}
112
+	}
113
+	if !foundCA {
114
+		return "", fmt.Errorf("could not find pinned CA for GUN: %s\n", gun)
115
+	}
116
+	return specificCAFilepath, nil
117
+}
... ...
@@ -75,7 +75,7 @@ func (c *Client) update() error {
75 75
 		return err
76 76
 	}
77 77
 	// will always need top level targets at a minimum
78
-	err = c.downloadTargets("targets")
78
+	err = c.downloadTargets(data.CanonicalTargetsRole)
79 79
 	if err != nil {
80 80
 		logrus.Debugf("Client Update (Targets): %s", err.Error())
81 81
 		return err
... ...
@@ -93,7 +93,7 @@ func (c Client) checkRoot() error {
93 93
 
94 94
 	expectedHashes := c.local.Snapshot.Signed.Meta[role].Hashes
95 95
 
96
-	raw, err := c.cache.GetMeta("root", size)
96
+	raw, err := c.cache.GetMeta(data.CanonicalRootRole, size)
97 97
 	if err != nil {
98 98
 		return err
99 99
 	}
... ...
@@ -331,7 +331,7 @@ func (c *Client) downloadSnapshot() error {
331 331
 	size := c.local.Timestamp.Signed.Meta[role].Length
332 332
 	expectedHashes := c.local.Timestamp.Signed.Meta[role].Hashes
333 333
 	if len(expectedHashes) == 0 {
334
-		return data.ErrMissingMeta{Role: "snapshot"}
334
+		return data.ErrMissingMeta{Role: data.CanonicalSnapshotRole}
335 335
 	}
336 336
 
337 337
 	var download bool
... ...
@@ -20,3 +20,21 @@ type ErrMissingMeta struct {
20 20
 func (e ErrMissingMeta) Error() string {
21 21
 	return fmt.Sprintf("tuf: sha256 checksum required for %s", e.Role)
22 22
 }
23
+
24
+// ErrInvalidChecksum is the error to be returned when checksum is invalid
25
+type ErrInvalidChecksum struct {
26
+	alg string
27
+}
28
+
29
+func (e ErrInvalidChecksum) Error() string {
30
+	return fmt.Sprintf("%s checksum invalid", e.alg)
31
+}
32
+
33
+// ErrMismatchedChecksum is the error to be returned when checksum is mismatched
34
+type ErrMismatchedChecksum struct {
35
+	alg string
36
+}
37
+
38
+func (e ErrMismatchedChecksum) Error() string {
39
+	return fmt.Sprintf("%s checksum mismatched", e.alg)
40
+}
... ...
@@ -116,6 +116,22 @@ func (b BaseRole) ListKeyIDs() []string {
116 116
 	return listKeyIDs(b.Keys)
117 117
 }
118 118
 
119
+// Equals returns whether this BaseRole equals another BaseRole
120
+func (b BaseRole) Equals(o BaseRole) bool {
121
+	if b.Threshold != o.Threshold || b.Name != o.Name || len(b.Keys) != len(o.Keys) {
122
+		return false
123
+	}
124
+
125
+	for keyID, key := range b.Keys {
126
+		oKey, ok := o.Keys[keyID]
127
+		if !ok || key.ID() != oKey.ID() {
128
+			return false
129
+		}
130
+	}
131
+
132
+	return true
133
+}
134
+
119 135
 // DelegationRole is an internal representation of a delegation role, with its public keys included
120 136
 type DelegationRole struct {
121 137
 	BaseRole
... ...
@@ -2,7 +2,6 @@ package data
2 2
 
3 3
 import (
4 4
 	"fmt"
5
-	"time"
6 5
 
7 6
 	"github.com/docker/go/canonical/json"
8 7
 )
... ...
@@ -16,9 +15,7 @@ type SignedRoot struct {
16 16
 
17 17
 // Root is the Signed component of a root.json
18 18
 type Root struct {
19
-	Type               string               `json:"_type"`
20
-	Version            int                  `json:"version"`
21
-	Expires            time.Time            `json:"expires"`
19
+	SignedCommon
22 20
 	Keys               Keys                 `json:"keys"`
23 21
 	Roles              map[string]*RootRole `json:"roles"`
24 22
 	ConsistentSnapshot bool                 `json:"consistent_snapshot"`
... ...
@@ -34,6 +31,11 @@ func isValidRootStructure(r Root) error {
34 34
 			role: CanonicalRootRole, msg: fmt.Sprintf("expected type %s, not %s", expectedType, r.Type)}
35 35
 	}
36 36
 
37
+	if r.Version < 0 {
38
+		return ErrInvalidMetadata{
39
+			role: CanonicalRootRole, msg: "version cannot be negative"}
40
+	}
41
+
37 42
 	// all the base roles MUST appear in the root.json - other roles are allowed,
38 43
 	// but other than the mirror role (not currently supported) are out of spec
39 44
 	for _, roleName := range BaseRoles {
... ...
@@ -72,9 +74,11 @@ func NewRoot(keys map[string]PublicKey, roles map[string]*RootRole, consistent b
72 72
 	signedRoot := &SignedRoot{
73 73
 		Signatures: make([]Signature, 0),
74 74
 		Signed: Root{
75
-			Type:               TUFTypes[CanonicalRootRole],
76
-			Version:            0,
77
-			Expires:            DefaultExpires(CanonicalRootRole),
75
+			SignedCommon: SignedCommon{
76
+				Type:    TUFTypes[CanonicalRootRole],
77
+				Version: 0,
78
+				Expires: DefaultExpires(CanonicalRootRole),
79
+			},
78 80
 			Keys:               keys,
79 81
 			Roles:              roles,
80 82
 			ConsistentSnapshot: consistent,
... ...
@@ -146,6 +150,12 @@ 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 s.Signed == nil {
150
+		return nil, ErrInvalidMetadata{
151
+			role: CanonicalRootRole,
152
+			msg:  "root file contained an empty payload",
153
+		}
154
+	}
149 155
 	if err := defaultSerializer.Unmarshal(*s.Signed, &r); err != nil {
150 156
 		return nil, err
151 157
 	}
... ...
@@ -3,7 +3,6 @@ package data
3 3
 import (
4 4
 	"bytes"
5 5
 	"fmt"
6
-	"time"
7 6
 
8 7
 	"github.com/Sirupsen/logrus"
9 8
 	"github.com/docker/go/canonical/json"
... ...
@@ -19,10 +18,8 @@ type SignedSnapshot struct {
19 19
 
20 20
 // Snapshot is the Signed component of a snapshot.json
21 21
 type Snapshot struct {
22
-	Type    string    `json:"_type"`
23
-	Version int       `json:"version"`
24
-	Expires time.Time `json:"expires"`
25
-	Meta    Files     `json:"meta"`
22
+	SignedCommon
23
+	Meta Files `json:"meta"`
26 24
 }
27 25
 
28 26
 // isValidSnapshotStructure returns an error, or nil, depending on whether the content of the
... ...
@@ -35,6 +32,11 @@ func isValidSnapshotStructure(s Snapshot) error {
35 35
 			role: CanonicalSnapshotRole, msg: fmt.Sprintf("expected type %s, not %s", expectedType, s.Type)}
36 36
 	}
37 37
 
38
+	if s.Version < 0 {
39
+		return ErrInvalidMetadata{
40
+			role: CanonicalSnapshotRole, msg: "version cannot be negative"}
41
+	}
42
+
38 43
 	for _, role := range []string{CanonicalRootRole, CanonicalTargetsRole} {
39 44
 		// Meta is a map of FileMeta, so if the role isn't in the map it returns
40 45
 		// an empty FileMeta, which has an empty map, and you can check on keys
... ...
@@ -82,9 +84,11 @@ func NewSnapshot(root *Signed, targets *Signed) (*SignedSnapshot, error) {
82 82
 	return &SignedSnapshot{
83 83
 		Signatures: make([]Signature, 0),
84 84
 		Signed: Snapshot{
85
-			Type:    TUFTypes["snapshot"],
86
-			Version: 0,
87
-			Expires: DefaultExpires("snapshot"),
85
+			SignedCommon: SignedCommon{
86
+				Type:    TUFTypes[CanonicalSnapshotRole],
87
+				Version: 0,
88
+				Expires: DefaultExpires(CanonicalSnapshotRole),
89
+			},
88 90
 			Meta: Files{
89 91
 				CanonicalRootRole:    rootMeta,
90 92
 				CanonicalTargetsRole: targetsMeta,
... ...
@@ -38,6 +38,10 @@ func isValidTargetsStructure(t Targets, roleName string) error {
38 38
 			role: roleName, msg: fmt.Sprintf("expected type %s, not %s", expectedType, t.Type)}
39 39
 	}
40 40
 
41
+	if t.Version < 0 {
42
+		return ErrInvalidMetadata{role: roleName, msg: "version cannot be negative"}
43
+	}
44
+
41 45
 	for _, roleObj := range t.Delegations.Roles {
42 46
 		if !IsDelegation(roleObj.Name) || path.Dir(roleObj.Name) != roleName {
43 47
 			return ErrInvalidMetadata{
... ...
@@ -3,7 +3,6 @@ package data
3 3
 import (
4 4
 	"bytes"
5 5
 	"fmt"
6
-	"time"
7 6
 
8 7
 	"github.com/docker/go/canonical/json"
9 8
 	"github.com/docker/notary"
... ...
@@ -18,10 +17,8 @@ type SignedTimestamp struct {
18 18
 
19 19
 // Timestamp is the Signed component of a timestamp.json
20 20
 type Timestamp struct {
21
-	Type    string    `json:"_type"`
22
-	Version int       `json:"version"`
23
-	Expires time.Time `json:"expires"`
24
-	Meta    Files     `json:"meta"`
21
+	SignedCommon
22
+	Meta Files `json:"meta"`
25 23
 }
26 24
 
27 25
 // isValidTimestampStructure returns an error, or nil, depending on whether the content of the struct
... ...
@@ -34,6 +31,11 @@ func isValidTimestampStructure(t Timestamp) error {
34 34
 			role: CanonicalTimestampRole, msg: fmt.Sprintf("expected type %s, not %s", expectedType, t.Type)}
35 35
 	}
36 36
 
37
+	if t.Version < 0 {
38
+		return ErrInvalidMetadata{
39
+			role: CanonicalTimestampRole, msg: "version cannot be negative"}
40
+	}
41
+
37 42
 	// Meta is a map of FileMeta, so if the role isn't in the map it returns
38 43
 	// an empty FileMeta, which has an empty map, and you can check on keys
39 44
 	// from an empty map.
... ...
@@ -64,9 +66,11 @@ func NewTimestamp(snapshot *Signed) (*SignedTimestamp, error) {
64 64
 	return &SignedTimestamp{
65 65
 		Signatures: make([]Signature, 0),
66 66
 		Signed: Timestamp{
67
-			Type:    TUFTypes["timestamp"],
68
-			Version: 0,
69
-			Expires: DefaultExpires("timestamp"),
67
+			SignedCommon: SignedCommon{
68
+				Type:    TUFTypes[CanonicalTimestampRole],
69
+				Version: 0,
70
+				Expires: DefaultExpires(CanonicalTimestampRole),
71
+			},
70 72
 			Meta: Files{
71 73
 				CanonicalSnapshotRole: snapshotMeta,
72 74
 			},
... ...
@@ -141,13 +141,13 @@ func CheckHashes(payload []byte, hashes Hashes) error {
141 141
 		case notary.SHA256:
142 142
 			checksum := sha256.Sum256(payload)
143 143
 			if subtle.ConstantTimeCompare(checksum[:], v) == 0 {
144
-				return fmt.Errorf("%s checksum mismatched", k)
144
+				return ErrMismatchedChecksum{alg: notary.SHA256}
145 145
 			}
146 146
 			cnt++
147 147
 		case notary.SHA512:
148 148
 			checksum := sha512.Sum512(payload)
149 149
 			if subtle.ConstantTimeCompare(checksum[:], v) == 0 {
150
-				return fmt.Errorf("%s checksum mismatched", k)
150
+				return ErrMismatchedChecksum{alg: notary.SHA512}
151 151
 			}
152 152
 			cnt++
153 153
 		}
... ...
@@ -169,12 +169,12 @@ func CheckValidHashStructures(hashes Hashes) error {
169 169
 		switch k {
170 170
 		case notary.SHA256:
171 171
 			if len(v) != sha256.Size {
172
-				return fmt.Errorf("invalid %s checksum", notary.SHA256)
172
+				return ErrInvalidChecksum{alg: notary.SHA256}
173 173
 			}
174 174
 			cnt++
175 175
 		case notary.SHA512:
176 176
 			if len(v) != sha512.Size {
177
-				return fmt.Errorf("invalid %s checksum", notary.SHA512)
177
+				return ErrInvalidChecksum{alg: notary.SHA512}
178 178
 			}
179 179
 			cnt++
180 180
 		}
... ...
@@ -5,14 +5,21 @@ import (
5 5
 	"strings"
6 6
 )
7 7
 
8
-// ErrInsufficientSignatures - do not have enough signatures on a piece of
8
+// ErrInsufficientSignatures - can not create enough signatures on a piece of
9 9
 // metadata
10 10
 type ErrInsufficientSignatures struct {
11
-	Name string
11
+	FoundKeys     int
12
+	NeededKeys    int
13
+	MissingKeyIDs []string
12 14
 }
13 15
 
14 16
 func (e ErrInsufficientSignatures) Error() string {
15
-	return fmt.Sprintf("tuf: insufficient signatures: %s", e.Name)
17
+	candidates := strings.Join(e.MissingKeyIDs, ", ")
18
+	if e.FoundKeys == 0 {
19
+		return fmt.Sprintf("signing keys not available, need %d keys out of: %s", e.NeededKeys, candidates)
20
+	}
21
+	return fmt.Sprintf("not enough signing keys: got %d of %d needed keys, other candidates: %s",
22
+		e.FoundKeys, e.NeededKeys, candidates)
16 23
 }
17 24
 
18 25
 // ErrExpired indicates a piece of metadata has expired
... ...
@@ -51,6 +58,13 @@ func (e ErrInvalidKeyType) Error() string {
51 51
 	return "key type is not valid for signature"
52 52
 }
53 53
 
54
+// ErrInvalidKeyID indicates the specified key ID was incorrect for its associated data
55
+type ErrInvalidKeyID struct{}
56
+
57
+func (e ErrInvalidKeyID) Error() string {
58
+	return "key ID is not valid for key content"
59
+}
60
+
54 61
 // ErrInvalidKeyLength indicates that while we may support the cipher, the provided
55 62
 // key length is not specifically supported, i.e. we support RSA, but not 1024 bit keys
56 63
 type ErrInvalidKeyLength struct {
... ...
@@ -13,54 +13,73 @@ package signed
13 13
 
14 14
 import (
15 15
 	"crypto/rand"
16
-	"fmt"
17 16
 
18 17
 	"github.com/Sirupsen/logrus"
18
+	"github.com/docker/notary/trustmanager"
19 19
 	"github.com/docker/notary/tuf/data"
20 20
 	"github.com/docker/notary/tuf/utils"
21 21
 )
22 22
 
23
-// Sign takes a data.Signed and a key, calculated and adds the signature
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.
27
-func Sign(service CryptoService, s *data.Signed, keys ...data.PublicKey) error {
28
-	logrus.Debugf("sign called with %d keys", len(keys))
23
+// Sign takes a data.Signed and a cryptoservice containing private keys,
24
+// calculates and adds at least minSignature signatures using signingKeys the
25
+// data.Signed.  It will also clean up any signatures that are not in produced
26
+// by either a signingKey or an otherWhitelistedKey.
27
+// Note that in most cases, otherWhitelistedKeys should probably be null. They
28
+// are for keys you don't want to sign with, but you also don't want to remove
29
+// existing signatures by those keys.  For instance, if you want to call Sign
30
+// multiple times with different sets of signing keys without undoing removing
31
+// signatures produced by the previous call to Sign.
32
+func Sign(service CryptoService, s *data.Signed, signingKeys []data.PublicKey,
33
+	minSignatures int, otherWhitelistedKeys []data.PublicKey) error {
34
+
35
+	logrus.Debugf("sign called with %d/%d required keys", minSignatures, len(signingKeys))
29 36
 	signatures := make([]data.Signature, 0, len(s.Signatures)+1)
30 37
 	signingKeyIDs := make(map[string]struct{})
31 38
 	tufIDs := make(map[string]data.PublicKey)
32
-	ids := make([]string, 0, len(keys))
33 39
 
34 40
 	privKeys := make(map[string]data.PrivateKey)
35 41
 
36 42
 	// Get all the private key objects related to the public keys
37
-	for _, key := range keys {
43
+	missingKeyIDs := []string{}
44
+	for _, key := range signingKeys {
38 45
 		canonicalID, err := utils.CanonicalKeyID(key)
39
-		ids = append(ids, canonicalID)
40 46
 		tufIDs[key.ID()] = key
41 47
 		if err != nil {
42
-			continue
48
+			return err
43 49
 		}
44 50
 		k, _, err := service.GetPrivateKey(canonicalID)
45 51
 		if err != nil {
46
-			continue
52
+			if _, ok := err.(trustmanager.ErrKeyNotFound); ok {
53
+				missingKeyIDs = append(missingKeyIDs, canonicalID)
54
+				continue
55
+			}
56
+			return err
47 57
 		}
48 58
 		privKeys[key.ID()] = k
49 59
 	}
50 60
 
51
-	// Check to ensure we have at least one signing key
52
-	if len(privKeys) == 0 {
53
-		return ErrNoKeys{KeyIDs: ids}
61
+	// include the list of otherWhitelistedKeys
62
+	for _, key := range otherWhitelistedKeys {
63
+		if _, ok := tufIDs[key.ID()]; !ok {
64
+			tufIDs[key.ID()] = key
65
+		}
66
+	}
67
+
68
+	// Check to ensure we have enough signing keys
69
+	if len(privKeys) < minSignatures {
70
+		return ErrInsufficientSignatures{FoundKeys: len(privKeys),
71
+			NeededKeys: minSignatures, MissingKeyIDs: missingKeyIDs}
54 72
 	}
55 73
 
74
+	emptyStruct := struct{}{}
56 75
 	// Do signing and generate list of signatures
57 76
 	for keyID, pk := range privKeys {
58 77
 		sig, err := pk.Sign(rand.Reader, *s.Signed, nil)
59 78
 		if err != nil {
60 79
 			logrus.Debugf("Failed to sign with key: %s. Reason: %v", keyID, err)
61
-			continue
80
+			return err
62 81
 		}
63
-		signingKeyIDs[keyID] = struct{}{}
82
+		signingKeyIDs[keyID] = emptyStruct
64 83
 		signatures = append(signatures, data.Signature{
65 84
 			KeyID:     keyID,
66 85
 			Method:    pk.SignatureAlgorithm(),
... ...
@@ -68,15 +87,6 @@ func Sign(service CryptoService, s *data.Signed, keys ...data.PublicKey) error {
68 68
 		})
69 69
 	}
70 70
 
71
-	// Check we produced at least on signature
72
-	if len(signatures) < 1 {
73
-		return ErrInsufficientSignatures{
74
-			Name: fmt.Sprintf(
75
-				"cryptoservice failed to produce any signatures for keys with IDs: %v",
76
-				ids),
77
-		}
78
-	}
79
-
80 71
 	for _, sig := range s.Signatures {
81 72
 		if _, ok := signingKeyIDs[sig.KeyID]; ok {
82 73
 			// key is in the set of key IDs for which a signature has been created
... ...
@@ -21,47 +21,6 @@ var (
21 21
 	ErrWrongType    = errors.New("tuf: meta file has wrong type")
22 22
 )
23 23
 
24
-// VerifyRoot checks if a given root file is valid against a known set of keys.
25
-// Threshold is always assumed to be 1
26
-func VerifyRoot(s *data.Signed, minVersion int, keys map[string]data.PublicKey) error {
27
-	if len(s.Signatures) == 0 {
28
-		return ErrNoSignatures
29
-	}
30
-
31
-	var decoded map[string]interface{}
32
-	if err := json.Unmarshal(*s.Signed, &decoded); err != nil {
33
-		return err
34
-	}
35
-	msg, err := json.MarshalCanonical(decoded)
36
-	if err != nil {
37
-		return err
38
-	}
39
-
40
-	for _, sig := range s.Signatures {
41
-		// method lookup is consistent due to Unmarshal JSON doing lower case for us.
42
-		method := sig.Method
43
-		verifier, ok := Verifiers[method]
44
-		if !ok {
45
-			logrus.Debugf("continuing b/c signing method is not supported for verify root: %s\n", sig.Method)
46
-			continue
47
-		}
48
-
49
-		key, ok := keys[sig.KeyID]
50
-		if !ok {
51
-			logrus.Debugf("continuing b/c signing key isn't present in keys: %s\n", sig.KeyID)
52
-			continue
53
-		}
54
-
55
-		if err := verifier.Verify(key, sig.Signature, msg); err != nil {
56
-			logrus.Debugf("continuing b/c signature was invalid\n")
57
-			continue
58
-		}
59
-		// threshold of 1 so return on first success
60
-		return verifyMeta(s, data.CanonicalRootRole, minVersion)
61
-	}
62
-	return ErrRoleThreshold{}
63
-}
64
-
65 24
 // Verify checks the signatures and metadata (expiry, version) for the signed role
66 25
 // data
67 26
 func Verify(s *data.Signed, role data.BaseRole, minVersion int) error {
... ...
@@ -125,6 +84,10 @@ func VerifySignatures(s *data.Signed, roleData data.BaseRole) error {
125 125
 			logrus.Debugf("continuing b/c keyid lookup was nil: %s\n", sig.KeyID)
126 126
 			continue
127 127
 		}
128
+		// Check that the signature key ID actually matches the content ID of the key
129
+		if key.ID() != sig.KeyID {
130
+			return ErrInvalidKeyID{}
131
+		}
128 132
 		if err := VerifySignature(msg, sig, key); err != nil {
129 133
 			logrus.Debugf("continuing b/c %s", err.Error())
130 134
 			continue
... ...
@@ -39,7 +39,8 @@ func (f *FilesystemStore) getPath(name string) string {
39 39
 }
40 40
 
41 41
 // GetMeta returns the meta for the given name (a role) up to size bytes
42
-// If size is -1, this corresponds to "infinite," but we cut off at 100MB
42
+// If size is -1, this corresponds to "infinite," but we cut off at the
43
+// predefined threshold "notary.MaxDownloadSize".
43 44
 func (f *FilesystemStore) GetMeta(name string, size int64) ([]byte, error) {
44 45
 	meta, err := ioutil.ReadFile(f.getPath(name))
45 46
 	if err != nil {
... ...
@@ -6,6 +6,8 @@ import (
6 6
 	"encoding/json"
7 7
 	"fmt"
8 8
 	"path"
9
+	"sort"
10
+	"strconv"
9 11
 	"strings"
10 12
 	"time"
11 13
 
... ...
@@ -62,6 +64,13 @@ type Repo struct {
62 62
 	Snapshot      *data.SignedSnapshot
63 63
 	Timestamp     *data.SignedTimestamp
64 64
 	cryptoService signed.CryptoService
65
+
66
+	// Because Repo is a mutable structure, these keep track of what the root
67
+	// role was when a root is set on the repo (as opposed to what it might be
68
+	// after things like AddBaseKeys and RemoveBaseKeys have been called on it).
69
+	// If we know what the original was, we'll if and how to handle root
70
+	// rotations.
71
+	originalRootRole data.BaseRole
65 72
 }
66 73
 
67 74
 // NewRepo initializes a Repo instance with a CryptoService.
... ...
@@ -90,7 +99,7 @@ func (tr *Repo) AddBaseKeys(role string, keys ...data.PublicKey) error {
90 90
 	tr.Root.Dirty = true
91 91
 
92 92
 	// also, whichever role was switched out needs to be re-signed
93
-	// root has already been marked dirty
93
+	// root has already been marked dirty.
94 94
 	switch role {
95 95
 	case data.CanonicalSnapshotRole:
96 96
 		if tr.Snapshot != nil {
... ...
@@ -128,17 +137,38 @@ func (tr *Repo) RemoveBaseKeys(role string, keyIDs ...string) error {
128 128
 	}
129 129
 	var keep []string
130 130
 	toDelete := make(map[string]struct{})
131
+	emptyStruct := struct{}{}
131 132
 	// remove keys from specified role
132 133
 	for _, k := range keyIDs {
133
-		toDelete[k] = struct{}{}
134
-		for _, rk := range tr.Root.Signed.Roles[role].KeyIDs {
135
-			if k != rk {
136
-				keep = append(keep, rk)
137
-			}
134
+		toDelete[k] = emptyStruct
135
+	}
136
+
137
+	oldKeyIDs := tr.Root.Signed.Roles[role].KeyIDs
138
+	for _, rk := range oldKeyIDs {
139
+		if _, ok := toDelete[rk]; !ok {
140
+			keep = append(keep, rk)
138 141
 		}
139 142
 	}
143
+
140 144
 	tr.Root.Signed.Roles[role].KeyIDs = keep
141 145
 
146
+	// also, whichever role had keys removed needs to be re-signed
147
+	// root has already been marked dirty.
148
+	switch role {
149
+	case data.CanonicalSnapshotRole:
150
+		if tr.Snapshot != nil {
151
+			tr.Snapshot.Dirty = true
152
+		}
153
+	case data.CanonicalTargetsRole:
154
+		if target, ok := tr.Targets[data.CanonicalTargetsRole]; ok {
155
+			target.Dirty = true
156
+		}
157
+	case data.CanonicalTimestampRole:
158
+		if tr.Timestamp != nil {
159
+			tr.Timestamp.Dirty = true
160
+		}
161
+	}
162
+
142 163
 	// determine which keys are no longer in use by any roles
143 164
 	for roleName, r := range tr.Root.Signed.Roles {
144 165
 		if roleName == role {
... ...
@@ -151,13 +181,16 @@ func (tr *Repo) RemoveBaseKeys(role string, keyIDs ...string) error {
151 151
 		}
152 152
 	}
153 153
 
154
-	// remove keys no longer in use by any roles
155
-	for k := range toDelete {
156
-		delete(tr.Root.Signed.Keys, k)
157
-		// remove the signing key from the cryptoservice if it
158
-		// isn't a root key. Root keys must be kept for rotation
159
-		// signing
160
-		if role != data.CanonicalRootRole {
154
+	// Remove keys no longer in use by any roles, except for root keys.
155
+	// Root private keys must be kept in tr.cryptoService to be able to sign
156
+	// for rotation, and root certificates must be kept in tr.Root.SignedKeys
157
+	// because we are not necessarily storing them elsewhere (tuf.Repo does not
158
+	// depend on certs.Manager, that is an upper layer), and without storing
159
+	// the certificates in their x509 form we are not able to do the
160
+	// util.CanonicalKeyID conversion.
161
+	if role != data.CanonicalRootRole {
162
+		for k := range toDelete {
163
+			delete(tr.Root.Signed.Keys, k)
161 164
 			tr.cryptoService.RemoveKey(k)
162 165
 		}
163 166
 	}
... ...
@@ -459,6 +492,7 @@ func (tr *Repo) InitRoot(root, timestamp, snapshot, targets data.BaseRole, consi
459 459
 		return err
460 460
 	}
461 461
 	tr.Root = r
462
+	tr.originalRootRole = root
462 463
 	return nil
463 464
 }
464 465
 
... ...
@@ -518,7 +552,11 @@ func (tr *Repo) InitTimestamp() error {
518 518
 // SetRoot sets the Repo.Root field to the SignedRoot object.
519 519
 func (tr *Repo) SetRoot(s *data.SignedRoot) error {
520 520
 	tr.Root = s
521
-	return nil
521
+	var err error
522
+	// originalRootRole is the root role prior to any mutations that might
523
+	// occur on tr.Root.
524
+	tr.originalRootRole, err = tr.Root.BuildBaseRole(data.CanonicalRootRole)
525
+	return err
522 526
 }
523 527
 
524 528
 // SetTimestamp parses the Signed object into a SignedTimestamp object
... ...
@@ -781,32 +819,161 @@ func (tr *Repo) UpdateTimestamp(s *data.Signed) error {
781 781
 	if err != nil {
782 782
 		return err
783 783
 	}
784
-	tr.Timestamp.Signed.Meta["snapshot"] = meta
784
+	tr.Timestamp.Signed.Meta[data.CanonicalSnapshotRole] = meta
785 785
 	tr.Timestamp.Dirty = true
786 786
 	return nil
787 787
 }
788 788
 
789
-// SignRoot signs the root
789
+type versionedRootRole struct {
790
+	data.BaseRole
791
+	version int
792
+}
793
+
794
+type versionedRootRoles []versionedRootRole
795
+
796
+func (v versionedRootRoles) Len() int           { return len(v) }
797
+func (v versionedRootRoles) Swap(i, j int)      { v[i], v[j] = v[j], v[i] }
798
+func (v versionedRootRoles) Less(i, j int) bool { return v[i].version < v[j].version }
799
+
800
+// SignRoot signs the root, using all keys from the "root" role (i.e. currently trusted)
801
+// as well as available keys used to sign the previous version, if the public part is
802
+// carried in tr.Root.Keys and the private key is available (i.e. probably previously
803
+// trusted keys, to allow rollover).  If there are any errors, attempt to put root
804
+// back to the way it was (so version won't be incremented, for instance).
790 805
 func (tr *Repo) SignRoot(expires time.Time) (*data.Signed, error) {
791 806
 	logrus.Debug("signing root...")
792
-	tr.Root.Signed.Expires = expires
793
-	tr.Root.Signed.Version++
794
-	root, err := tr.GetBaseRole(data.CanonicalRootRole)
807
+
808
+	// duplicate root and attempt to modify it rather than the existing root
809
+	rootBytes, err := tr.Root.MarshalJSON()
795 810
 	if err != nil {
796 811
 		return nil, err
797 812
 	}
798
-	signed, err := tr.Root.ToSigned()
813
+	tempRoot := data.SignedRoot{}
814
+	if err := json.Unmarshal(rootBytes, &tempRoot); err != nil {
815
+		return nil, err
816
+	}
817
+
818
+	currRoot, err := tr.GetBaseRole(data.CanonicalRootRole)
799 819
 	if err != nil {
800 820
 		return nil, err
801 821
 	}
802
-	signed, err = tr.sign(signed, root)
822
+
823
+	oldRootRoles := tr.getOldRootRoles()
824
+
825
+	var latestSavedRole data.BaseRole
826
+	rolesToSignWith := make([]data.BaseRole, 0, len(oldRootRoles))
827
+
828
+	if len(oldRootRoles) > 0 {
829
+		sort.Sort(oldRootRoles)
830
+		for _, vRole := range oldRootRoles {
831
+			rolesToSignWith = append(rolesToSignWith, vRole.BaseRole)
832
+		}
833
+		latest := rolesToSignWith[len(rolesToSignWith)-1]
834
+		latestSavedRole = data.BaseRole{
835
+			Name:      data.CanonicalRootRole,
836
+			Threshold: latest.Threshold,
837
+			Keys:      latest.Keys,
838
+		}
839
+	}
840
+
841
+	// if the root role has changed and original role had not been saved as a previous role, save it now
842
+	if !tr.originalRootRole.Equals(currRoot) && !tr.originalRootRole.Equals(latestSavedRole) {
843
+		rolesToSignWith = append(rolesToSignWith, tr.originalRootRole)
844
+		latestSavedRole = tr.originalRootRole
845
+
846
+		versionName := oldRootVersionName(tempRoot.Signed.Version)
847
+		tempRoot.Signed.Roles[versionName] = &data.RootRole{
848
+			KeyIDs: latestSavedRole.ListKeyIDs(), Threshold: latestSavedRole.Threshold}
849
+
850
+	}
851
+
852
+	tempRoot.Signed.Expires = expires
853
+	tempRoot.Signed.Version++
854
+
855
+	// if the current role doesn't match with the latest saved role, save it
856
+	if !currRoot.Equals(latestSavedRole) {
857
+		rolesToSignWith = append(rolesToSignWith, currRoot)
858
+
859
+		versionName := oldRootVersionName(tempRoot.Signed.Version)
860
+		tempRoot.Signed.Roles[versionName] = &data.RootRole{
861
+			KeyIDs: currRoot.ListKeyIDs(), Threshold: currRoot.Threshold}
862
+	}
863
+
864
+	signed, err := tempRoot.ToSigned()
865
+	if err != nil {
866
+		return nil, err
867
+	}
868
+	signed, err = tr.sign(signed, rolesToSignWith, tr.getOptionalRootKeys(rolesToSignWith))
803 869
 	if err != nil {
804 870
 		return nil, err
805 871
 	}
872
+
873
+	tr.Root = &tempRoot
806 874
 	tr.Root.Signatures = signed.Signatures
875
+	tr.originalRootRole = currRoot
807 876
 	return signed, nil
808 877
 }
809 878
 
879
+// get all the saved previous roles <= the current root version
880
+func (tr *Repo) getOldRootRoles() versionedRootRoles {
881
+	oldRootRoles := make(versionedRootRoles, 0, len(tr.Root.Signed.Roles))
882
+
883
+	// now go through the old roles
884
+	for roleName := range tr.Root.Signed.Roles {
885
+		// ensure that the rolename matches our format and that the version is
886
+		// not too high
887
+		if data.ValidRole(roleName) {
888
+			continue
889
+		}
890
+		nameTokens := strings.Split(roleName, ".")
891
+		if len(nameTokens) != 2 || nameTokens[0] != data.CanonicalRootRole {
892
+			continue
893
+		}
894
+		version, err := strconv.Atoi(nameTokens[1])
895
+		if err != nil || version > tr.Root.Signed.Version {
896
+			continue
897
+		}
898
+
899
+		// ignore invalid roles, which shouldn't happen
900
+		oldRole, err := tr.Root.BuildBaseRole(roleName)
901
+		if err != nil {
902
+			continue
903
+		}
904
+
905
+		oldRootRoles = append(oldRootRoles, versionedRootRole{BaseRole: oldRole, version: version})
906
+	}
907
+
908
+	return oldRootRoles
909
+}
910
+
911
+// gets any extra optional root keys from the existing root.json signatures
912
+// (because older repositories that have already done root rotation may not
913
+// necessarily have older root roles)
914
+func (tr *Repo) getOptionalRootKeys(signingRoles []data.BaseRole) []data.PublicKey {
915
+	oldKeysMap := make(map[string]data.PublicKey)
916
+	for _, oldSig := range tr.Root.Signatures {
917
+		if k, ok := tr.Root.Signed.Keys[oldSig.KeyID]; ok {
918
+			oldKeysMap[k.ID()] = k
919
+		}
920
+	}
921
+	for _, role := range signingRoles {
922
+		for keyID := range role.Keys {
923
+			delete(oldKeysMap, keyID)
924
+		}
925
+	}
926
+
927
+	oldKeys := make([]data.PublicKey, 0, len(oldKeysMap))
928
+	for _, key := range oldKeysMap {
929
+		oldKeys = append(oldKeys, key)
930
+	}
931
+
932
+	return oldKeys
933
+}
934
+
935
+func oldRootVersionName(version int) string {
936
+	return fmt.Sprintf("%s.%v", data.CanonicalRootRole, version)
937
+}
938
+
810 939
 // SignTargets signs the targets file for the given top level or delegated targets role
811 940
 func (tr *Repo) SignTargets(role string, expires time.Time) (*data.Signed, error) {
812 941
 	logrus.Debugf("sign targets called for role %s", role)
... ...
@@ -838,7 +1005,7 @@ func (tr *Repo) SignTargets(role string, expires time.Time) (*data.Signed, error
838 838
 		return nil, err
839 839
 	}
840 840
 
841
-	signed, err = tr.sign(signed, targets)
841
+	signed, err = tr.sign(signed, []data.BaseRole{targets}, nil)
842 842
 	if err != nil {
843 843
 		logrus.Debug("errored signing ", role)
844 844
 		return nil, err
... ...
@@ -854,7 +1021,7 @@ func (tr *Repo) SignSnapshot(expires time.Time) (*data.Signed, error) {
854 854
 	if err != nil {
855 855
 		return nil, err
856 856
 	}
857
-	err = tr.UpdateSnapshot("root", signedRoot)
857
+	err = tr.UpdateSnapshot(data.CanonicalRootRole, signedRoot)
858 858
 	if err != nil {
859 859
 		return nil, err
860 860
 	}
... ...
@@ -880,7 +1047,7 @@ func (tr *Repo) SignSnapshot(expires time.Time) (*data.Signed, error) {
880 880
 	if err != nil {
881 881
 		return nil, err
882 882
 	}
883
-	signed, err = tr.sign(signed, snapshot)
883
+	signed, err = tr.sign(signed, []data.BaseRole{snapshot}, nil)
884 884
 	if err != nil {
885 885
 		return nil, err
886 886
 	}
... ...
@@ -909,7 +1076,7 @@ func (tr *Repo) SignTimestamp(expires time.Time) (*data.Signed, error) {
909 909
 	if err != nil {
910 910
 		return nil, err
911 911
 	}
912
-	signed, err = tr.sign(signed, timestamp)
912
+	signed, err = tr.sign(signed, []data.BaseRole{timestamp}, nil)
913 913
 	if err != nil {
914 914
 		return nil, err
915 915
 	}
... ...
@@ -918,9 +1085,17 @@ func (tr *Repo) SignTimestamp(expires time.Time) (*data.Signed, error) {
918 918
 	return signed, nil
919 919
 }
920 920
 
921
-func (tr Repo) sign(signedData *data.Signed, role data.BaseRole) (*data.Signed, error) {
922
-	if err := signed.Sign(tr.cryptoService, signedData, role.ListKeys()...); err != nil {
923
-		return nil, err
921
+func (tr Repo) sign(signedData *data.Signed, roles []data.BaseRole, optionalKeys []data.PublicKey) (*data.Signed, error) {
922
+	validKeys := optionalKeys
923
+	for _, r := range roles {
924
+		roleKeys := r.ListKeys()
925
+		validKeys = append(roleKeys, validKeys...)
926
+		if err := signed.Sign(tr.cryptoService, signedData, roleKeys, r.Threshold, validKeys); err != nil {
927
+			return nil, err
928
+		}
924 929
 	}
930
+	// Attempt to sign with the optional keys, but ignore any errors, because these keys are optional
931
+	signed.Sign(tr.cryptoService, signedData, optionalKeys, 0, validKeys)
932
+
925 933
 	return signedData, nil
926 934
 }
... ...
@@ -98,7 +98,7 @@ func HashedPaths(path string, hashes data.Hashes) []string {
98 98
 
99 99
 // CanonicalKeyID returns the ID of the public bytes version of a TUF key.
100 100
 // On regular RSA/ECDSA TUF keys, this is just the key ID.  On X509 RSA/ECDSA
101
-// TUF keys, this is the key ID of the public key part of the key.
101
+// TUF keys, this is the key ID of the public key part of the key in the leaf cert
102 102
 func CanonicalKeyID(k data.PublicKey) (string, error) {
103 103
 	switch k.Algorithm() {
104 104
 	case data.ECDSAx509Key, data.RSAx509Key: