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