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