Vendor in notary v0.2.0
Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
This commit is contained in:
parent
6fa5576e30
commit
84dc2d9e70
45 changed files with 1755 additions and 1195 deletions
|
@ -169,7 +169,7 @@ RUN set -x \
|
||||||
&& rm -rf "$GOPATH"
|
&& rm -rf "$GOPATH"
|
||||||
|
|
||||||
# Install notary server
|
# Install notary server
|
||||||
ENV NOTARY_VERSION docker-v1.10-5
|
ENV NOTARY_VERSION v0.2.0
|
||||||
RUN set -x \
|
RUN set -x \
|
||||||
&& export GOPATH="$(mktemp -d)" \
|
&& export GOPATH="$(mktemp -d)" \
|
||||||
&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
|
&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
|
||||||
|
|
|
@ -119,7 +119,7 @@ RUN set -x \
|
||||||
&& rm -rf "$GOPATH"
|
&& rm -rf "$GOPATH"
|
||||||
|
|
||||||
# Install notary server
|
# Install notary server
|
||||||
ENV NOTARY_VERSION docker-v1.10-5
|
ENV NOTARY_VERSION v0.2.0
|
||||||
RUN set -x \
|
RUN set -x \
|
||||||
&& export GOPATH="$(mktemp -d)" \
|
&& export GOPATH="$(mktemp -d)" \
|
||||||
&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
|
&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
|
||||||
|
|
|
@ -135,7 +135,7 @@ RUN set -x \
|
||||||
&& rm -rf "$GOPATH"
|
&& rm -rf "$GOPATH"
|
||||||
|
|
||||||
# Install notary server
|
# Install notary server
|
||||||
ENV NOTARY_VERSION docker-v1.10-5
|
ENV NOTARY_VERSION v0.2.0
|
||||||
RUN set -x \
|
RUN set -x \
|
||||||
&& export GOPATH="$(mktemp -d)" \
|
&& export GOPATH="$(mktemp -d)" \
|
||||||
&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
|
&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
|
||||||
|
|
|
@ -127,16 +127,16 @@ RUN set -x \
|
||||||
go build -o /usr/local/bin/registry-v2-schema1 github.com/docker/distribution/cmd/registry \
|
go build -o /usr/local/bin/registry-v2-schema1 github.com/docker/distribution/cmd/registry \
|
||||||
&& rm -rf "$GOPATH"
|
&& rm -rf "$GOPATH"
|
||||||
|
|
||||||
# TODO update this when we upgrade to Go 1.5.1+
|
|
||||||
# Install notary server
|
# Install notary server
|
||||||
#ENV NOTARY_VERSION docker-v1.10-5
|
ENV NOTARY_VERSION v0.2.0
|
||||||
#RUN set -x \
|
RUN set -x \
|
||||||
# && export GOPATH="$(mktemp -d)" \
|
&& export GOPATH="$(mktemp -d)" \
|
||||||
# && git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
|
&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
|
||||||
# && (cd "$GOPATH/src/github.com/docker/notary" && git checkout -q "$NOTARY_VERSION") \
|
&& (cd "$GOPATH/src/github.com/docker/notary" && git checkout -q "$NOTARY_VERSION") \
|
||||||
# && GOPATH="$GOPATH/src/github.com/docker/notary/Godeps/_workspace:$GOPATH" \
|
&& GOPATH="$GOPATH/src/github.com/docker/notary/Godeps/_workspace:$GOPATH" \
|
||||||
# go build -o /usr/local/bin/notary-server github.com/docker/notary/cmd/notary-server \
|
go build -o /usr/local/bin/notary-server github.com/docker/notary/cmd/notary-server \
|
||||||
# && rm -rf "$GOPATH"
|
&& rm -rf "$GOPATH"
|
||||||
|
|
||||||
# Get the "docker-py" source so we can run their integration tests
|
# Get the "docker-py" source so we can run their integration tests
|
||||||
ENV DOCKER_PY_COMMIT e2878cbcc3a7eef99917adc1be252800b0e41ece
|
ENV DOCKER_PY_COMMIT e2878cbcc3a7eef99917adc1be252800b0e41ece
|
||||||
|
|
|
@ -108,7 +108,7 @@ RUN set -x \
|
||||||
&& rm -rf "$GOPATH"
|
&& rm -rf "$GOPATH"
|
||||||
|
|
||||||
# Install notary server
|
# Install notary server
|
||||||
ENV NOTARY_VERSION docker-v1.10-5
|
ENV NOTARY_VERSION v0.2.0
|
||||||
RUN set -x \
|
RUN set -x \
|
||||||
&& export GOPATH="$(mktemp -d)" \
|
&& export GOPATH="$(mktemp -d)" \
|
||||||
&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
|
&& 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
|
||||||
clone git github.com/vbatts/tar-split v0.9.11
|
clone git github.com/vbatts/tar-split v0.9.11
|
||||||
|
|
||||||
# get desired notary commit, might also need to be updated in Dockerfile
|
# get desired notary commit, might also need to be updated in Dockerfile
|
||||||
clone git github.com/docker/notary docker-v1.10-5
|
clone git github.com/docker/notary v0.2.0
|
||||||
|
|
||||||
clone git google.golang.org/grpc 174192fc93efcb188fc8f46ca447f0da606b6885 https://github.com/grpc/grpc-go.git
|
clone git google.golang.org/grpc 174192fc93efcb188fc8f46ca447f0da606b6885 https://github.com/grpc/grpc-go.git
|
||||||
clone git github.com/miekg/pkcs11 80f102b5cac759de406949c47f0928b99bd64cdf
|
clone git github.com/miekg/pkcs11 80f102b5cac759de406949c47f0928b99bd64cdf
|
||||||
|
|
|
@ -365,7 +365,7 @@ func (s *DockerTrustSuite) TestCreateWhenCertExpired(c *check.C) {
|
||||||
|
|
||||||
func (s *DockerTrustSuite) TestTrustedCreateFromBadTrustServer(c *check.C) {
|
func (s *DockerTrustSuite) TestTrustedCreateFromBadTrustServer(c *check.C) {
|
||||||
repoName := fmt.Sprintf("%v/dockerclievilcreate/trusted:latest", privateRegistryURL)
|
repoName := fmt.Sprintf("%v/dockerclievilcreate/trusted:latest", privateRegistryURL)
|
||||||
evilLocalConfigDir, err := ioutil.TempDir("", "evil-local-config-dir")
|
evilLocalConfigDir, err := ioutil.TempDir("", "evilcreate-local-config-dir")
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
|
|
||||||
// tag the image and upload it to the private registry
|
// tag the image and upload it to the private registry
|
||||||
|
@ -404,12 +404,16 @@ func (s *DockerTrustSuite) TestTrustedCreateFromBadTrustServer(c *check.C) {
|
||||||
c.Assert(err, check.IsNil)
|
c.Assert(err, check.IsNil)
|
||||||
c.Assert(string(out), checker.Contains, "Signing and pushing trust metadata", check.Commentf("Missing expected output on trusted push:\n%s", out))
|
c.Assert(string(out), checker.Contains, "Signing and pushing trust metadata", check.Commentf("Missing expected output on trusted push:\n%s", out))
|
||||||
|
|
||||||
// Now, try creating with the original client from this new trust server. This should fail.
|
// Now, try creating with the original client from this new trust server. This should fallback to our cached timestamp and metadata.
|
||||||
createCmd = exec.Command(dockerBinary, "create", repoName)
|
createCmd = exec.Command(dockerBinary, "create", repoName)
|
||||||
s.trustedCmd(createCmd)
|
s.trustedCmd(createCmd)
|
||||||
out, _, err = runCommandWithOutput(createCmd)
|
out, _, err = runCommandWithOutput(createCmd)
|
||||||
c.Assert(err, check.Not(check.IsNil))
|
if err != nil {
|
||||||
c.Assert(string(out), checker.Contains, "valid signatures did not meet threshold", check.Commentf("Missing expected output on trusted push:\n%s", out))
|
c.Fatalf("Error falling back to cached trust data: %s\n%s", err, out)
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(out), "Error while downloading remote metadata, using cached timestamp") {
|
||||||
|
c.Fatalf("Missing expected output on trusted create:\n%s", out)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -135,13 +135,16 @@ func (s *DockerTrustSuite) TestTrustedPullFromBadTrustServer(c *check.C) {
|
||||||
c.Assert(err, check.IsNil, check.Commentf(out))
|
c.Assert(err, check.IsNil, check.Commentf(out))
|
||||||
c.Assert(string(out), checker.Contains, "Signing and pushing trust metadata", check.Commentf(out))
|
c.Assert(string(out), checker.Contains, "Signing and pushing trust metadata", check.Commentf(out))
|
||||||
|
|
||||||
// Now, try pulling with the original client from this new trust server. This should fail.
|
// Now, try pulling with the original client from this new trust server. This should fall back to cached metadata.
|
||||||
pullCmd = exec.Command(dockerBinary, "pull", repoName)
|
pullCmd = exec.Command(dockerBinary, "pull", repoName)
|
||||||
s.trustedCmd(pullCmd)
|
s.trustedCmd(pullCmd)
|
||||||
out, _, err = runCommandWithOutput(pullCmd)
|
out, _, err = runCommandWithOutput(pullCmd)
|
||||||
|
if err != nil {
|
||||||
c.Assert(err, check.NotNil, check.Commentf(out))
|
c.Fatalf("Error falling back to cached trust data: %s\n%s", err, out)
|
||||||
c.Assert(string(out), checker.Contains, "valid signatures did not meet threshold", check.Commentf(out))
|
}
|
||||||
|
if !strings.Contains(string(out), "Error while downloading remote metadata, using cached timestamp") {
|
||||||
|
c.Fatalf("Missing expected output on trusted pull:\n%s", out)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DockerTrustSuite) TestTrustedPullWithExpiredSnapshot(c *check.C) {
|
func (s *DockerTrustSuite) TestTrustedPullWithExpiredSnapshot(c *check.C) {
|
||||||
|
|
|
@ -3260,7 +3260,7 @@ func (s *DockerTrustSuite) TestTrustedRunFromBadTrustServer(c *check.C) {
|
||||||
// Windows does not support this functionality
|
// Windows does not support this functionality
|
||||||
testRequires(c, DaemonIsLinux)
|
testRequires(c, DaemonIsLinux)
|
||||||
repoName := fmt.Sprintf("%v/dockerclievilrun/trusted:latest", privateRegistryURL)
|
repoName := fmt.Sprintf("%v/dockerclievilrun/trusted:latest", privateRegistryURL)
|
||||||
evilLocalConfigDir, err := ioutil.TempDir("", "evil-local-config-dir")
|
evilLocalConfigDir, err := ioutil.TempDir("", "evilrun-local-config-dir")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Fatalf("Failed to create local temp dir")
|
c.Fatalf("Failed to create local temp dir")
|
||||||
}
|
}
|
||||||
|
@ -3316,15 +3316,15 @@ func (s *DockerTrustSuite) TestTrustedRunFromBadTrustServer(c *check.C) {
|
||||||
c.Fatalf("Missing expected output on trusted push:\n%s", out)
|
c.Fatalf("Missing expected output on trusted push:\n%s", out)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now, try running with the original client from this new trust server. This should fail.
|
// Now, try running with the original client from this new trust server. This should fallback to our cached timestamp and metadata.
|
||||||
runCmd = exec.Command(dockerBinary, "run", repoName)
|
runCmd = exec.Command(dockerBinary, "run", repoName)
|
||||||
s.trustedCmd(runCmd)
|
s.trustedCmd(runCmd)
|
||||||
out, _, err = runCommandWithOutput(runCmd)
|
out, _, err = runCommandWithOutput(runCmd)
|
||||||
if err == nil {
|
|
||||||
c.Fatalf("Expected to fail on this run due to different remote data: %s\n%s", err, out)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.Contains(string(out), "valid signatures did not meet threshold") {
|
if err != nil {
|
||||||
|
c.Fatalf("Error falling back to cached trust data: %s\n%s", err, out)
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(out), "Error while downloading remote metadata, using cached timestamp") {
|
||||||
c.Fatalf("Missing expected output on trusted push:\n%s", out)
|
c.Fatalf("Missing expected output on trusted push:\n%s", out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -237,7 +237,7 @@ func (s *DockerTrustSuite) setupDelegations(c *check.C, repoName, pwd string) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Fatalf("Error creating delegation key: %s\n", err)
|
c.Fatalf("Error creating delegation key: %s\n", err)
|
||||||
}
|
}
|
||||||
err = nRepo.AddDelegation("targets/releases", 1, []data.PublicKey{delgKey}, []string{""})
|
err = nRepo.AddDelegation("targets/releases", []data.PublicKey{delgKey}, []string{""})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Fatalf("Error creating delegation: %s\n", err)
|
c.Fatalf("Error creating delegation: %s\n", err)
|
||||||
}
|
}
|
||||||
|
|
49
vendor/src/github.com/docker/notary/Makefile
vendored
49
vendor/src/github.com/docker/notary/Makefile
vendored
|
@ -42,14 +42,6 @@ GO_VERSION = $(shell go version | awk '{print $$3}')
|
||||||
.DELETE_ON_ERROR: cover
|
.DELETE_ON_ERROR: cover
|
||||||
.DEFAULT: default
|
.DEFAULT: default
|
||||||
|
|
||||||
go_version:
|
|
||||||
ifeq (,$(findstring go1.5.,$(GO_VERSION)))
|
|
||||||
$(error Requires go version 1.5.x - found $(GO_VERSION))
|
|
||||||
else
|
|
||||||
@echo
|
|
||||||
endif
|
|
||||||
|
|
||||||
|
|
||||||
all: AUTHORS clean fmt vet fmt lint build test binaries
|
all: AUTHORS clean fmt vet fmt lint build test binaries
|
||||||
|
|
||||||
AUTHORS: .git/HEAD
|
AUTHORS: .git/HEAD
|
||||||
|
@ -71,7 +63,23 @@ ${PREFIX}/bin/notary-signer: NOTARY_VERSION $(shell find . -type f -name '*.go')
|
||||||
@echo "+ $@"
|
@echo "+ $@"
|
||||||
@godep go build -tags ${NOTARY_BUILDTAGS} -o $@ ${GO_LDFLAGS} ./cmd/notary-signer
|
@godep go build -tags ${NOTARY_BUILDTAGS} -o $@ ${GO_LDFLAGS} ./cmd/notary-signer
|
||||||
|
|
||||||
vet: go_version
|
ifeq ($(shell uname -s),Darwin)
|
||||||
|
${PREFIX}/bin/static/notary-server:
|
||||||
|
@echo "notary-server: static builds not supported on OS X"
|
||||||
|
|
||||||
|
${PREFIX}/bin/static/notary-signer:
|
||||||
|
@echo "notary-signer: static builds not supported on OS X"
|
||||||
|
else
|
||||||
|
${PREFIX}/bin/static/notary-server: NOTARY_VERSION $(shell find . -type f -name '*.go')
|
||||||
|
@echo "+ $@"
|
||||||
|
@godep go build -tags ${NOTARY_BUILDTAGS} -o $@ ${GO_LDFLAGS_STATIC} ./cmd/notary-server
|
||||||
|
|
||||||
|
${PREFIX}/bin/static/notary-signer: NOTARY_VERSION $(shell find . -type f -name '*.go')
|
||||||
|
@echo "+ $@"
|
||||||
|
@godep go build -tags ${NOTARY_BUILDTAGS} -o $@ ${GO_LDFLAGS_STATIC} ./cmd/notary-signer
|
||||||
|
endif
|
||||||
|
|
||||||
|
vet:
|
||||||
@echo "+ $@"
|
@echo "+ $@"
|
||||||
ifeq ($(shell uname -s), Darwin)
|
ifeq ($(shell uname -s), Darwin)
|
||||||
@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)"
|
@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:
|
||||||
@echo "+ $@"
|
@echo "+ $@"
|
||||||
@test -z "$$(golint ./... | grep -v .pb. | grep -v Godeps/_workspace/src/ | tee /dev/stderr)"
|
@test -z "$$(golint ./... | grep -v .pb. | grep -v Godeps/_workspace/src/ | tee /dev/stderr)"
|
||||||
|
|
||||||
build: go_version
|
# Requires that the following:
|
||||||
|
# go get -u github.com/client9/misspell/cmd/misspell
|
||||||
|
#
|
||||||
|
# be run first
|
||||||
|
|
||||||
|
# misspell target, don't include Godeps, binaries, python tests, or git files
|
||||||
|
misspell:
|
||||||
|
@echo "+ $@"
|
||||||
|
@test -z "$$(find . -name '*' | grep -v Godeps/_workspace/src/ | grep -v bin/ | grep -v misc/ | grep -v .git/ | xargs misspell | tee /dev/stderr)"
|
||||||
|
|
||||||
|
build:
|
||||||
@echo "+ $@"
|
@echo "+ $@"
|
||||||
@go build -tags "${NOTARY_BUILDTAGS}" -v ${GO_LDFLAGS} ./...
|
@go build -tags "${NOTARY_BUILDTAGS}" -v ${GO_LDFLAGS} ./...
|
||||||
|
|
||||||
# When running `go test ./...`, it runs all the suites in parallel, which causes
|
# When running `go test ./...`, it runs all the suites in parallel, which causes
|
||||||
# problems when running with a yubikey
|
# problems when running with a yubikey
|
||||||
test: TESTOPTS =
|
test: TESTOPTS =
|
||||||
test: go_version
|
test:
|
||||||
@echo Note: when testing with a yubikey plugged in, make sure to include 'TESTOPTS="-p 1"'
|
@echo Note: when testing with a yubikey plugged in, make sure to include 'TESTOPTS="-p 1"'
|
||||||
@echo "+ $@ $(TESTOPTS)"
|
@echo "+ $@ $(TESTOPTS)"
|
||||||
@echo
|
@echo
|
||||||
|
@ -121,7 +139,7 @@ define gocover
|
||||||
$(GO_EXC) test $(OPTS) $(TESTOPTS) -covermode="$(COVERMODE)" -coverprofile="$(COVERDIR)/$(subst /,-,$(1)).$(subst $(_space),.,$(NOTARY_BUILDTAGS)).coverage.txt" "$(1)" || exit 1;
|
$(GO_EXC) test $(OPTS) $(TESTOPTS) -covermode="$(COVERMODE)" -coverprofile="$(COVERDIR)/$(subst /,-,$(1)).$(subst $(_space),.,$(NOTARY_BUILDTAGS)).coverage.txt" "$(1)" || exit 1;
|
||||||
endef
|
endef
|
||||||
|
|
||||||
gen-cover: go_version
|
gen-cover:
|
||||||
@mkdir -p "$(COVERDIR)"
|
@mkdir -p "$(COVERDIR)"
|
||||||
$(foreach PKG,$(PKGS),$(call gocover,$(PKG)))
|
$(foreach PKG,$(PKGS),$(call gocover,$(PKG)))
|
||||||
rm -f "$(COVERDIR)"/*testutils*.coverage.txt
|
rm -f "$(COVERDIR)"/*testutils*.coverage.txt
|
||||||
|
@ -150,7 +168,10 @@ covmerge:
|
||||||
clean-protos:
|
clean-protos:
|
||||||
@rm proto/*.pb.go
|
@rm proto/*.pb.go
|
||||||
|
|
||||||
binaries: go_version ${PREFIX}/bin/notary-server ${PREFIX}/bin/notary ${PREFIX}/bin/notary-signer
|
binaries: ${PREFIX}/bin/notary-server ${PREFIX}/bin/notary ${PREFIX}/bin/notary-signer
|
||||||
|
@echo "+ $@"
|
||||||
|
|
||||||
|
static: ${PREFIX}/bin/static/notary-server ${PREFIX}/bin/static/notary-signer
|
||||||
@echo "+ $@"
|
@echo "+ $@"
|
||||||
|
|
||||||
define template
|
define template
|
||||||
|
@ -158,7 +179,7 @@ mkdir -p ${PREFIX}/cross/$(1)/$(2);
|
||||||
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;
|
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;
|
||||||
endef
|
endef
|
||||||
|
|
||||||
cross: go_version
|
cross:
|
||||||
$(foreach GOARCH,$(GOARCHS),$(foreach GOOS,$(GOOSES),$(call template,$(GOOS),$(GOARCH))))
|
$(foreach GOARCH,$(GOARCHS),$(foreach GOOS,$(GOOSES),$(call template,$(GOOS),$(GOARCH))))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
# Notary [![Circle CI](https://circleci.com/gh/docker/notary/tree/master.svg?style=shield)](https://circleci.com/gh/docker/notary/tree/master)
|
# Notary
|
||||||
|
[![Circle CI](https://circleci.com/gh/docker/notary/tree/master.svg?style=shield)](https://circleci.com/gh/docker/notary/tree/master) [![CodeCov](https://codecov.io/github/docker/notary/coverage.svg?branch=master)](https://codecov.io/github/docker/notary)
|
||||||
|
|
||||||
The Notary project comprises a [server](cmd/notary-server) and a [client](cmd/notary) for running and interacting
|
The Notary project comprises a [server](cmd/notary-server) and a [client](cmd/notary) for running and interacting
|
||||||
with trusted collections.
|
with trusted collections.
|
||||||
|
|
11
vendor/src/github.com/docker/notary/circle.yml
vendored
11
vendor/src/github.com/docker/notary/circle.yml
vendored
|
@ -6,7 +6,7 @@ machine:
|
||||||
|
|
||||||
post:
|
post:
|
||||||
# Install many go versions
|
# Install many go versions
|
||||||
- gvm install go1.5.1 -B --name=stable
|
- gvm install go1.6 -B --name=stable
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
# Convenient shortcuts to "common" locations
|
# Convenient shortcuts to "common" locations
|
||||||
|
@ -37,10 +37,11 @@ dependencies:
|
||||||
pwd: $BASE_STABLE
|
pwd: $BASE_STABLE
|
||||||
|
|
||||||
post:
|
post:
|
||||||
# For the stable go version, additionally install linting tools
|
# For the stable go version, additionally install linting and misspell tools
|
||||||
- >
|
- >
|
||||||
gvm use stable &&
|
gvm use stable &&
|
||||||
go get github.com/golang/lint/golint
|
go get github.com/golang/lint/golint &&
|
||||||
|
go get -u github.com/client9/misspell/cmd/misspell
|
||||||
test:
|
test:
|
||||||
pre:
|
pre:
|
||||||
# Output the go versions we are going to test
|
# Output the go versions we are going to test
|
||||||
|
@ -62,6 +63,10 @@ test:
|
||||||
- gvm use stable && make lint:
|
- gvm use stable && make lint:
|
||||||
pwd: $BASE_STABLE
|
pwd: $BASE_STABLE
|
||||||
|
|
||||||
|
# MISSPELL
|
||||||
|
- gvm use stable && make misspell:
|
||||||
|
pwd: $BASE_STABLE
|
||||||
|
|
||||||
override:
|
override:
|
||||||
# Test stable, and report
|
# Test stable, and report
|
||||||
# hacking this to be parallel
|
# hacking this to be parallel
|
||||||
|
|
|
@ -17,7 +17,7 @@ const (
|
||||||
// Types for TufChanges are namespaced by the Role they
|
// Types for TufChanges are namespaced by the Role they
|
||||||
// are relevant for. The Root and Targets roles are the
|
// are relevant for. The Root and Targets roles are the
|
||||||
// only ones for which user action can cause a change, as
|
// only ones for which user action can cause a change, as
|
||||||
// all changes in Snapshot and Timestamp are programatically
|
// all changes in Snapshot and Timestamp are programmatically
|
||||||
// generated base on Root and Targets changes.
|
// generated base on Root and Targets changes.
|
||||||
const (
|
const (
|
||||||
TypeRootRole = "role"
|
TypeRootRole = "role"
|
||||||
|
@ -82,14 +82,13 @@ func (c TufChange) Content() []byte {
|
||||||
// this includes creating a delegations. This format is used to avoid
|
// this includes creating a delegations. This format is used to avoid
|
||||||
// unexpected race conditions between humans modifying the same delegation
|
// unexpected race conditions between humans modifying the same delegation
|
||||||
type TufDelegation struct {
|
type TufDelegation struct {
|
||||||
NewName string `json:"new_name,omitempty"`
|
NewName string `json:"new_name,omitempty"`
|
||||||
NewThreshold int `json:"threshold, omitempty"`
|
NewThreshold int `json:"threshold, omitempty"`
|
||||||
AddKeys data.KeyList `json:"add_keys, omitempty"`
|
AddKeys data.KeyList `json:"add_keys, omitempty"`
|
||||||
RemoveKeys []string `json:"remove_keys,omitempty"`
|
RemoveKeys []string `json:"remove_keys,omitempty"`
|
||||||
AddPaths []string `json:"add_paths,omitempty"`
|
AddPaths []string `json:"add_paths,omitempty"`
|
||||||
RemovePaths []string `json:"remove_paths,omitempty"`
|
RemovePaths []string `json:"remove_paths,omitempty"`
|
||||||
AddPathHashPrefixes []string `json:"add_prefixes,omitempty"`
|
ClearAllPaths bool `json:"clear_paths,omitempty"`
|
||||||
RemovePathHashPrefixes []string `json:"remove_prefixes,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToNewRole creates a fresh role object from the TufDelegation data
|
// ToNewRole creates a fresh role object from the TufDelegation data
|
||||||
|
@ -98,5 +97,5 @@ func (td TufDelegation) ToNewRole(scope string) (*data.Role, error) {
|
||||||
if td.NewName != "" {
|
if td.NewName != "" {
|
||||||
name = td.NewName
|
name = td.NewName
|
||||||
}
|
}
|
||||||
return data.NewRole(name, td.NewThreshold, td.AddKeys.IDs(), td.AddPaths, td.AddPathHashPrefixes)
|
return data.NewRole(name, td.NewThreshold, td.AddKeys.IDs(), td.AddPaths)
|
||||||
}
|
}
|
||||||
|
|
308
vendor/src/github.com/docker/notary/client/client.go
vendored
308
vendor/src/github.com/docker/notary/client/client.go
vendored
|
@ -9,6 +9,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
|
@ -20,23 +21,13 @@ import (
|
||||||
"github.com/docker/notary/tuf"
|
"github.com/docker/notary/tuf"
|
||||||
tufclient "github.com/docker/notary/tuf/client"
|
tufclient "github.com/docker/notary/tuf/client"
|
||||||
"github.com/docker/notary/tuf/data"
|
"github.com/docker/notary/tuf/data"
|
||||||
"github.com/docker/notary/tuf/keys"
|
|
||||||
"github.com/docker/notary/tuf/signed"
|
"github.com/docker/notary/tuf/signed"
|
||||||
"github.com/docker/notary/tuf/store"
|
"github.com/docker/notary/tuf/store"
|
||||||
)
|
"github.com/docker/notary/tuf/utils"
|
||||||
|
|
||||||
const (
|
|
||||||
maxSize = 5 << 20
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
data.SetDefaultExpiryTimes(
|
data.SetDefaultExpiryTimes(notary.NotaryDefaultExpiries)
|
||||||
map[string]int{
|
|
||||||
"root": 3650,
|
|
||||||
"targets": 1095,
|
|
||||||
"snapshot": 1095,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrRepoNotInitialized is returned when trying to publish an uninitialized
|
// ErrRepoNotInitialized is returned when trying to publish an uninitialized
|
||||||
|
@ -118,7 +109,6 @@ func repositoryFromKeystores(baseDir, gun, baseURL string, rt http.RoundTripper,
|
||||||
nRepo.tufRepoPath,
|
nRepo.tufRepoPath,
|
||||||
"metadata",
|
"metadata",
|
||||||
"json",
|
"json",
|
||||||
"",
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -218,11 +208,16 @@ func (r *NotaryRepository) Initialize(rootKeyID string, serverManagedRoles ...st
|
||||||
return fmt.Errorf("invalid format for root key: %s", privKey.Algorithm())
|
return fmt.Errorf("invalid format for root key: %s", privKey.Algorithm())
|
||||||
}
|
}
|
||||||
|
|
||||||
kdb := keys.NewDB()
|
var (
|
||||||
err = addKeyForRole(kdb, data.CanonicalRootRole, rootKey)
|
rootRole = data.NewBaseRole(
|
||||||
if err != nil {
|
data.CanonicalRootRole,
|
||||||
return err
|
notary.MinThreshold,
|
||||||
}
|
rootKey,
|
||||||
|
)
|
||||||
|
timestampRole data.BaseRole
|
||||||
|
snapshotRole data.BaseRole
|
||||||
|
targetsRole data.BaseRole
|
||||||
|
)
|
||||||
|
|
||||||
// we want to create all the local keys first so we don't have to
|
// we want to create all the local keys first so we don't have to
|
||||||
// make unnecessary network calls
|
// make unnecessary network calls
|
||||||
|
@ -232,8 +227,19 @@ func (r *NotaryRepository) Initialize(rootKeyID string, serverManagedRoles ...st
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := addKeyForRole(kdb, role, key); err != nil {
|
switch role {
|
||||||
return err
|
case data.CanonicalSnapshotRole:
|
||||||
|
snapshotRole = data.NewBaseRole(
|
||||||
|
role,
|
||||||
|
notary.MinThreshold,
|
||||||
|
key,
|
||||||
|
)
|
||||||
|
case data.CanonicalTargetsRole:
|
||||||
|
targetsRole = data.NewBaseRole(
|
||||||
|
role,
|
||||||
|
notary.MinThreshold,
|
||||||
|
key,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, role := range remotelyManagedKeys {
|
for _, role := range remotelyManagedKeys {
|
||||||
|
@ -244,14 +250,31 @@ func (r *NotaryRepository) Initialize(rootKeyID string, serverManagedRoles ...st
|
||||||
}
|
}
|
||||||
logrus.Debugf("got remote %s %s key with keyID: %s",
|
logrus.Debugf("got remote %s %s key with keyID: %s",
|
||||||
role, key.Algorithm(), key.ID())
|
role, key.Algorithm(), key.ID())
|
||||||
if err := addKeyForRole(kdb, role, key); err != nil {
|
switch role {
|
||||||
return err
|
case data.CanonicalSnapshotRole:
|
||||||
|
snapshotRole = data.NewBaseRole(
|
||||||
|
role,
|
||||||
|
notary.MinThreshold,
|
||||||
|
key,
|
||||||
|
)
|
||||||
|
case data.CanonicalTimestampRole:
|
||||||
|
timestampRole = data.NewBaseRole(
|
||||||
|
role,
|
||||||
|
notary.MinThreshold,
|
||||||
|
key,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
r.tufRepo = tuf.NewRepo(kdb, r.CryptoService)
|
r.tufRepo = tuf.NewRepo(r.CryptoService)
|
||||||
|
|
||||||
err = r.tufRepo.InitRoot(false)
|
err = r.tufRepo.InitRoot(
|
||||||
|
rootRole,
|
||||||
|
timestampRole,
|
||||||
|
snapshotRole,
|
||||||
|
targetsRole,
|
||||||
|
false,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Debug("Error on InitRoot: ", err.Error())
|
logrus.Debug("Error on InitRoot: ", err.Error())
|
||||||
return err
|
return err
|
||||||
|
@ -305,96 +328,6 @@ func addChange(cl *changelist.FileChangelist, c changelist.Change, roles ...stri
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddDelegation creates a new changelist entry to add a delegation to the repository
|
|
||||||
// when the changelist gets applied at publish time. This does not do any validation
|
|
||||||
// other than checking the name of the delegation to add - all that will happen
|
|
||||||
// at publish time.
|
|
||||||
func (r *NotaryRepository) AddDelegation(name string, threshold int,
|
|
||||||
delegationKeys []data.PublicKey, paths []string) error {
|
|
||||||
|
|
||||||
if !data.IsDelegation(name) {
|
|
||||||
return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"}
|
|
||||||
}
|
|
||||||
|
|
||||||
cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer cl.Close()
|
|
||||||
|
|
||||||
logrus.Debugf(`Adding delegation "%s" with threshold %d, and %d keys\n`,
|
|
||||||
name, threshold, len(delegationKeys))
|
|
||||||
|
|
||||||
tdJSON, err := json.Marshal(&changelist.TufDelegation{
|
|
||||||
NewThreshold: threshold,
|
|
||||||
AddKeys: data.KeyList(delegationKeys),
|
|
||||||
AddPaths: paths,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
template := changelist.NewTufChange(
|
|
||||||
changelist.ActionCreate,
|
|
||||||
name,
|
|
||||||
changelist.TypeTargetsDelegation,
|
|
||||||
"", // no path
|
|
||||||
tdJSON,
|
|
||||||
)
|
|
||||||
|
|
||||||
return addChange(cl, template, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveDelegation creates a new changelist entry to remove a delegation from
|
|
||||||
// the repository when the changelist gets applied at publish time.
|
|
||||||
// This does not validate that the delegation exists, since one might exist
|
|
||||||
// after applying all changes.
|
|
||||||
func (r *NotaryRepository) RemoveDelegation(name string, keyIDs, paths []string, removeAll bool) error {
|
|
||||||
|
|
||||||
if !data.IsDelegation(name) {
|
|
||||||
return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"}
|
|
||||||
}
|
|
||||||
|
|
||||||
cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer cl.Close()
|
|
||||||
|
|
||||||
logrus.Debugf(`Removing delegation "%s"\n`, name)
|
|
||||||
var template *changelist.TufChange
|
|
||||||
|
|
||||||
// We use the Delete action only for force removal, Update is used for removing individual keys and paths
|
|
||||||
if removeAll {
|
|
||||||
template = changelist.NewTufChange(
|
|
||||||
changelist.ActionDelete,
|
|
||||||
name,
|
|
||||||
changelist.TypeTargetsDelegation,
|
|
||||||
"", // no path
|
|
||||||
nil, // deleting role, no data needed
|
|
||||||
)
|
|
||||||
|
|
||||||
} else {
|
|
||||||
tdJSON, err := json.Marshal(&changelist.TufDelegation{
|
|
||||||
RemoveKeys: keyIDs,
|
|
||||||
RemovePaths: paths,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
template = changelist.NewTufChange(
|
|
||||||
changelist.ActionUpdate,
|
|
||||||
name,
|
|
||||||
changelist.TypeTargetsDelegation,
|
|
||||||
"", // no path
|
|
||||||
tdJSON,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return addChange(cl, template, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddTarget creates new changelist entries to add a target to the given roles
|
// AddTarget creates new changelist entries to add a target to the given roles
|
||||||
// in the repository when the changelist gets applied at publish time.
|
// in the repository when the changelist gets applied at publish time.
|
||||||
// If roles are unspecified, the default role is "targets".
|
// If roles are unspecified, the default role is "targets".
|
||||||
|
@ -452,10 +385,24 @@ func (r *NotaryRepository) ListTargets(roles ...string) ([]*TargetWithRole, erro
|
||||||
}
|
}
|
||||||
targets := make(map[string]*TargetWithRole)
|
targets := make(map[string]*TargetWithRole)
|
||||||
for _, role := range roles {
|
for _, role := range roles {
|
||||||
// we don't need to do anything special with removing role from
|
// Define an array of roles to skip for this walk (see IMPORTANT comment above)
|
||||||
// roles because listSubtree always processes role and only excludes
|
skipRoles := utils.StrSliceRemove(roles, role)
|
||||||
// descendant delegations that appear in roles.
|
|
||||||
r.listSubtree(targets, role, roles...)
|
// Define a visitor function to populate the targets map in priority order
|
||||||
|
listVisitorFunc := func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} {
|
||||||
|
// We found targets so we should try to add them to our targets map
|
||||||
|
for targetName, targetMeta := range tgt.Signed.Targets {
|
||||||
|
// Follow the priority by not overriding previously set targets
|
||||||
|
// and check that this path is valid with this role
|
||||||
|
if _, ok := targets[targetName]; ok || !validRole.CheckPaths(targetName) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
targets[targetName] =
|
||||||
|
&TargetWithRole{Target: Target{Name: targetName, Hashes: targetMeta.Hashes, Length: targetMeta.Length}, Role: validRole.Name}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
r.tufRepo.WalkTargets("", role, listVisitorFunc, skipRoles...)
|
||||||
}
|
}
|
||||||
|
|
||||||
var targetList []*TargetWithRole
|
var targetList []*TargetWithRole
|
||||||
|
@ -466,34 +413,6 @@ func (r *NotaryRepository) ListTargets(roles ...string) ([]*TargetWithRole, erro
|
||||||
return targetList, nil
|
return targetList, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *NotaryRepository) listSubtree(targets map[string]*TargetWithRole, role string, exclude ...string) {
|
|
||||||
excl := make(map[string]bool)
|
|
||||||
for _, r := range exclude {
|
|
||||||
excl[r] = true
|
|
||||||
}
|
|
||||||
roles := []string{role}
|
|
||||||
for len(roles) > 0 {
|
|
||||||
role = roles[0]
|
|
||||||
roles = roles[1:]
|
|
||||||
tgts, ok := r.tufRepo.Targets[role]
|
|
||||||
if !ok {
|
|
||||||
// not every role has to exist
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for name, meta := range tgts.Signed.Targets {
|
|
||||||
if _, ok := targets[name]; !ok {
|
|
||||||
targets[name] = &TargetWithRole{
|
|
||||||
Target: Target{Name: name, Hashes: meta.Hashes, Length: meta.Length}, Role: role}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, d := range tgts.Signed.Delegations.Roles {
|
|
||||||
if !excl[d.Name] {
|
|
||||||
roles = append(roles, d.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTargetByName returns a target given a name. If no roles are passed
|
// GetTargetByName returns a target given a name. If no roles are passed
|
||||||
// it uses the targets role and does a search of the entire delegation
|
// it uses the targets role and does a search of the entire delegation
|
||||||
// graph, finding the first entry in a breadth first search of the delegations.
|
// 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
|
||||||
// will be returned
|
// will be returned
|
||||||
// See the IMPORTANT section on ListTargets above. Those roles also apply here.
|
// See the IMPORTANT section on ListTargets above. Those roles also apply here.
|
||||||
func (r *NotaryRepository) GetTargetByName(name string, roles ...string) (*TargetWithRole, error) {
|
func (r *NotaryRepository) GetTargetByName(name string, roles ...string) (*TargetWithRole, error) {
|
||||||
c, err := r.Update(false)
|
_, err := r.Update(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -510,11 +429,30 @@ func (r *NotaryRepository) GetTargetByName(name string, roles ...string) (*Targe
|
||||||
if len(roles) == 0 {
|
if len(roles) == 0 {
|
||||||
roles = append(roles, data.CanonicalTargetsRole)
|
roles = append(roles, data.CanonicalTargetsRole)
|
||||||
}
|
}
|
||||||
|
var resultMeta data.FileMeta
|
||||||
|
var resultRoleName string
|
||||||
|
var foundTarget bool
|
||||||
for _, role := range roles {
|
for _, role := range roles {
|
||||||
meta, foundRole := c.TargetMeta(role, name, roles...)
|
// Define an array of roles to skip for this walk (see IMPORTANT comment above)
|
||||||
if meta != nil {
|
skipRoles := utils.StrSliceRemove(roles, role)
|
||||||
return &TargetWithRole{
|
|
||||||
Target: Target{Name: name, Hashes: meta.Hashes, Length: meta.Length}, Role: foundRole}, nil
|
// Define a visitor function to find the specified target
|
||||||
|
getTargetVisitorFunc := func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} {
|
||||||
|
if tgt == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// We found the target and validated path compatibility in our walk,
|
||||||
|
// so we should stop our walk and set the resultMeta and resultRoleName variables
|
||||||
|
if resultMeta, foundTarget = tgt.Signed.Targets[name]; foundTarget {
|
||||||
|
resultRoleName = validRole.Name
|
||||||
|
return tuf.StopWalk{}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err = r.tufRepo.WalkTargets(name, role, getTargetVisitorFunc, skipRoles...)
|
||||||
|
// Check that we didn't error, and that we assigned to our target
|
||||||
|
if err == nil && foundTarget {
|
||||||
|
return &TargetWithRole{Target: Target{Name: name, Hashes: resultMeta.Hashes, Length: resultMeta.Length}, Role: resultRoleName}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("No trust data for %s", name)
|
return nil, fmt.Errorf("No trust data for %s", name)
|
||||||
|
@ -532,45 +470,6 @@ func (r *NotaryRepository) GetChangelist() (changelist.Changelist, error) {
|
||||||
return cl, nil
|
return cl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDelegationRoles returns the keys and roles of the repository's delegations
|
|
||||||
func (r *NotaryRepository) GetDelegationRoles() ([]*data.Role, error) {
|
|
||||||
// Update state of the repo to latest
|
|
||||||
if _, err := r.Update(false); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// All top level delegations (ex: targets/level1) are stored exclusively in targets.json
|
|
||||||
targets, ok := r.tufRepo.Targets[data.CanonicalTargetsRole]
|
|
||||||
if !ok {
|
|
||||||
return nil, store.ErrMetaNotFound{Resource: data.CanonicalTargetsRole}
|
|
||||||
}
|
|
||||||
|
|
||||||
allDelegations := targets.Signed.Delegations.Roles
|
|
||||||
|
|
||||||
// make a copy for traversing nested delegations
|
|
||||||
delegationsList := make([]*data.Role, len(allDelegations))
|
|
||||||
copy(delegationsList, allDelegations)
|
|
||||||
|
|
||||||
// Now traverse to lower level delegations (ex: targets/level1/level2)
|
|
||||||
for len(delegationsList) > 0 {
|
|
||||||
// Pop off first delegation to traverse
|
|
||||||
delegation := delegationsList[0]
|
|
||||||
delegationsList = delegationsList[1:]
|
|
||||||
|
|
||||||
// Get metadata
|
|
||||||
delegationMeta, ok := r.tufRepo.Targets[delegation.Name]
|
|
||||||
// If we get an error, don't try to traverse further into this subtree because it doesn't exist or is malformed
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add nested delegations to return list and exploration list
|
|
||||||
allDelegations = append(allDelegations, delegationMeta.Signed.Delegations.Roles...)
|
|
||||||
delegationsList = append(delegationsList, delegationMeta.Signed.Delegations.Roles...)
|
|
||||||
}
|
|
||||||
return allDelegations, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RoleWithSignatures is a Role with its associated signatures
|
// RoleWithSignatures is a Role with its associated signatures
|
||||||
type RoleWithSignatures struct {
|
type RoleWithSignatures struct {
|
||||||
Signatures []data.Signature
|
Signatures []data.Signature
|
||||||
|
@ -604,7 +503,7 @@ func (r *NotaryRepository) ListRoles() ([]RoleWithSignatures, error) {
|
||||||
case data.CanonicalTimestampRole:
|
case data.CanonicalTimestampRole:
|
||||||
roleWithSig.Signatures = r.tufRepo.Timestamp.Signatures
|
roleWithSig.Signatures = r.tufRepo.Timestamp.Signatures
|
||||||
default:
|
default:
|
||||||
// If the role isn't a delegation, we should error -- this is only possible if we have invalid keyDB state
|
// If the role isn't a delegation, we should error -- this is only possible if we have invalid state
|
||||||
if !data.IsDelegation(role.Name) {
|
if !data.IsDelegation(role.Name) {
|
||||||
return nil, data.ErrInvalidRole{Role: role.Name, Reason: "invalid role name"}
|
return nil, data.ErrInvalidRole{Role: role.Name, Reason: "invalid role name"}
|
||||||
}
|
}
|
||||||
|
@ -705,7 +604,7 @@ func (r *NotaryRepository) Publish() error {
|
||||||
r.tufRepo, data.CanonicalSnapshotRole)
|
r.tufRepo, data.CanonicalSnapshotRole)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// Only update the snapshot if we've sucessfully signed it.
|
// Only update the snapshot if we've successfully signed it.
|
||||||
updatedFiles[data.CanonicalSnapshotRole] = snapshotJSON
|
updatedFiles[data.CanonicalSnapshotRole] = snapshotJSON
|
||||||
} else if _, ok := err.(signed.ErrNoKeys); ok {
|
} else if _, ok := err.(signed.ErrNoKeys); ok {
|
||||||
// If signing fails due to us not having the snapshot key, then
|
// If signing fails due to us not having the snapshot key, then
|
||||||
|
@ -743,11 +642,10 @@ func (r *NotaryRepository) Publish() error {
|
||||||
// This can also be unified with some cache reading tools from tuf/client.
|
// This can also be unified with some cache reading tools from tuf/client.
|
||||||
// This assumes that bootstrapRepo is only used by Publish()
|
// This assumes that bootstrapRepo is only used by Publish()
|
||||||
func (r *NotaryRepository) bootstrapRepo() error {
|
func (r *NotaryRepository) bootstrapRepo() error {
|
||||||
kdb := keys.NewDB()
|
tufRepo := tuf.NewRepo(r.CryptoService)
|
||||||
tufRepo := tuf.NewRepo(kdb, r.CryptoService)
|
|
||||||
|
|
||||||
logrus.Debugf("Loading trusted collection.")
|
logrus.Debugf("Loading trusted collection.")
|
||||||
rootJSON, err := r.fileStore.GetMeta("root", 0)
|
rootJSON, err := r.fileStore.GetMeta("root", -1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -760,7 +658,7 @@ func (r *NotaryRepository) bootstrapRepo() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
targetsJSON, err := r.fileStore.GetMeta("targets", 0)
|
targetsJSON, err := r.fileStore.GetMeta("targets", -1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -771,7 +669,7 @@ func (r *NotaryRepository) bootstrapRepo() error {
|
||||||
}
|
}
|
||||||
tufRepo.SetTargets("targets", targets)
|
tufRepo.SetTargets("targets", targets)
|
||||||
|
|
||||||
snapshotJSON, err := r.fileStore.GetMeta("snapshot", 0)
|
snapshotJSON, err := r.fileStore.GetMeta("snapshot", -1)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
snapshot := &data.SignedSnapshot{}
|
snapshot := &data.SignedSnapshot{}
|
||||||
err = json.Unmarshal(snapshotJSON, snapshot)
|
err = json.Unmarshal(snapshotJSON, snapshot)
|
||||||
|
@ -854,7 +752,10 @@ func (r *NotaryRepository) Update(forWrite bool) (*tufclient.Client, error) {
|
||||||
}
|
}
|
||||||
err = c.Update()
|
err = c.Update()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if notFound, ok := err.(store.ErrMetaNotFound); ok && notFound.Resource == data.CanonicalRootRole {
|
// notFound.Resource may include a checksum so when the role is root,
|
||||||
|
// it will be root.json or root.<checksum>.json. Therefore best we can
|
||||||
|
// do it match a "root." prefix
|
||||||
|
if notFound, ok := err.(store.ErrMetaNotFound); ok && strings.HasPrefix(notFound.Resource, data.CanonicalRootRole+".") {
|
||||||
return nil, r.errRepositoryNotExist()
|
return nil, r.errRepositoryNotExist()
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -876,7 +777,7 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl
|
||||||
// try to read root from cache first. We will trust this root
|
// try to read root from cache first. We will trust this root
|
||||||
// until we detect a problem during update which will cause
|
// until we detect a problem during update which will cause
|
||||||
// us to download a new root and perform a rotation.
|
// us to download a new root and perform a rotation.
|
||||||
rootJSON, cachedRootErr := r.fileStore.GetMeta("root", maxSize)
|
rootJSON, cachedRootErr := r.fileStore.GetMeta("root", -1)
|
||||||
|
|
||||||
if cachedRootErr == nil {
|
if cachedRootErr == nil {
|
||||||
signedRoot, cachedRootErr = r.validateRoot(rootJSON)
|
signedRoot, cachedRootErr = r.validateRoot(rootJSON)
|
||||||
|
@ -890,7 +791,8 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl
|
||||||
// checking for initialization of the repo).
|
// checking for initialization of the repo).
|
||||||
|
|
||||||
// if remote store successfully set up, try and get root from remote
|
// if remote store successfully set up, try and get root from remote
|
||||||
tmpJSON, err := remote.GetMeta("root", maxSize)
|
// We don't have any local data to determine the size of root, so try the maximum (though it is restricted at 100MB)
|
||||||
|
tmpJSON, err := remote.GetMeta("root", -1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// we didn't have a root in cache and were unable to load one from
|
// we didn't have a root in cache and were unable to load one from
|
||||||
// the server. Nothing we can do but error.
|
// the server. Nothing we can do but error.
|
||||||
|
@ -912,8 +814,7 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kdb := keys.NewDB()
|
r.tufRepo = tuf.NewRepo(r.CryptoService)
|
||||||
r.tufRepo = tuf.NewRepo(kdb, r.CryptoService)
|
|
||||||
|
|
||||||
if signedRoot == nil {
|
if signedRoot == nil {
|
||||||
return nil, ErrRepoNotInitialized{}
|
return nil, ErrRepoNotInitialized{}
|
||||||
|
@ -927,7 +828,6 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl
|
||||||
return tufclient.NewClient(
|
return tufclient.NewClient(
|
||||||
r.tufRepo,
|
r.tufRepo,
|
||||||
remote,
|
remote,
|
||||||
kdb,
|
|
||||||
r.fileStore,
|
r.fileStore,
|
||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
|
@ -1020,7 +920,7 @@ func (r *NotaryRepository) DeleteTrustData() error {
|
||||||
if err := r.fileStore.RemoveAll(); err != nil {
|
if err := r.fileStore.RemoveAll(); err != nil {
|
||||||
return fmt.Errorf("error clearing TUF repo data: %v", err)
|
return fmt.Errorf("error clearing TUF repo data: %v", err)
|
||||||
}
|
}
|
||||||
r.tufRepo = tuf.NewRepo(nil, nil)
|
r.tufRepo = tuf.NewRepo(nil)
|
||||||
// Clear certificates
|
// Clear certificates
|
||||||
certificates, err := r.CertStore.GetCertificatesByCN(r.gun)
|
certificates, err := r.CertStore.GetCertificatesByCN(r.gun)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
294
vendor/src/github.com/docker/notary/client/delegations.go
vendored
Normal file
294
vendor/src/github.com/docker/notary/client/delegations.go
vendored
Normal file
|
@ -0,0 +1,294 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/notary"
|
||||||
|
"github.com/docker/notary/client/changelist"
|
||||||
|
"github.com/docker/notary/tuf/data"
|
||||||
|
"github.com/docker/notary/tuf/store"
|
||||||
|
"github.com/docker/notary/tuf/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddDelegation creates changelist entries to add provided delegation public keys and paths.
|
||||||
|
// This method composes AddDelegationRoleAndKeys and AddDelegationPaths (each creates one changelist if called).
|
||||||
|
func (r *NotaryRepository) AddDelegation(name string, delegationKeys []data.PublicKey, paths []string) error {
|
||||||
|
if len(delegationKeys) > 0 {
|
||||||
|
err := r.AddDelegationRoleAndKeys(name, delegationKeys)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(paths) > 0 {
|
||||||
|
err := r.AddDelegationPaths(name, paths)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddDelegationRoleAndKeys creates a changelist entry to add provided delegation public keys.
|
||||||
|
// This method is the simplest way to create a new delegation, because the delegation must have at least
|
||||||
|
// one key upon creation to be valid since we will reject the changelist while validating the threshold.
|
||||||
|
func (r *NotaryRepository) AddDelegationRoleAndKeys(name string, delegationKeys []data.PublicKey) error {
|
||||||
|
|
||||||
|
if !data.IsDelegation(name) {
|
||||||
|
return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"}
|
||||||
|
}
|
||||||
|
|
||||||
|
cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer cl.Close()
|
||||||
|
|
||||||
|
logrus.Debugf(`Adding delegation "%s" with threshold %d, and %d keys\n`,
|
||||||
|
name, notary.MinThreshold, len(delegationKeys))
|
||||||
|
|
||||||
|
// Defaulting to threshold of 1, since we don't allow for larger thresholds at the moment.
|
||||||
|
tdJSON, err := json.Marshal(&changelist.TufDelegation{
|
||||||
|
NewThreshold: notary.MinThreshold,
|
||||||
|
AddKeys: data.KeyList(delegationKeys),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
template := newCreateDelegationChange(name, tdJSON)
|
||||||
|
return addChange(cl, template, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddDelegationPaths creates a changelist entry to add provided paths to an existing delegation.
|
||||||
|
// This method cannot create a new delegation itself because the role must meet the key threshold upon creation.
|
||||||
|
func (r *NotaryRepository) AddDelegationPaths(name string, paths []string) error {
|
||||||
|
|
||||||
|
if !data.IsDelegation(name) {
|
||||||
|
return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"}
|
||||||
|
}
|
||||||
|
|
||||||
|
cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer cl.Close()
|
||||||
|
|
||||||
|
logrus.Debugf(`Adding %s paths to delegation %s\n`, paths, name)
|
||||||
|
|
||||||
|
tdJSON, err := json.Marshal(&changelist.TufDelegation{
|
||||||
|
AddPaths: paths,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
template := newCreateDelegationChange(name, tdJSON)
|
||||||
|
return addChange(cl, template, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveDelegationKeysAndPaths creates changelist entries to remove provided delegation key IDs and paths.
|
||||||
|
// This method composes RemoveDelegationPaths and RemoveDelegationKeys (each creates one changelist if called).
|
||||||
|
func (r *NotaryRepository) RemoveDelegationKeysAndPaths(name string, keyIDs, paths []string) error {
|
||||||
|
if len(paths) > 0 {
|
||||||
|
err := r.RemoveDelegationPaths(name, paths)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(keyIDs) > 0 {
|
||||||
|
err := r.RemoveDelegationKeys(name, keyIDs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveDelegationRole creates a changelist to remove all paths and keys from a role, and delete the role in its entirety.
|
||||||
|
func (r *NotaryRepository) RemoveDelegationRole(name string) error {
|
||||||
|
|
||||||
|
if !data.IsDelegation(name) {
|
||||||
|
return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"}
|
||||||
|
}
|
||||||
|
|
||||||
|
cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer cl.Close()
|
||||||
|
|
||||||
|
logrus.Debugf(`Removing delegation "%s"\n`, name)
|
||||||
|
|
||||||
|
template := newDeleteDelegationChange(name, nil)
|
||||||
|
return addChange(cl, template, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveDelegationPaths creates a changelist entry to remove provided paths from an existing delegation.
|
||||||
|
func (r *NotaryRepository) RemoveDelegationPaths(name string, paths []string) error {
|
||||||
|
|
||||||
|
if !data.IsDelegation(name) {
|
||||||
|
return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"}
|
||||||
|
}
|
||||||
|
|
||||||
|
cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer cl.Close()
|
||||||
|
|
||||||
|
logrus.Debugf(`Removing %s paths from delegation "%s"\n`, paths, name)
|
||||||
|
|
||||||
|
tdJSON, err := json.Marshal(&changelist.TufDelegation{
|
||||||
|
RemovePaths: paths,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
template := newUpdateDelegationChange(name, tdJSON)
|
||||||
|
return addChange(cl, template, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveDelegationKeys creates a changelist entry to remove provided keys from an existing delegation.
|
||||||
|
// When this changelist is applied, if the specified keys are the only keys left in the role,
|
||||||
|
// the role itself will be deleted in its entirety.
|
||||||
|
func (r *NotaryRepository) RemoveDelegationKeys(name string, keyIDs []string) error {
|
||||||
|
|
||||||
|
if !data.IsDelegation(name) {
|
||||||
|
return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"}
|
||||||
|
}
|
||||||
|
|
||||||
|
cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer cl.Close()
|
||||||
|
|
||||||
|
logrus.Debugf(`Removing %s keys from delegation "%s"\n`, keyIDs, name)
|
||||||
|
|
||||||
|
tdJSON, err := json.Marshal(&changelist.TufDelegation{
|
||||||
|
RemoveKeys: keyIDs,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
template := newUpdateDelegationChange(name, tdJSON)
|
||||||
|
return addChange(cl, template, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearDelegationPaths creates a changelist entry to remove all paths from an existing delegation.
|
||||||
|
func (r *NotaryRepository) ClearDelegationPaths(name string) error {
|
||||||
|
|
||||||
|
if !data.IsDelegation(name) {
|
||||||
|
return data.ErrInvalidRole{Role: name, Reason: "invalid delegation role name"}
|
||||||
|
}
|
||||||
|
|
||||||
|
cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer cl.Close()
|
||||||
|
|
||||||
|
logrus.Debugf(`Removing all paths from delegation "%s"\n`, name)
|
||||||
|
|
||||||
|
tdJSON, err := json.Marshal(&changelist.TufDelegation{
|
||||||
|
ClearAllPaths: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
template := newUpdateDelegationChange(name, tdJSON)
|
||||||
|
return addChange(cl, template, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newUpdateDelegationChange(name string, content []byte) *changelist.TufChange {
|
||||||
|
return changelist.NewTufChange(
|
||||||
|
changelist.ActionUpdate,
|
||||||
|
name,
|
||||||
|
changelist.TypeTargetsDelegation,
|
||||||
|
"", // no path for delegations
|
||||||
|
content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCreateDelegationChange(name string, content []byte) *changelist.TufChange {
|
||||||
|
return changelist.NewTufChange(
|
||||||
|
changelist.ActionCreate,
|
||||||
|
name,
|
||||||
|
changelist.TypeTargetsDelegation,
|
||||||
|
"", // no path for delegations
|
||||||
|
content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDeleteDelegationChange(name string, content []byte) *changelist.TufChange {
|
||||||
|
return changelist.NewTufChange(
|
||||||
|
changelist.ActionDelete,
|
||||||
|
name,
|
||||||
|
changelist.TypeTargetsDelegation,
|
||||||
|
"", // no path for delegations
|
||||||
|
content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDelegationRoles returns the keys and roles of the repository's delegations
|
||||||
|
// Also converts key IDs to canonical key IDs to keep consistent with signing prompts
|
||||||
|
func (r *NotaryRepository) GetDelegationRoles() ([]*data.Role, error) {
|
||||||
|
// Update state of the repo to latest
|
||||||
|
if _, err := r.Update(false); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// All top level delegations (ex: targets/level1) are stored exclusively in targets.json
|
||||||
|
_, ok := r.tufRepo.Targets[data.CanonicalTargetsRole]
|
||||||
|
if !ok {
|
||||||
|
return nil, store.ErrMetaNotFound{Resource: data.CanonicalTargetsRole}
|
||||||
|
}
|
||||||
|
|
||||||
|
// make a copy for traversing nested delegations
|
||||||
|
allDelegations := []*data.Role{}
|
||||||
|
|
||||||
|
// Define a visitor function to populate the delegations list and translate their key IDs to canonical IDs
|
||||||
|
delegationCanonicalListVisitor := func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} {
|
||||||
|
// For the return list, update with a copy that includes canonicalKeyIDs
|
||||||
|
// These aren't validated by the validRole
|
||||||
|
canonicalDelegations, err := translateDelegationsToCanonicalIDs(tgt.Signed.Delegations)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
allDelegations = append(allDelegations, canonicalDelegations...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err := r.tufRepo.WalkTargets("", "", delegationCanonicalListVisitor)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return allDelegations, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func translateDelegationsToCanonicalIDs(delegationInfo data.Delegations) ([]*data.Role, error) {
|
||||||
|
canonicalDelegations := make([]*data.Role, len(delegationInfo.Roles))
|
||||||
|
copy(canonicalDelegations, delegationInfo.Roles)
|
||||||
|
delegationKeys := delegationInfo.Keys
|
||||||
|
for i, delegation := range canonicalDelegations {
|
||||||
|
canonicalKeyIDs := []string{}
|
||||||
|
for _, keyID := range delegation.KeyIDs {
|
||||||
|
pubKey, ok := delegationKeys[keyID]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("Could not translate canonical key IDs for %s", delegation.Name)
|
||||||
|
}
|
||||||
|
canonicalKeyID, err := utils.CanonicalKeyID(pubKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not translate canonical key IDs for %s: %v", delegation.Name, err)
|
||||||
|
}
|
||||||
|
canonicalKeyIDs = append(canonicalKeyIDs, canonicalKeyID)
|
||||||
|
}
|
||||||
|
canonicalDelegations[i].KeyIDs = canonicalKeyIDs
|
||||||
|
}
|
||||||
|
return canonicalDelegations, nil
|
||||||
|
}
|
|
@ -12,8 +12,8 @@ import (
|
||||||
"github.com/docker/notary/client/changelist"
|
"github.com/docker/notary/client/changelist"
|
||||||
tuf "github.com/docker/notary/tuf"
|
tuf "github.com/docker/notary/tuf"
|
||||||
"github.com/docker/notary/tuf/data"
|
"github.com/docker/notary/tuf/data"
|
||||||
"github.com/docker/notary/tuf/keys"
|
|
||||||
"github.com/docker/notary/tuf/store"
|
"github.com/docker/notary/tuf/store"
|
||||||
|
"github.com/docker/notary/tuf/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Use this to initialize remote HTTPStores from the config settings
|
// Use this to initialize remote HTTPStores from the config settings
|
||||||
|
@ -22,7 +22,6 @@ func getRemoteStore(baseURL, gun string, rt http.RoundTripper) (store.RemoteStor
|
||||||
baseURL+"/v2/"+gun+"/_trust/tuf/",
|
baseURL+"/v2/"+gun+"/_trust/tuf/",
|
||||||
"",
|
"",
|
||||||
"json",
|
"json",
|
||||||
"",
|
|
||||||
"key",
|
"key",
|
||||||
rt,
|
rt,
|
||||||
)
|
)
|
||||||
|
@ -80,53 +79,51 @@ func changeTargetsDelegation(repo *tuf.Repo, c changelist.Change) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
r, err := repo.GetDelegation(c.Scope())
|
|
||||||
if _, ok := err.(data.ErrNoSuchRole); err != nil && !ok {
|
// Try to create brand new role or update one
|
||||||
// error that wasn't ErrNoSuchRole
|
// First add the keys, then the paths. We can only add keys and paths in this scenario
|
||||||
return err
|
err = repo.UpdateDelegationKeys(c.Scope(), td.AddKeys, []string{}, td.NewThreshold)
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
// role existed, attempt to merge paths and keys
|
|
||||||
if err := r.AddPaths(td.AddPaths); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return repo.UpdateDelegations(r, td.AddKeys)
|
|
||||||
}
|
|
||||||
// create brand new role
|
|
||||||
r, err = td.ToNewRole(c.Scope())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return repo.UpdateDelegations(r, td.AddKeys)
|
return repo.UpdateDelegationPaths(c.Scope(), td.AddPaths, []string{}, false)
|
||||||
case changelist.ActionUpdate:
|
case changelist.ActionUpdate:
|
||||||
td := changelist.TufDelegation{}
|
td := changelist.TufDelegation{}
|
||||||
err := json.Unmarshal(c.Content(), &td)
|
err := json.Unmarshal(c.Content(), &td)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
r, err := repo.GetDelegation(c.Scope())
|
delgRole, err := repo.GetDelegationRole(c.Scope())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We need to translate the keys from canonical ID to TUF ID for compatibility
|
||||||
|
canonicalToTUFID := make(map[string]string)
|
||||||
|
for tufID, pubKey := range delgRole.Keys {
|
||||||
|
canonicalID, err := utils.CanonicalKeyID(pubKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
canonicalToTUFID[canonicalID] = tufID
|
||||||
|
}
|
||||||
|
|
||||||
|
removeTUFKeyIDs := []string{}
|
||||||
|
for _, canonID := range td.RemoveKeys {
|
||||||
|
removeTUFKeyIDs = append(removeTUFKeyIDs, canonicalToTUFID[canonID])
|
||||||
|
}
|
||||||
|
|
||||||
// If we specify the only keys left delete the role, else just delete specified keys
|
// If we specify the only keys left delete the role, else just delete specified keys
|
||||||
if strings.Join(r.KeyIDs, ";") == strings.Join(td.RemoveKeys, ";") && len(td.AddKeys) == 0 {
|
if strings.Join(delgRole.ListKeyIDs(), ";") == strings.Join(removeTUFKeyIDs, ";") && len(td.AddKeys) == 0 {
|
||||||
r := data.Role{Name: c.Scope()}
|
return repo.DeleteDelegation(c.Scope())
|
||||||
return repo.DeleteDelegation(r)
|
|
||||||
}
|
}
|
||||||
// if we aren't deleting and the role exists, merge
|
err = repo.UpdateDelegationKeys(c.Scope(), td.AddKeys, removeTUFKeyIDs, td.NewThreshold)
|
||||||
if err := r.AddPaths(td.AddPaths); err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := r.AddPathHashPrefixes(td.AddPathHashPrefixes); err != nil {
|
return repo.UpdateDelegationPaths(c.Scope(), td.AddPaths, td.RemovePaths, td.ClearAllPaths)
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.RemoveKeys(td.RemoveKeys)
|
|
||||||
r.RemovePaths(td.RemovePaths)
|
|
||||||
r.RemovePathHashPrefixes(td.RemovePathHashPrefixes)
|
|
||||||
return repo.UpdateDelegations(r, td.AddKeys)
|
|
||||||
case changelist.ActionDelete:
|
case changelist.ActionDelete:
|
||||||
r := data.Role{Name: c.Scope()}
|
return repo.DeleteDelegation(c.Scope())
|
||||||
return repo.DeleteDelegation(r)
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unsupported action against delegations: %s", c.Action())
|
return fmt.Errorf("unsupported action against delegations: %s", c.Action())
|
||||||
}
|
}
|
||||||
|
@ -239,19 +236,6 @@ func getRemoteKey(url, gun, role string, rt http.RoundTripper) (data.PublicKey,
|
||||||
return pubKey, nil
|
return pubKey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// add a key to a KeyDB, and create a role for the key and add it.
|
|
||||||
func addKeyForRole(kdb *keys.KeyDB, role string, key data.PublicKey) error {
|
|
||||||
theRole, err := data.NewRole(role, 1, []string{key.ID()}, nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
kdb.AddKey(key)
|
|
||||||
if err := kdb.AddRole(theRole); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// signs and serializes the metadata for a canonical role in a tuf repo to JSON
|
// signs and serializes the metadata for a canonical role in a tuf repo to JSON
|
||||||
func serializeCanonicalRole(tufRepo *tuf.Repo, role string) (out []byte, err error) {
|
func serializeCanonicalRole(tufRepo *tuf.Repo, role string) (out []byte, err error) {
|
||||||
var s *data.Signed
|
var s *data.Signed
|
||||||
|
|
33
vendor/src/github.com/docker/notary/const.go
vendored
33
vendor/src/github.com/docker/notary/const.go
vendored
|
@ -1,7 +1,15 @@
|
||||||
package notary
|
package notary
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
// application wide constants
|
// application wide constants
|
||||||
const (
|
const (
|
||||||
|
// MaxDownloadSize is the maximum size we'll download for metadata if no limit is given
|
||||||
|
MaxDownloadSize int64 = 100 << 20
|
||||||
|
// MaxTimestampSize is the maximum size of timestamp metadata - 1MiB.
|
||||||
|
MaxTimestampSize int64 = 1 << 20
|
||||||
// MinRSABitSize is the minimum bit size for RSA keys allowed in notary
|
// MinRSABitSize is the minimum bit size for RSA keys allowed in notary
|
||||||
MinRSABitSize = 2048
|
MinRSABitSize = 2048
|
||||||
// MinThreshold requires a minimum of one threshold for roles; currently we do not support a higher threshold
|
// MinThreshold requires a minimum of one threshold for roles; currently we do not support a higher threshold
|
||||||
|
@ -14,4 +22,29 @@ const (
|
||||||
Sha256HexSize = 64
|
Sha256HexSize = 64
|
||||||
// TrustedCertsDir is the directory, under the notary repo base directory, where trusted certs are stored
|
// TrustedCertsDir is the directory, under the notary repo base directory, where trusted certs are stored
|
||||||
TrustedCertsDir = "trusted_certificates"
|
TrustedCertsDir = "trusted_certificates"
|
||||||
|
// PrivDir is the directory, under the notary repo base directory, where private keys are stored
|
||||||
|
PrivDir = "private"
|
||||||
|
// RootKeysSubdir is the subdirectory under PrivDir where root private keys are stored
|
||||||
|
RootKeysSubdir = "root_keys"
|
||||||
|
// NonRootKeysSubdir is the subdirectory under PrivDir where non-root private keys are stored
|
||||||
|
NonRootKeysSubdir = "tuf_keys"
|
||||||
|
|
||||||
|
// Day is a duration of one day
|
||||||
|
Day = 24 * time.Hour
|
||||||
|
Year = 365 * Day
|
||||||
|
|
||||||
|
// NotaryRootExpiry is the duration representing the expiry time of the Root role
|
||||||
|
NotaryRootExpiry = 10 * Year
|
||||||
|
NotaryTargetsExpiry = 3 * Year
|
||||||
|
NotarySnapshotExpiry = 3 * Year
|
||||||
|
NotaryTimestampExpiry = 14 * Day
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// NotaryDefaultExpiries is the construct used to configure the default expiry times of
|
||||||
|
// the various role files.
|
||||||
|
var NotaryDefaultExpiries = map[string]time.Duration{
|
||||||
|
"root": NotaryRootExpiry,
|
||||||
|
"targets": NotaryTargetsExpiry,
|
||||||
|
"snapshot": NotarySnapshotExpiry,
|
||||||
|
"timestamp": NotaryTimestampExpiry,
|
||||||
|
}
|
||||||
|
|
|
@ -11,8 +11,10 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/notary"
|
||||||
"github.com/docker/notary/passphrase"
|
"github.com/docker/notary/passphrase"
|
||||||
"github.com/docker/notary/trustmanager"
|
"github.com/docker/notary/trustmanager"
|
||||||
|
"github.com/docker/notary/tuf/data"
|
||||||
)
|
)
|
||||||
|
|
||||||
const zipMadeByUNIX = 3 << 8
|
const zipMadeByUNIX = 3 << 8
|
||||||
|
@ -31,14 +33,17 @@ var (
|
||||||
ErrNoKeysFoundForGUN = errors.New("no keys found for specified GUN")
|
ErrNoKeysFoundForGUN = errors.New("no keys found for specified GUN")
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExportRootKey exports the specified root key to an io.Writer in PEM format.
|
// ExportKey exports the specified private key to an io.Writer in PEM format.
|
||||||
// The key's existing encryption is preserved.
|
// The key's existing encryption is preserved.
|
||||||
func (cs *CryptoService) ExportRootKey(dest io.Writer, keyID string) error {
|
func (cs *CryptoService) ExportKey(dest io.Writer, keyID, role string) error {
|
||||||
var (
|
var (
|
||||||
pemBytes []byte
|
pemBytes []byte
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if role != data.CanonicalRootRole {
|
||||||
|
keyID = filepath.Join(cs.gun, keyID)
|
||||||
|
}
|
||||||
for _, ks := range cs.keyStores {
|
for _, ks := range cs.keyStores {
|
||||||
pemBytes, err = ks.ExportKey(keyID)
|
pemBytes, err = ks.ExportKey(keyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -59,9 +64,9 @@ func (cs *CryptoService) ExportRootKey(dest io.Writer, keyID string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExportRootKeyReencrypt exports the specified root key to an io.Writer in
|
// ExportKeyReencrypt exports the specified private key to an io.Writer in
|
||||||
// PEM format. The key is reencrypted with a new passphrase.
|
// PEM format. The key is reencrypted with a new passphrase.
|
||||||
func (cs *CryptoService) ExportRootKeyReencrypt(dest io.Writer, keyID string, newPassphraseRetriever passphrase.Retriever) error {
|
func (cs *CryptoService) ExportKeyReencrypt(dest io.Writer, keyID string, newPassphraseRetriever passphrase.Retriever) error {
|
||||||
privateKey, role, err := cs.GetPrivateKey(keyID)
|
privateKey, role, err := cs.GetPrivateKey(keyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -103,14 +108,41 @@ func (cs *CryptoService) ImportRootKey(source io.Reader) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
return cs.ImportRoleKey(pemBytes, data.CanonicalRootRole, nil)
|
||||||
|
}
|
||||||
|
|
||||||
if err = checkRootKeyIsEncrypted(pemBytes); err != nil {
|
// ImportRoleKey imports a private key in PEM format key from a byte array
|
||||||
return err
|
// It prompts for the key's passphrase to verify the data and to determine
|
||||||
|
// the key ID.
|
||||||
|
func (cs *CryptoService) ImportRoleKey(pemBytes []byte, role string, newPassphraseRetriever passphrase.Retriever) error {
|
||||||
|
var alias string
|
||||||
|
var err error
|
||||||
|
if role == data.CanonicalRootRole {
|
||||||
|
alias = role
|
||||||
|
if err = checkRootKeyIsEncrypted(pemBytes); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Parse the private key to get the key ID so that we can import it to the correct location
|
||||||
|
privKey, err := trustmanager.ParsePEMPrivateKey(pemBytes, "")
|
||||||
|
if err != nil {
|
||||||
|
privKey, _, err = trustmanager.GetPasswdDecryptBytes(newPassphraseRetriever, pemBytes, role, string(role))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Since we're importing a non-root role, we need to pass the path as an alias
|
||||||
|
alias = filepath.Join(notary.NonRootKeysSubdir, cs.gun, privKey.ID())
|
||||||
|
// We also need to ensure that the role is properly set in the PEM headers
|
||||||
|
pemBytes, err = trustmanager.KeyToPEM(privKey, role)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ks := range cs.keyStores {
|
for _, ks := range cs.keyStores {
|
||||||
// don't redeclare err, we want the value carried out of the loop
|
// don't redeclare err, we want the value carried out of the loop
|
||||||
if err = ks.ImportKey(pemBytes, "root"); err == nil {
|
if err = ks.ImportKey(pemBytes, alias); err == nil {
|
||||||
return nil //bail on the first keystore we import to
|
return nil //bail on the first keystore we import to
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,34 @@
|
||||||
notaryserver:
|
server:
|
||||||
build: .
|
build: .
|
||||||
dockerfile: server.Dockerfile
|
dockerfile: server.Dockerfile
|
||||||
links:
|
links:
|
||||||
- notarymysql
|
- mysql
|
||||||
- notarysigner
|
- signer
|
||||||
ports:
|
- signer:notarysigner
|
||||||
- "8080"
|
|
||||||
- "4443:4443"
|
|
||||||
environment:
|
environment:
|
||||||
- SERVICE_NAME=notary
|
- SERVICE_NAME=notary_server
|
||||||
command: -config=fixtures/server-config.json
|
ports:
|
||||||
notarysigner:
|
- "8080"
|
||||||
volumes:
|
- "4443:4443"
|
||||||
- /dev/bus/usb/003/010:/dev/bus/usb/002/010
|
entrypoint: /bin/bash
|
||||||
- /var/run/pcscd/pcscd.comm:/var/run/pcscd/pcscd.comm
|
command: -c "./migrations/migrate.sh && notary-server -config=fixtures/server-config.json"
|
||||||
|
signer:
|
||||||
build: .
|
build: .
|
||||||
dockerfile: signer.Dockerfile
|
dockerfile: signer.Dockerfile
|
||||||
links:
|
links:
|
||||||
- notarymysql
|
- mysql
|
||||||
command: -config=fixtures/signer-config.json
|
environment:
|
||||||
notarymysql:
|
- SERVICE_NAME=notary_signer
|
||||||
|
entrypoint: /bin/bash
|
||||||
|
command: -c "./migrations/migrate.sh && notary-signer -config=fixtures/signer-config.json"
|
||||||
|
mysql:
|
||||||
volumes:
|
volumes:
|
||||||
- notarymysql:/var/lib/mysql
|
- ./notarymysql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
|
||||||
build: ./notarymysql/
|
- notary_data:/var/lib/mysql
|
||||||
|
image: mariadb:10.1.10
|
||||||
ports:
|
ports:
|
||||||
- "3306:3306"
|
- "3306:3306"
|
||||||
|
environment:
|
||||||
|
- TERM=dumb
|
||||||
|
- MYSQL_ALLOW_EMPTY_PASSWORD="true"
|
||||||
|
command: mysqld --innodb_file_per_table
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2014 Sameer Naik
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
|
@ -1,4 +1,5 @@
|
||||||
FROM golang:1.5.1
|
FROM golang:1.5.3
|
||||||
|
MAINTAINER David Lawrence "david.lawrence@docker.com"
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y \
|
RUN apt-get update && apt-get install -y \
|
||||||
libltdl-dev \
|
libltdl-dev \
|
||||||
|
@ -7,13 +8,20 @@ RUN apt-get update && apt-get install -y \
|
||||||
|
|
||||||
EXPOSE 4443
|
EXPOSE 4443
|
||||||
|
|
||||||
|
# Install DB migration tool
|
||||||
|
RUN go get github.com/mattes/migrate
|
||||||
|
|
||||||
ENV NOTARYPKG github.com/docker/notary
|
ENV NOTARYPKG github.com/docker/notary
|
||||||
ENV GOPATH /go/src/${NOTARYPKG}/Godeps/_workspace:$GOPATH
|
ENV GOPATH /go/src/${NOTARYPKG}/Godeps/_workspace:$GOPATH
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Copy the local repo to the expected go path
|
||||||
COPY . /go/src/github.com/docker/notary
|
COPY . /go/src/github.com/docker/notary
|
||||||
|
|
||||||
WORKDIR /go/src/${NOTARYPKG}
|
WORKDIR /go/src/${NOTARYPKG}
|
||||||
|
|
||||||
|
# Install notary-server
|
||||||
RUN go install \
|
RUN go install \
|
||||||
-tags pkcs11 \
|
-tags pkcs11 \
|
||||||
-ldflags "-w -X ${NOTARYPKG}/version.GitCommit=`git rev-parse --short HEAD` -X ${NOTARYPKG}/version.NotaryVersion=`cat NOTARY_VERSION`" \
|
-ldflags "-w -X ${NOTARYPKG}/version.GitCommit=`git rev-parse --short HEAD` -X ${NOTARYPKG}/version.NotaryVersion=`cat NOTARY_VERSION`" \
|
||||||
|
|
|
@ -1,29 +1,20 @@
|
||||||
FROM dockersecurity/golang-softhsm2
|
FROM golang:1.5.3
|
||||||
MAINTAINER Diogo Monica "diogo@docker.com"
|
MAINTAINER David Lawrence "david.lawrence@docker.com"
|
||||||
|
|
||||||
# CHANGE-ME: Default values for SoftHSM2 PIN and SOPIN, used to initialize the first token
|
|
||||||
ENV NOTARY_SIGNER_PIN="1234"
|
|
||||||
ENV SOPIN="1234"
|
|
||||||
ENV LIBDIR="/usr/local/lib/softhsm/"
|
|
||||||
ENV NOTARY_SIGNER_DEFAULT_ALIAS="timestamp_1"
|
|
||||||
ENV NOTARY_SIGNER_TIMESTAMP_1="testpassword"
|
|
||||||
|
|
||||||
# Install openSC and dependencies
|
|
||||||
RUN apt-get update && apt-get install -y \
|
RUN apt-get update && apt-get install -y \
|
||||||
libltdl-dev \
|
libltdl-dev \
|
||||||
libpcsclite-dev \
|
|
||||||
opensc \
|
|
||||||
usbutils \
|
|
||||||
--no-install-recommends \
|
--no-install-recommends \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Initialize the SoftHSM2 token on slod 0, using PIN and SOPIN varaibles
|
EXPOSE 4444
|
||||||
RUN softhsm2-util --init-token --slot 0 --label "test_token" --pin $NOTARY_SIGNER_PIN --so-pin $SOPIN
|
|
||||||
|
# Install DB migration tool
|
||||||
|
RUN go get github.com/mattes/migrate
|
||||||
|
|
||||||
ENV NOTARYPKG github.com/docker/notary
|
ENV NOTARYPKG github.com/docker/notary
|
||||||
ENV GOPATH /go/src/${NOTARYPKG}/Godeps/_workspace:$GOPATH
|
ENV GOPATH /go/src/${NOTARYPKG}/Godeps/_workspace:$GOPATH
|
||||||
|
ENV NOTARY_SIGNER_DEFAULT_ALIAS="timestamp_1"
|
||||||
EXPOSE 4444
|
ENV NOTARY_SIGNER_TIMESTAMP_1="testpassword"
|
||||||
|
|
||||||
# Copy the local repo to the expected go path
|
# Copy the local repo to the expected go path
|
||||||
COPY . /go/src/github.com/docker/notary
|
COPY . /go/src/github.com/docker/notary
|
||||||
|
@ -36,6 +27,5 @@ RUN go install \
|
||||||
-ldflags "-w -X ${NOTARYPKG}/version.GitCommit=`git rev-parse --short HEAD` -X ${NOTARYPKG}/version.NotaryVersion=`cat NOTARY_VERSION`" \
|
-ldflags "-w -X ${NOTARYPKG}/version.GitCommit=`git rev-parse --short HEAD` -X ${NOTARYPKG}/version.NotaryVersion=`cat NOTARY_VERSION`" \
|
||||||
${NOTARYPKG}/cmd/notary-signer
|
${NOTARYPKG}/cmd/notary-signer
|
||||||
|
|
||||||
|
|
||||||
ENTRYPOINT [ "notary-signer" ]
|
ENTRYPOINT [ "notary-signer" ]
|
||||||
CMD [ "-config=fixtures/signer-config-local.json" ]
|
CMD [ "-config=fixtures/signer-config-local.json" ]
|
||||||
|
|
|
@ -8,16 +8,11 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/notary"
|
||||||
"github.com/docker/notary/passphrase"
|
"github.com/docker/notary/passphrase"
|
||||||
"github.com/docker/notary/tuf/data"
|
"github.com/docker/notary/tuf/data"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
rootKeysSubdir = "root_keys"
|
|
||||||
nonRootKeysSubdir = "tuf_keys"
|
|
||||||
privDir = "private"
|
|
||||||
)
|
|
||||||
|
|
||||||
// KeyFileStore persists and manages private keys on disk
|
// KeyFileStore persists and manages private keys on disk
|
||||||
type KeyFileStore struct {
|
type KeyFileStore struct {
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
|
@ -37,7 +32,7 @@ type KeyMemoryStore struct {
|
||||||
// NewKeyFileStore returns a new KeyFileStore creating a private directory to
|
// NewKeyFileStore returns a new KeyFileStore creating a private directory to
|
||||||
// hold the keys.
|
// hold the keys.
|
||||||
func NewKeyFileStore(baseDir string, passphraseRetriever passphrase.Retriever) (*KeyFileStore, error) {
|
func NewKeyFileStore(baseDir string, passphraseRetriever passphrase.Retriever) (*KeyFileStore, error) {
|
||||||
baseDir = filepath.Join(baseDir, privDir)
|
baseDir = filepath.Join(baseDir, notary.PrivDir)
|
||||||
fileStore, err := NewPrivateSimpleFileStore(baseDir, keyExtension)
|
fileStore, err := NewPrivateSimpleFileStore(baseDir, keyExtension)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -242,10 +237,10 @@ func listKeys(s LimitedFileStore) map[string]string {
|
||||||
for _, f := range s.ListFiles() {
|
for _, f := range s.ListFiles() {
|
||||||
// Remove the prefix of the directory from the filename
|
// Remove the prefix of the directory from the filename
|
||||||
var keyIDFull string
|
var keyIDFull string
|
||||||
if strings.HasPrefix(f, rootKeysSubdir+"/") {
|
if strings.HasPrefix(f, notary.RootKeysSubdir+"/") {
|
||||||
keyIDFull = strings.TrimPrefix(f, rootKeysSubdir+"/")
|
keyIDFull = strings.TrimPrefix(f, notary.RootKeysSubdir+"/")
|
||||||
} else {
|
} else {
|
||||||
keyIDFull = strings.TrimPrefix(f, nonRootKeysSubdir+"/")
|
keyIDFull = strings.TrimPrefix(f, notary.NonRootKeysSubdir+"/")
|
||||||
}
|
}
|
||||||
|
|
||||||
keyIDFull = strings.TrimSpace(keyIDFull)
|
keyIDFull = strings.TrimSpace(keyIDFull)
|
||||||
|
@ -302,9 +297,9 @@ func removeKey(s LimitedFileStore, cachedKeys map[string]*cachedKey, name string
|
||||||
// Assumes 2 subdirectories, 1 containing root keys and 1 containing tuf keys
|
// Assumes 2 subdirectories, 1 containing root keys and 1 containing tuf keys
|
||||||
func getSubdir(alias string) string {
|
func getSubdir(alias string) string {
|
||||||
if alias == "root" {
|
if alias == "root" {
|
||||||
return rootKeysSubdir
|
return notary.RootKeysSubdir
|
||||||
}
|
}
|
||||||
return nonRootKeysSubdir
|
return notary.NonRootKeysSubdir
|
||||||
}
|
}
|
||||||
|
|
||||||
// Given a key ID, gets the bytes and alias belonging to that key if the key
|
// 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) {
|
||||||
return keyBytes, role, nil
|
return keyBytes, role, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPasswdDecryptBytes gets the password to decript the given pem bytes.
|
// GetPasswdDecryptBytes gets the password to decrypt the given pem bytes.
|
||||||
// Returns the password and private key
|
// Returns the password and private key
|
||||||
func GetPasswdDecryptBytes(passphraseRetriever passphrase.Retriever, pemBytes []byte, name, alias string) (data.PrivateKey, string, error) {
|
func GetPasswdDecryptBytes(passphraseRetriever passphrase.Retriever, pemBytes []byte, name, alias string) (data.PrivateKey, string, error) {
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -470,12 +470,17 @@ func KeyToPEM(privKey data.PrivateKey, role string) ([]byte, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
block := &pem.Block{
|
headers := map[string]string{}
|
||||||
Type: bt,
|
if role != "" {
|
||||||
Headers: map[string]string{
|
headers = map[string]string{
|
||||||
"role": role,
|
"role": role,
|
||||||
},
|
}
|
||||||
Bytes: privKey.Private(),
|
}
|
||||||
|
|
||||||
|
block := &pem.Block{
|
||||||
|
Type: bt,
|
||||||
|
Headers: headers,
|
||||||
|
Bytes: privKey.Private(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return pem.EncodeToMemory(block), nil
|
return pem.EncodeToMemory(block), nil
|
||||||
|
@ -509,6 +514,19 @@ func EncryptPrivateKey(key data.PrivateKey, role, passphrase string) ([]byte, er
|
||||||
return pem.EncodeToMemory(encryptedPEMBlock), nil
|
return pem.EncodeToMemory(encryptedPEMBlock), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReadRoleFromPEM returns the value from the role PEM header, if it exists
|
||||||
|
func ReadRoleFromPEM(pemBytes []byte) string {
|
||||||
|
pemBlock, _ := pem.Decode(pemBytes)
|
||||||
|
if pemBlock.Headers == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
role, ok := pemBlock.Headers["role"]
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return role
|
||||||
|
}
|
||||||
|
|
||||||
// CertToKey transforms a single input certificate into its corresponding
|
// CertToKey transforms a single input certificate into its corresponding
|
||||||
// PublicKey
|
// PublicKey
|
||||||
func CertToKey(cert *x509.Certificate) data.PublicKey {
|
func CertToKey(cert *x509.Certificate) data.PublicKey {
|
||||||
|
|
|
@ -765,15 +765,15 @@ func (s *YubiKeyStore) ExportKey(keyID string) ([]byte, error) {
|
||||||
// ImportKey imports a root key into a Yubikey
|
// ImportKey imports a root key into a Yubikey
|
||||||
func (s *YubiKeyStore) ImportKey(pemBytes []byte, keyPath string) error {
|
func (s *YubiKeyStore) ImportKey(pemBytes []byte, keyPath string) error {
|
||||||
logrus.Debugf("Attempting to import: %s key inside of YubiKeyStore", keyPath)
|
logrus.Debugf("Attempting to import: %s key inside of YubiKeyStore", keyPath)
|
||||||
|
if keyPath != data.CanonicalRootRole {
|
||||||
|
return fmt.Errorf("yubikey only supports storing root keys")
|
||||||
|
}
|
||||||
privKey, _, err := trustmanager.GetPasswdDecryptBytes(
|
privKey, _, err := trustmanager.GetPasswdDecryptBytes(
|
||||||
s.passRetriever, pemBytes, "", "imported root")
|
s.passRetriever, pemBytes, "", "imported root")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Debugf("Failed to get and retrieve a key from: %s", keyPath)
|
logrus.Debugf("Failed to get and retrieve a key from: %s", keyPath)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if keyPath != data.CanonicalRootRole {
|
|
||||||
return fmt.Errorf("yubikey only supports storing root keys")
|
|
||||||
}
|
|
||||||
_, err = s.addKey(privKey.ID(), "root", privKey)
|
_, err = s.addKey(privKey.ID(), "root", privKey)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ however in attempting to add delegations I found I was making such
|
||||||
significant changes that I could not maintain backwards compatibility
|
significant changes that I could not maintain backwards compatibility
|
||||||
without the code becoming overly convoluted.
|
without the code becoming overly convoluted.
|
||||||
|
|
||||||
Some features such as pluggable verifiers have alreayd been merged upstream to flynn/go-tuf
|
Some features such as pluggable verifiers have already been merged upstream to flynn/go-tuf
|
||||||
and we are in discussion with [titanous](https://github.com/titanous) about working to merge the 2 implementations.
|
and we are in discussion with [titanous](https://github.com/titanous) about working to merge the 2 implementations.
|
||||||
|
|
||||||
This implementation retains the same 3 Clause BSD license present on
|
This implementation retains the same 3 Clause BSD license present on
|
||||||
|
|
|
@ -3,38 +3,31 @@ package client
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/notary"
|
||||||
tuf "github.com/docker/notary/tuf"
|
tuf "github.com/docker/notary/tuf"
|
||||||
"github.com/docker/notary/tuf/data"
|
"github.com/docker/notary/tuf/data"
|
||||||
"github.com/docker/notary/tuf/keys"
|
|
||||||
"github.com/docker/notary/tuf/signed"
|
"github.com/docker/notary/tuf/signed"
|
||||||
"github.com/docker/notary/tuf/store"
|
"github.com/docker/notary/tuf/store"
|
||||||
"github.com/docker/notary/tuf/utils"
|
"github.com/docker/notary/tuf/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
const maxSize int64 = 5 << 20
|
|
||||||
|
|
||||||
// Client is a usability wrapper around a raw TUF repo
|
// Client is a usability wrapper around a raw TUF repo
|
||||||
type Client struct {
|
type Client struct {
|
||||||
local *tuf.Repo
|
local *tuf.Repo
|
||||||
remote store.RemoteStore
|
remote store.RemoteStore
|
||||||
keysDB *keys.KeyDB
|
|
||||||
cache store.MetadataStore
|
cache store.MetadataStore
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient initialized a Client with the given repo, remote source of content, key database, and cache
|
// NewClient initialized a Client with the given repo, remote source of content, and cache
|
||||||
func NewClient(local *tuf.Repo, remote store.RemoteStore, keysDB *keys.KeyDB, cache store.MetadataStore) *Client {
|
func NewClient(local *tuf.Repo, remote store.RemoteStore, cache store.MetadataStore) *Client {
|
||||||
return &Client{
|
return &Client{
|
||||||
local: local,
|
local: local,
|
||||||
remote: remote,
|
remote: remote,
|
||||||
keysDB: keysDB,
|
|
||||||
cache: cache,
|
cache: cache,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,11 +124,15 @@ func (c Client) checkRoot() error {
|
||||||
func (c *Client) downloadRoot() error {
|
func (c *Client) downloadRoot() error {
|
||||||
logrus.Debug("Downloading Root...")
|
logrus.Debug("Downloading Root...")
|
||||||
role := data.CanonicalRootRole
|
role := data.CanonicalRootRole
|
||||||
size := maxSize
|
// We can't read an exact size for the root metadata without risking getting stuck in the TUF update cycle
|
||||||
|
// since it's possible that downloading timestamp/snapshot metadata may fail due to a signature mismatch
|
||||||
|
var size int64 = -1
|
||||||
var expectedSha256 []byte
|
var expectedSha256 []byte
|
||||||
if c.local.Snapshot != nil {
|
if c.local.Snapshot != nil {
|
||||||
size = c.local.Snapshot.Signed.Meta[role].Length
|
if prevRootMeta, ok := c.local.Snapshot.Signed.Meta[role]; ok {
|
||||||
expectedSha256 = c.local.Snapshot.Signed.Meta[role].Hashes["sha256"]
|
size = prevRootMeta.Length
|
||||||
|
expectedSha256 = prevRootMeta.Hashes["sha256"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we're bootstrapping we may not have a cached root, an
|
// if we're bootstrapping we may not have a cached root, an
|
||||||
|
@ -178,6 +175,7 @@ func (c *Client) downloadRoot() error {
|
||||||
var s *data.Signed
|
var s *data.Signed
|
||||||
var raw []byte
|
var raw []byte
|
||||||
if download {
|
if download {
|
||||||
|
// use consistent download if we have the checksum.
|
||||||
raw, s, err = c.downloadSigned(role, size, expectedSha256)
|
raw, s, err = c.downloadSigned(role, size, expectedSha256)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -201,34 +199,45 @@ func (c *Client) downloadRoot() error {
|
||||||
|
|
||||||
func (c Client) verifyRoot(role string, s *data.Signed, minVersion int) error {
|
func (c Client) verifyRoot(role string, s *data.Signed, minVersion int) error {
|
||||||
// this will confirm that the root has been signed by the old root role
|
// this will confirm that the root has been signed by the old root role
|
||||||
// as c.keysDB contains the root keys we bootstrapped with.
|
// with the root keys we bootstrapped with.
|
||||||
// Still need to determine if there has been a root key update and
|
// Still need to determine if there has been a root key update and
|
||||||
// confirm signature with new root key
|
// confirm signature with new root key
|
||||||
logrus.Debug("verifying root with existing keys")
|
logrus.Debug("verifying root with existing keys")
|
||||||
err := signed.Verify(s, role, minVersion, c.keysDB)
|
rootRole, err := c.local.GetBaseRole(role)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
logrus.Debug("no previous root role loaded")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Verify using the rootRole loaded from the known root.json
|
||||||
|
if err = signed.Verify(s, rootRole, minVersion); err != nil {
|
||||||
logrus.Debug("root did not verify with existing keys")
|
logrus.Debug("root did not verify with existing keys")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// This will cause keyDB to get updated, overwriting any keyIDs associated
|
|
||||||
// with the roles in root.json
|
|
||||||
logrus.Debug("updating known root roles and keys")
|
logrus.Debug("updating known root roles and keys")
|
||||||
root, err := data.RootFromSigned(s)
|
root, err := data.RootFromSigned(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Error(err.Error())
|
logrus.Error(err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// replace the existing root.json with the new one (just in memory, we
|
||||||
|
// have another validation step before we fully accept the new root)
|
||||||
err = c.local.SetRoot(root)
|
err = c.local.SetRoot(root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Error(err.Error())
|
logrus.Error(err.Error())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// verify again now that the old keys have been replaced with the new keys.
|
// Verify the new root again having loaded the rootRole out of this new
|
||||||
|
// file (verifies self-referential integrity)
|
||||||
// TODO(endophage): be more intelligent and only re-verify if we detect
|
// TODO(endophage): be more intelligent and only re-verify if we detect
|
||||||
// there has been a change in root keys
|
// there has been a change in root keys
|
||||||
logrus.Debug("verifying root with updated keys")
|
logrus.Debug("verifying root with updated keys")
|
||||||
err = signed.Verify(s, role, minVersion, c.keysDB)
|
rootRole, err = c.local.GetBaseRole(role)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debug("root role with new keys not loaded")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = signed.Verify(s, rootRole, minVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Debug("root did not verify with new keys")
|
logrus.Debug("root did not verify with new keys")
|
||||||
return err
|
return err
|
||||||
|
@ -248,11 +257,11 @@ func (c *Client) downloadTimestamp() error {
|
||||||
// we're interacting with the repo. This will result in the
|
// we're interacting with the repo. This will result in the
|
||||||
// version being 0
|
// version being 0
|
||||||
var (
|
var (
|
||||||
saveToCache bool
|
old *data.Signed
|
||||||
old *data.Signed
|
ts *data.SignedTimestamp
|
||||||
version = 0
|
version = 0
|
||||||
)
|
)
|
||||||
cachedTS, err := c.cache.GetMeta(role, maxSize)
|
cachedTS, err := c.cache.GetMeta(role, notary.MaxTimestampSize)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
cached := &data.Signed{}
|
cached := &data.Signed{}
|
||||||
err := json.Unmarshal(cachedTS, cached)
|
err := json.Unmarshal(cachedTS, cached)
|
||||||
|
@ -266,49 +275,56 @@ func (c *Client) downloadTimestamp() error {
|
||||||
}
|
}
|
||||||
// unlike root, targets and snapshot, always try and download timestamps
|
// unlike root, targets and snapshot, always try and download timestamps
|
||||||
// from remote, only using the cache one if we couldn't reach remote.
|
// from remote, only using the cache one if we couldn't reach remote.
|
||||||
raw, s, err := c.downloadSigned(role, maxSize, nil)
|
raw, s, err := c.downloadSigned(role, notary.MaxTimestampSize, nil)
|
||||||
if err != nil || len(raw) == 0 {
|
if err == nil {
|
||||||
if old == nil {
|
ts, err = c.verifyTimestamp(s, version)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// couldn't retrieve data from server and don't have valid
|
logrus.Debug("successfully verified downloaded timestamp")
|
||||||
// data in cache.
|
c.cache.SetMeta(role, raw)
|
||||||
return store.ErrMetaNotFound{Resource: data.CanonicalTimestampRole}
|
c.local.SetTimestamp(ts)
|
||||||
}
|
return nil
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
logrus.Debug(err.Error())
|
|
||||||
logrus.Warn("Error while downloading remote metadata, using cached timestamp - this might not be the latest version available remotely")
|
|
||||||
s = old
|
|
||||||
} else {
|
|
||||||
saveToCache = true
|
|
||||||
}
|
}
|
||||||
err = signed.Verify(s, role, version, c.keysDB)
|
if old == nil {
|
||||||
if err != nil {
|
// couldn't retrieve valid data from server and don't have unmarshallable data in cache.
|
||||||
return err
|
logrus.Debug("no cached timestamp available")
|
||||||
}
|
return err
|
||||||
logrus.Debug("successfully verified timestamp")
|
}
|
||||||
if saveToCache {
|
logrus.Debug(err.Error())
|
||||||
c.cache.SetMeta(role, raw)
|
logrus.Warn("Error while downloading remote metadata, using cached timestamp - this might not be the latest version available remotely")
|
||||||
}
|
ts, err = c.verifyTimestamp(old, version)
|
||||||
ts, err := data.TimestampFromSigned(s)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
logrus.Debug("successfully verified cached timestamp")
|
||||||
c.local.SetTimestamp(ts)
|
c.local.SetTimestamp(ts)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// verifies that a timestamp is valid, and returned the SignedTimestamp object to add to the tuf repo
|
||||||
|
func (c *Client) verifyTimestamp(s *data.Signed, minVersion int) (*data.SignedTimestamp, error) {
|
||||||
|
timestampRole, err := c.local.GetBaseRole(data.CanonicalTimestampRole)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debug("no timestamp role loaded")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := signed.Verify(s, timestampRole, minVersion); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return data.TimestampFromSigned(s)
|
||||||
|
}
|
||||||
|
|
||||||
// downloadSnapshot is responsible for downloading the snapshot.json
|
// downloadSnapshot is responsible for downloading the snapshot.json
|
||||||
func (c *Client) downloadSnapshot() error {
|
func (c *Client) downloadSnapshot() error {
|
||||||
logrus.Debug("Downloading Snapshot...")
|
logrus.Debug("Downloading Snapshot...")
|
||||||
role := data.CanonicalSnapshotRole
|
role := data.CanonicalSnapshotRole
|
||||||
if c.local.Timestamp == nil {
|
if c.local.Timestamp == nil {
|
||||||
return ErrMissingMeta{role: "snapshot"}
|
return tuf.ErrNotLoaded{Role: data.CanonicalTimestampRole}
|
||||||
}
|
}
|
||||||
size := c.local.Timestamp.Signed.Meta[role].Length
|
size := c.local.Timestamp.Signed.Meta[role].Length
|
||||||
expectedSha256, ok := c.local.Timestamp.Signed.Meta[role].Hashes["sha256"]
|
expectedSha256, ok := c.local.Timestamp.Signed.Meta[role].Hashes["sha256"]
|
||||||
if !ok {
|
if !ok {
|
||||||
return ErrMissingMeta{role: "snapshot"}
|
return data.ErrMissingMeta{Role: "snapshot"}
|
||||||
}
|
}
|
||||||
|
|
||||||
var download bool
|
var download bool
|
||||||
|
@ -350,7 +366,12 @@ func (c *Client) downloadSnapshot() error {
|
||||||
s = old
|
s = old
|
||||||
}
|
}
|
||||||
|
|
||||||
err = signed.Verify(s, role, version, c.keysDB)
|
snapshotRole, err := c.local.GetBaseRole(role)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debug("no snapshot role loaded")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = signed.Verify(s, snapshotRole, version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -382,18 +403,14 @@ func (c *Client) downloadTargets(role string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if c.local.Snapshot == nil {
|
if c.local.Snapshot == nil {
|
||||||
return ErrMissingMeta{role: role}
|
return tuf.ErrNotLoaded{Role: data.CanonicalSnapshotRole}
|
||||||
}
|
}
|
||||||
snap := c.local.Snapshot.Signed
|
snap := c.local.Snapshot.Signed
|
||||||
root := c.local.Root.Signed
|
root := c.local.Root.Signed
|
||||||
r := c.keysDB.GetRole(role)
|
|
||||||
if r == nil {
|
s, err := c.getTargetsFile(role, snap.Meta, root.ConsistentSnapshot)
|
||||||
return fmt.Errorf("Invalid role: %s", role)
|
|
||||||
}
|
|
||||||
keyIDs := r.KeyIDs
|
|
||||||
s, err := c.getTargetsFile(role, keyIDs, snap.Meta, root.ConsistentSnapshot, r.Threshold)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if _, ok := err.(ErrMissingMeta); ok && role != data.CanonicalTargetsRole {
|
if _, ok := err.(data.ErrMissingMeta); ok && role != data.CanonicalTargetsRole {
|
||||||
// if the role meta hasn't been published,
|
// if the role meta hasn't been published,
|
||||||
// that's ok, continue
|
// that's ok, continue
|
||||||
continue
|
continue
|
||||||
|
@ -401,7 +418,7 @@ func (c *Client) downloadTargets(role string) error {
|
||||||
logrus.Error("Error getting targets file:", err)
|
logrus.Error("Error getting targets file:", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
t, err := data.TargetsFromSigned(s)
|
t, err := data.TargetsFromSigned(s, role)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -412,14 +429,19 @@ func (c *Client) downloadTargets(role string) error {
|
||||||
|
|
||||||
// push delegated roles contained in the targets file onto the stack
|
// push delegated roles contained in the targets file onto the stack
|
||||||
for _, r := range t.Signed.Delegations.Roles {
|
for _, r := range t.Signed.Delegations.Roles {
|
||||||
stack.Push(r.Name)
|
if path.Dir(r.Name) == role {
|
||||||
|
// only load children that are direct 1st generation descendants
|
||||||
|
// of the role we've just downloaded
|
||||||
|
stack.Push(r.Name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) downloadSigned(role string, size int64, expectedSha256 []byte) ([]byte, *data.Signed, error) {
|
func (c *Client) downloadSigned(role string, size int64, expectedSha256 []byte) ([]byte, *data.Signed, error) {
|
||||||
raw, err := c.remote.GetMeta(role, size)
|
rolePath := utils.ConsistentName(role, expectedSha256)
|
||||||
|
raw, err := c.remote.GetMeta(rolePath, size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -437,15 +459,15 @@ func (c *Client) downloadSigned(role string, size int64, expectedSha256 []byte)
|
||||||
return raw, s, nil
|
return raw, s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Client) getTargetsFile(role string, keyIDs []string, snapshotMeta data.Files, consistent bool, threshold int) (*data.Signed, error) {
|
func (c Client) getTargetsFile(role string, snapshotMeta data.Files, consistent bool) (*data.Signed, error) {
|
||||||
// require role exists in snapshots
|
// require role exists in snapshots
|
||||||
roleMeta, ok := snapshotMeta[role]
|
roleMeta, ok := snapshotMeta[role]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, ErrMissingMeta{role: role}
|
return nil, data.ErrMissingMeta{Role: role}
|
||||||
}
|
}
|
||||||
expectedSha256, ok := snapshotMeta[role].Hashes["sha256"]
|
expectedSha256, ok := snapshotMeta[role].Hashes["sha256"]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, ErrMissingMeta{role: role}
|
return nil, data.ErrMissingMeta{Role: role}
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to get meta file from content addressed cache
|
// try to get meta file from content addressed cache
|
||||||
|
@ -464,7 +486,7 @@ func (c Client) getTargetsFile(role string, keyIDs []string, snapshotMeta data.F
|
||||||
}
|
}
|
||||||
err := json.Unmarshal(raw, old)
|
err := json.Unmarshal(raw, old)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
targ, err := data.TargetsFromSigned(old)
|
targ, err := data.TargetsFromSigned(old, role)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
version = targ.Signed.Version
|
version = targ.Signed.Version
|
||||||
} else {
|
} else {
|
||||||
|
@ -478,11 +500,7 @@ func (c Client) getTargetsFile(role string, keyIDs []string, snapshotMeta data.F
|
||||||
size := snapshotMeta[role].Length
|
size := snapshotMeta[role].Length
|
||||||
var s *data.Signed
|
var s *data.Signed
|
||||||
if download {
|
if download {
|
||||||
rolePath, err := c.RoleTargetsPath(role, hex.EncodeToString(expectedSha256), consistent)
|
raw, s, err = c.downloadSigned(role, size, expectedSha256)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
raw, s, err = c.downloadSigned(rolePath, size, expectedSha256)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -490,9 +508,22 @@ func (c Client) getTargetsFile(role string, keyIDs []string, snapshotMeta data.F
|
||||||
logrus.Debug("using cached ", role)
|
logrus.Debug("using cached ", role)
|
||||||
s = old
|
s = old
|
||||||
}
|
}
|
||||||
|
var targetOrDelgRole data.BaseRole
|
||||||
err = signed.Verify(s, role, version, c.keysDB)
|
if data.IsDelegation(role) {
|
||||||
if err != nil {
|
delgRole, err := c.local.GetDelegationRole(role)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("no %s delegation role loaded", role)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
targetOrDelgRole = delgRole.BaseRole
|
||||||
|
} else {
|
||||||
|
targetOrDelgRole, err = c.local.GetBaseRole(role)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("no %s role loaded", role)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err = signed.Verify(s, targetOrDelgRole, version); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
logrus.Debugf("successfully verified %s", role)
|
logrus.Debugf("successfully verified %s", role)
|
||||||
|
@ -505,73 +536,3 @@ func (c Client) getTargetsFile(role string, keyIDs []string, snapshotMeta data.F
|
||||||
}
|
}
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoleTargetsPath generates the appropriate HTTP URL for the targets file,
|
|
||||||
// based on whether the repo is marked as consistent.
|
|
||||||
func (c Client) RoleTargetsPath(role string, hashSha256 string, consistent bool) (string, error) {
|
|
||||||
if consistent {
|
|
||||||
// Use path instead of filepath since we refer to the TUF role directly instead of its target files
|
|
||||||
dir := path.Dir(role)
|
|
||||||
if strings.Contains(role, "/") {
|
|
||||||
lastSlashIdx := strings.LastIndex(role, "/")
|
|
||||||
role = role[lastSlashIdx+1:]
|
|
||||||
}
|
|
||||||
role = path.Join(
|
|
||||||
dir,
|
|
||||||
fmt.Sprintf("%s.%s.json", hashSha256, role),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return role, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TargetMeta ensures the repo is up to date. It assumes downloadTargets
|
|
||||||
// has already downloaded all delegated roles
|
|
||||||
func (c Client) TargetMeta(role, path string, excludeRoles ...string) (*data.FileMeta, string) {
|
|
||||||
excl := make(map[string]bool)
|
|
||||||
for _, r := range excludeRoles {
|
|
||||||
excl[r] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
pathDigest := sha256.Sum256([]byte(path))
|
|
||||||
pathHex := hex.EncodeToString(pathDigest[:])
|
|
||||||
|
|
||||||
// FIFO list of targets delegations to inspect for target
|
|
||||||
roles := []string{role}
|
|
||||||
var (
|
|
||||||
meta *data.FileMeta
|
|
||||||
curr string
|
|
||||||
)
|
|
||||||
for len(roles) > 0 {
|
|
||||||
// have to do these lines here because of order of execution in for statement
|
|
||||||
curr = roles[0]
|
|
||||||
roles = roles[1:]
|
|
||||||
|
|
||||||
meta = c.local.TargetMeta(curr, path)
|
|
||||||
if meta != nil {
|
|
||||||
// we found the target!
|
|
||||||
return meta, curr
|
|
||||||
}
|
|
||||||
delegations := c.local.TargetDelegations(curr, path, pathHex)
|
|
||||||
for _, d := range delegations {
|
|
||||||
if !excl[d.Name] {
|
|
||||||
roles = append(roles, d.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return meta, ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// DownloadTarget downloads the target to dst from the remote
|
|
||||||
func (c Client) DownloadTarget(dst io.Writer, path string, meta *data.FileMeta) error {
|
|
||||||
reader, err := c.remote.GetTarget(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer reader.Close()
|
|
||||||
r := io.TeeReader(
|
|
||||||
io.LimitReader(reader, meta.Length),
|
|
||||||
dst,
|
|
||||||
)
|
|
||||||
err = utils.ValidateTarget(r, meta)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
|
@ -13,15 +13,6 @@ func (e ErrChecksumMismatch) Error() string {
|
||||||
return fmt.Sprintf("tuf: checksum for %s did not match", e.role)
|
return fmt.Sprintf("tuf: checksum for %s did not match", e.role)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrMissingMeta - couldn't find the FileMeta object for a role or target
|
|
||||||
type ErrMissingMeta struct {
|
|
||||||
role string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e ErrMissingMeta) Error() string {
|
|
||||||
return fmt.Sprintf("tuf: sha256 checksum required for %s", e.role)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrCorruptedCache - local data is incorrect
|
// ErrCorruptedCache - local data is incorrect
|
||||||
type ErrCorruptedCache struct {
|
type ErrCorruptedCache struct {
|
||||||
file string
|
file string
|
||||||
|
|
22
vendor/src/github.com/docker/notary/tuf/data/errors.go
vendored
Normal file
22
vendor/src/github.com/docker/notary/tuf/data/errors.go
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package data
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// ErrInvalidMetadata is the error to be returned when metadata is invalid
|
||||||
|
type ErrInvalidMetadata struct {
|
||||||
|
role string
|
||||||
|
msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrInvalidMetadata) Error() string {
|
||||||
|
return fmt.Sprintf("%s type metadata invalid: %s", e.role, e.msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrMissingMeta - couldn't find the FileMeta object for a role or target
|
||||||
|
type ErrMissingMeta struct {
|
||||||
|
Role string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrMissingMeta) Error() string {
|
||||||
|
return fmt.Sprintf("tuf: sha256 checksum required for %s", e.Role)
|
||||||
|
}
|
|
@ -46,7 +46,7 @@ type Keys map[string]PublicKey
|
||||||
|
|
||||||
// UnmarshalJSON implements the json.Unmarshaller interface
|
// UnmarshalJSON implements the json.Unmarshaller interface
|
||||||
func (ks *Keys) UnmarshalJSON(data []byte) error {
|
func (ks *Keys) UnmarshalJSON(data []byte) error {
|
||||||
parsed := make(map[string]tufKey)
|
parsed := make(map[string]TUFKey)
|
||||||
err := json.Unmarshal(data, &parsed)
|
err := json.Unmarshal(data, &parsed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -64,7 +64,7 @@ type KeyList []PublicKey
|
||||||
|
|
||||||
// UnmarshalJSON implements the json.Unmarshaller interface
|
// UnmarshalJSON implements the json.Unmarshaller interface
|
||||||
func (ks *KeyList) UnmarshalJSON(data []byte) error {
|
func (ks *KeyList) UnmarshalJSON(data []byte) error {
|
||||||
parsed := make([]tufKey, 0, 1)
|
parsed := make([]TUFKey, 0, 1)
|
||||||
err := json.Unmarshal(data, &parsed)
|
err := json.Unmarshal(data, &parsed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -86,64 +86,64 @@ func (ks KeyList) IDs() []string {
|
||||||
return keyIDs
|
return keyIDs
|
||||||
}
|
}
|
||||||
|
|
||||||
func typedPublicKey(tk tufKey) PublicKey {
|
func typedPublicKey(tk TUFKey) PublicKey {
|
||||||
switch tk.Algorithm() {
|
switch tk.Algorithm() {
|
||||||
case ECDSAKey:
|
case ECDSAKey:
|
||||||
return &ECDSAPublicKey{tufKey: tk}
|
return &ECDSAPublicKey{TUFKey: tk}
|
||||||
case ECDSAx509Key:
|
case ECDSAx509Key:
|
||||||
return &ECDSAx509PublicKey{tufKey: tk}
|
return &ECDSAx509PublicKey{TUFKey: tk}
|
||||||
case RSAKey:
|
case RSAKey:
|
||||||
return &RSAPublicKey{tufKey: tk}
|
return &RSAPublicKey{TUFKey: tk}
|
||||||
case RSAx509Key:
|
case RSAx509Key:
|
||||||
return &RSAx509PublicKey{tufKey: tk}
|
return &RSAx509PublicKey{TUFKey: tk}
|
||||||
case ED25519Key:
|
case ED25519Key:
|
||||||
return &ED25519PublicKey{tufKey: tk}
|
return &ED25519PublicKey{TUFKey: tk}
|
||||||
}
|
}
|
||||||
return &UnknownPublicKey{tufKey: tk}
|
return &UnknownPublicKey{TUFKey: tk}
|
||||||
}
|
}
|
||||||
|
|
||||||
func typedPrivateKey(tk tufKey) (PrivateKey, error) {
|
func typedPrivateKey(tk TUFKey) (PrivateKey, error) {
|
||||||
private := tk.Value.Private
|
private := tk.Value.Private
|
||||||
tk.Value.Private = nil
|
tk.Value.Private = nil
|
||||||
switch tk.Algorithm() {
|
switch tk.Algorithm() {
|
||||||
case ECDSAKey:
|
case ECDSAKey:
|
||||||
return NewECDSAPrivateKey(
|
return NewECDSAPrivateKey(
|
||||||
&ECDSAPublicKey{
|
&ECDSAPublicKey{
|
||||||
tufKey: tk,
|
TUFKey: tk,
|
||||||
},
|
},
|
||||||
private,
|
private,
|
||||||
)
|
)
|
||||||
case ECDSAx509Key:
|
case ECDSAx509Key:
|
||||||
return NewECDSAPrivateKey(
|
return NewECDSAPrivateKey(
|
||||||
&ECDSAx509PublicKey{
|
&ECDSAx509PublicKey{
|
||||||
tufKey: tk,
|
TUFKey: tk,
|
||||||
},
|
},
|
||||||
private,
|
private,
|
||||||
)
|
)
|
||||||
case RSAKey:
|
case RSAKey:
|
||||||
return NewRSAPrivateKey(
|
return NewRSAPrivateKey(
|
||||||
&RSAPublicKey{
|
&RSAPublicKey{
|
||||||
tufKey: tk,
|
TUFKey: tk,
|
||||||
},
|
},
|
||||||
private,
|
private,
|
||||||
)
|
)
|
||||||
case RSAx509Key:
|
case RSAx509Key:
|
||||||
return NewRSAPrivateKey(
|
return NewRSAPrivateKey(
|
||||||
&RSAx509PublicKey{
|
&RSAx509PublicKey{
|
||||||
tufKey: tk,
|
TUFKey: tk,
|
||||||
},
|
},
|
||||||
private,
|
private,
|
||||||
)
|
)
|
||||||
case ED25519Key:
|
case ED25519Key:
|
||||||
return NewED25519PrivateKey(
|
return NewED25519PrivateKey(
|
||||||
ED25519PublicKey{
|
ED25519PublicKey{
|
||||||
tufKey: tk,
|
TUFKey: tk,
|
||||||
},
|
},
|
||||||
private,
|
private,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return &UnknownPrivateKey{
|
return &UnknownPrivateKey{
|
||||||
tufKey: tk,
|
TUFKey: tk,
|
||||||
privateKey: privateKey{private: private},
|
privateKey: privateKey{private: private},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -151,7 +151,7 @@ func typedPrivateKey(tk tufKey) (PrivateKey, error) {
|
||||||
// NewPublicKey creates a new, correctly typed PublicKey, using the
|
// NewPublicKey creates a new, correctly typed PublicKey, using the
|
||||||
// UnknownPublicKey catchall for unsupported ciphers
|
// UnknownPublicKey catchall for unsupported ciphers
|
||||||
func NewPublicKey(alg string, public []byte) PublicKey {
|
func NewPublicKey(alg string, public []byte) PublicKey {
|
||||||
tk := tufKey{
|
tk := TUFKey{
|
||||||
Type: alg,
|
Type: alg,
|
||||||
Value: KeyPair{
|
Value: KeyPair{
|
||||||
Public: public,
|
Public: public,
|
||||||
|
@ -163,7 +163,7 @@ func NewPublicKey(alg string, public []byte) PublicKey {
|
||||||
// NewPrivateKey creates a new, correctly typed PrivateKey, using the
|
// NewPrivateKey creates a new, correctly typed PrivateKey, using the
|
||||||
// UnknownPrivateKey catchall for unsupported ciphers
|
// UnknownPrivateKey catchall for unsupported ciphers
|
||||||
func NewPrivateKey(pubKey PublicKey, private []byte) (PrivateKey, error) {
|
func NewPrivateKey(pubKey PublicKey, private []byte) (PrivateKey, error) {
|
||||||
tk := tufKey{
|
tk := TUFKey{
|
||||||
Type: pubKey.Algorithm(),
|
Type: pubKey.Algorithm(),
|
||||||
Value: KeyPair{
|
Value: KeyPair{
|
||||||
Public: pubKey.Public(),
|
Public: pubKey.Public(),
|
||||||
|
@ -175,7 +175,7 @@ func NewPrivateKey(pubKey PublicKey, private []byte) (PrivateKey, error) {
|
||||||
|
|
||||||
// UnmarshalPublicKey is used to parse individual public keys in JSON
|
// UnmarshalPublicKey is used to parse individual public keys in JSON
|
||||||
func UnmarshalPublicKey(data []byte) (PublicKey, error) {
|
func UnmarshalPublicKey(data []byte) (PublicKey, error) {
|
||||||
var parsed tufKey
|
var parsed TUFKey
|
||||||
err := json.Unmarshal(data, &parsed)
|
err := json.Unmarshal(data, &parsed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -185,7 +185,7 @@ func UnmarshalPublicKey(data []byte) (PublicKey, error) {
|
||||||
|
|
||||||
// UnmarshalPrivateKey is used to parse individual private keys in JSON
|
// UnmarshalPrivateKey is used to parse individual private keys in JSON
|
||||||
func UnmarshalPrivateKey(data []byte) (PrivateKey, error) {
|
func UnmarshalPrivateKey(data []byte) (PrivateKey, error) {
|
||||||
var parsed tufKey
|
var parsed TUFKey
|
||||||
err := json.Unmarshal(data, &parsed)
|
err := json.Unmarshal(data, &parsed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -193,26 +193,26 @@ func UnmarshalPrivateKey(data []byte) (PrivateKey, error) {
|
||||||
return typedPrivateKey(parsed)
|
return typedPrivateKey(parsed)
|
||||||
}
|
}
|
||||||
|
|
||||||
// tufKey is the structure used for both public and private keys in TUF.
|
// TUFKey is the structure used for both public and private keys in TUF.
|
||||||
// Normally it would make sense to use a different structures for public and
|
// Normally it would make sense to use a different structures for public and
|
||||||
// private keys, but that would change the key ID algorithm (since the canonical
|
// private keys, but that would change the key ID algorithm (since the canonical
|
||||||
// JSON would be different). This structure should normally be accessed through
|
// JSON would be different). This structure should normally be accessed through
|
||||||
// the PublicKey or PrivateKey interfaces.
|
// the PublicKey or PrivateKey interfaces.
|
||||||
type tufKey struct {
|
type TUFKey struct {
|
||||||
id string
|
id string
|
||||||
Type string `json:"keytype"`
|
Type string `json:"keytype"`
|
||||||
Value KeyPair `json:"keyval"`
|
Value KeyPair `json:"keyval"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Algorithm returns the algorithm of the key
|
// Algorithm returns the algorithm of the key
|
||||||
func (k tufKey) Algorithm() string {
|
func (k TUFKey) Algorithm() string {
|
||||||
return k.Type
|
return k.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
// ID efficiently generates if necessary, and caches the ID of the key
|
// ID efficiently generates if necessary, and caches the ID of the key
|
||||||
func (k *tufKey) ID() string {
|
func (k *TUFKey) ID() string {
|
||||||
if k.id == "" {
|
if k.id == "" {
|
||||||
pubK := tufKey{
|
pubK := TUFKey{
|
||||||
Type: k.Algorithm(),
|
Type: k.Algorithm(),
|
||||||
Value: KeyPair{
|
Value: KeyPair{
|
||||||
Public: k.Public(),
|
Public: k.Public(),
|
||||||
|
@ -230,7 +230,7 @@ func (k *tufKey) ID() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public returns the public bytes
|
// Public returns the public bytes
|
||||||
func (k tufKey) Public() []byte {
|
func (k TUFKey) Public() []byte {
|
||||||
return k.Value.Public
|
return k.Value.Public
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,42 +239,42 @@ func (k tufKey) Public() []byte {
|
||||||
// ECDSAPublicKey represents an ECDSA key using a raw serialization
|
// ECDSAPublicKey represents an ECDSA key using a raw serialization
|
||||||
// of the public key
|
// of the public key
|
||||||
type ECDSAPublicKey struct {
|
type ECDSAPublicKey struct {
|
||||||
tufKey
|
TUFKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// ECDSAx509PublicKey represents an ECDSA key using an x509 cert
|
// ECDSAx509PublicKey represents an ECDSA key using an x509 cert
|
||||||
// as the serialized format of the public key
|
// as the serialized format of the public key
|
||||||
type ECDSAx509PublicKey struct {
|
type ECDSAx509PublicKey struct {
|
||||||
tufKey
|
TUFKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// RSAPublicKey represents an RSA key using a raw serialization
|
// RSAPublicKey represents an RSA key using a raw serialization
|
||||||
// of the public key
|
// of the public key
|
||||||
type RSAPublicKey struct {
|
type RSAPublicKey struct {
|
||||||
tufKey
|
TUFKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// RSAx509PublicKey represents an RSA key using an x509 cert
|
// RSAx509PublicKey represents an RSA key using an x509 cert
|
||||||
// as the serialized format of the public key
|
// as the serialized format of the public key
|
||||||
type RSAx509PublicKey struct {
|
type RSAx509PublicKey struct {
|
||||||
tufKey
|
TUFKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// ED25519PublicKey represents an ED25519 key using a raw serialization
|
// ED25519PublicKey represents an ED25519 key using a raw serialization
|
||||||
// of the public key
|
// of the public key
|
||||||
type ED25519PublicKey struct {
|
type ED25519PublicKey struct {
|
||||||
tufKey
|
TUFKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnknownPublicKey is a catchall for key types that are not supported
|
// UnknownPublicKey is a catchall for key types that are not supported
|
||||||
type UnknownPublicKey struct {
|
type UnknownPublicKey struct {
|
||||||
tufKey
|
TUFKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewECDSAPublicKey initializes a new public key with the ECDSAKey type
|
// NewECDSAPublicKey initializes a new public key with the ECDSAKey type
|
||||||
func NewECDSAPublicKey(public []byte) *ECDSAPublicKey {
|
func NewECDSAPublicKey(public []byte) *ECDSAPublicKey {
|
||||||
return &ECDSAPublicKey{
|
return &ECDSAPublicKey{
|
||||||
tufKey: tufKey{
|
TUFKey: TUFKey{
|
||||||
Type: ECDSAKey,
|
Type: ECDSAKey,
|
||||||
Value: KeyPair{
|
Value: KeyPair{
|
||||||
Public: public,
|
Public: public,
|
||||||
|
@ -287,7 +287,7 @@ func NewECDSAPublicKey(public []byte) *ECDSAPublicKey {
|
||||||
// NewECDSAx509PublicKey initializes a new public key with the ECDSAx509Key type
|
// NewECDSAx509PublicKey initializes a new public key with the ECDSAx509Key type
|
||||||
func NewECDSAx509PublicKey(public []byte) *ECDSAx509PublicKey {
|
func NewECDSAx509PublicKey(public []byte) *ECDSAx509PublicKey {
|
||||||
return &ECDSAx509PublicKey{
|
return &ECDSAx509PublicKey{
|
||||||
tufKey: tufKey{
|
TUFKey: TUFKey{
|
||||||
Type: ECDSAx509Key,
|
Type: ECDSAx509Key,
|
||||||
Value: KeyPair{
|
Value: KeyPair{
|
||||||
Public: public,
|
Public: public,
|
||||||
|
@ -300,7 +300,7 @@ func NewECDSAx509PublicKey(public []byte) *ECDSAx509PublicKey {
|
||||||
// NewRSAPublicKey initializes a new public key with the RSA type
|
// NewRSAPublicKey initializes a new public key with the RSA type
|
||||||
func NewRSAPublicKey(public []byte) *RSAPublicKey {
|
func NewRSAPublicKey(public []byte) *RSAPublicKey {
|
||||||
return &RSAPublicKey{
|
return &RSAPublicKey{
|
||||||
tufKey: tufKey{
|
TUFKey: TUFKey{
|
||||||
Type: RSAKey,
|
Type: RSAKey,
|
||||||
Value: KeyPair{
|
Value: KeyPair{
|
||||||
Public: public,
|
Public: public,
|
||||||
|
@ -313,7 +313,7 @@ func NewRSAPublicKey(public []byte) *RSAPublicKey {
|
||||||
// NewRSAx509PublicKey initializes a new public key with the RSAx509Key type
|
// NewRSAx509PublicKey initializes a new public key with the RSAx509Key type
|
||||||
func NewRSAx509PublicKey(public []byte) *RSAx509PublicKey {
|
func NewRSAx509PublicKey(public []byte) *RSAx509PublicKey {
|
||||||
return &RSAx509PublicKey{
|
return &RSAx509PublicKey{
|
||||||
tufKey: tufKey{
|
TUFKey: TUFKey{
|
||||||
Type: RSAx509Key,
|
Type: RSAx509Key,
|
||||||
Value: KeyPair{
|
Value: KeyPair{
|
||||||
Public: public,
|
Public: public,
|
||||||
|
@ -326,7 +326,7 @@ func NewRSAx509PublicKey(public []byte) *RSAx509PublicKey {
|
||||||
// NewED25519PublicKey initializes a new public key with the ED25519Key type
|
// NewED25519PublicKey initializes a new public key with the ED25519Key type
|
||||||
func NewED25519PublicKey(public []byte) *ED25519PublicKey {
|
func NewED25519PublicKey(public []byte) *ED25519PublicKey {
|
||||||
return &ED25519PublicKey{
|
return &ED25519PublicKey{
|
||||||
tufKey: tufKey{
|
TUFKey: TUFKey{
|
||||||
Type: ED25519Key,
|
Type: ED25519Key,
|
||||||
Value: KeyPair{
|
Value: KeyPair{
|
||||||
Public: public,
|
Public: public,
|
||||||
|
@ -367,7 +367,7 @@ type ED25519PrivateKey struct {
|
||||||
|
|
||||||
// UnknownPrivateKey is a catchall for unsupported key types
|
// UnknownPrivateKey is a catchall for unsupported key types
|
||||||
type UnknownPrivateKey struct {
|
type UnknownPrivateKey struct {
|
||||||
tufKey
|
TUFKey
|
||||||
privateKey
|
privateKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -515,10 +515,10 @@ func (k UnknownPrivateKey) SignatureAlgorithm() SigAlgorithm {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// PublicKeyFromPrivate returns a new tufKey based on a private key, with
|
// PublicKeyFromPrivate returns a new TUFKey based on a private key, with
|
||||||
// the private key bytes guaranteed to be nil.
|
// the private key bytes guaranteed to be nil.
|
||||||
func PublicKeyFromPrivate(pk PrivateKey) PublicKey {
|
func PublicKeyFromPrivate(pk PrivateKey) PublicKey {
|
||||||
return typedPublicKey(tufKey{
|
return typedPublicKey(TUFKey{
|
||||||
Type: pk.Algorithm(),
|
Type: pk.Algorithm(),
|
||||||
Value: KeyPair{
|
Value: KeyPair{
|
||||||
Public: pk.Public(),
|
Public: pk.Public(),
|
||||||
|
|
|
@ -2,10 +2,11 @@ package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"path"
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Canonical base role names
|
// Canonical base role names
|
||||||
|
@ -85,32 +86,139 @@ func IsDelegation(role string) bool {
|
||||||
isClean
|
isClean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BaseRole is an internal representation of a root/targets/snapshot/timestamp role, with its public keys included
|
||||||
|
type BaseRole struct {
|
||||||
|
Keys map[string]PublicKey
|
||||||
|
Name string
|
||||||
|
Threshold int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBaseRole creates a new BaseRole object with the provided parameters
|
||||||
|
func NewBaseRole(name string, threshold int, keys ...PublicKey) BaseRole {
|
||||||
|
r := BaseRole{
|
||||||
|
Name: name,
|
||||||
|
Threshold: threshold,
|
||||||
|
Keys: make(map[string]PublicKey),
|
||||||
|
}
|
||||||
|
for _, k := range keys {
|
||||||
|
r.Keys[k.ID()] = k
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListKeys retrieves the public keys valid for this role
|
||||||
|
func (b BaseRole) ListKeys() KeyList {
|
||||||
|
return listKeys(b.Keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListKeyIDs retrieves the list of key IDs valid for this role
|
||||||
|
func (b BaseRole) ListKeyIDs() []string {
|
||||||
|
return listKeyIDs(b.Keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelegationRole is an internal representation of a delegation role, with its public keys included
|
||||||
|
type DelegationRole struct {
|
||||||
|
BaseRole
|
||||||
|
Paths []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func listKeys(keyMap map[string]PublicKey) KeyList {
|
||||||
|
keys := KeyList{}
|
||||||
|
for _, key := range keyMap {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
func listKeyIDs(keyMap map[string]PublicKey) []string {
|
||||||
|
keyIDs := []string{}
|
||||||
|
for id := range keyMap {
|
||||||
|
keyIDs = append(keyIDs, id)
|
||||||
|
}
|
||||||
|
return keyIDs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restrict restricts the paths and path hash prefixes for the passed in delegation role,
|
||||||
|
// returning a copy of the role with validated paths as if it was a direct child
|
||||||
|
func (d DelegationRole) Restrict(child DelegationRole) (DelegationRole, error) {
|
||||||
|
if !d.IsParentOf(child) {
|
||||||
|
return DelegationRole{}, fmt.Errorf("%s is not a parent of %s", d.Name, child.Name)
|
||||||
|
}
|
||||||
|
return DelegationRole{
|
||||||
|
BaseRole: BaseRole{
|
||||||
|
Keys: child.Keys,
|
||||||
|
Name: child.Name,
|
||||||
|
Threshold: child.Threshold,
|
||||||
|
},
|
||||||
|
Paths: RestrictDelegationPathPrefixes(d.Paths, child.Paths),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsParentOf returns whether the passed in delegation role is the direct child of this role,
|
||||||
|
// determined by delegation name.
|
||||||
|
// Ex: targets/a is a direct parent of targets/a/b, but targets/a is not a direct parent of targets/a/b/c
|
||||||
|
func (d DelegationRole) IsParentOf(child DelegationRole) bool {
|
||||||
|
return path.Dir(child.Name) == d.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckPaths checks if a given path is valid for the role
|
||||||
|
func (d DelegationRole) CheckPaths(path string) bool {
|
||||||
|
return checkPaths(path, d.Paths)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkPaths(path string, permitted []string) bool {
|
||||||
|
for _, p := range permitted {
|
||||||
|
if strings.HasPrefix(path, p) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// RestrictDelegationPathPrefixes returns the list of valid delegationPaths that are prefixed by parentPaths
|
||||||
|
func RestrictDelegationPathPrefixes(parentPaths, delegationPaths []string) []string {
|
||||||
|
validPaths := []string{}
|
||||||
|
if len(delegationPaths) == 0 {
|
||||||
|
return validPaths
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate each individual delegation path
|
||||||
|
for _, delgPath := range delegationPaths {
|
||||||
|
isPrefixed := false
|
||||||
|
for _, parentPath := range parentPaths {
|
||||||
|
if strings.HasPrefix(delgPath, parentPath) {
|
||||||
|
isPrefixed = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If the delegation path did not match prefix against any parent path, it is not valid
|
||||||
|
if isPrefixed {
|
||||||
|
validPaths = append(validPaths, delgPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return validPaths
|
||||||
|
}
|
||||||
|
|
||||||
// RootRole is a cut down role as it appears in the root.json
|
// RootRole is a cut down role as it appears in the root.json
|
||||||
|
// Eventually should only be used for immediately before and after serialization/deserialization
|
||||||
type RootRole struct {
|
type RootRole struct {
|
||||||
KeyIDs []string `json:"keyids"`
|
KeyIDs []string `json:"keyids"`
|
||||||
Threshold int `json:"threshold"`
|
Threshold int `json:"threshold"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Role is a more verbose role as they appear in targets delegations
|
// Role is a more verbose role as they appear in targets delegations
|
||||||
|
// Eventually should only be used for immediately before and after serialization/deserialization
|
||||||
type Role struct {
|
type Role struct {
|
||||||
RootRole
|
RootRole
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Paths []string `json:"paths,omitempty"`
|
Paths []string `json:"paths,omitempty"`
|
||||||
PathHashPrefixes []string `json:"path_hash_prefixes,omitempty"`
|
|
||||||
Email string `json:"email,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRole creates a new Role object from the given parameters
|
// NewRole creates a new Role object from the given parameters
|
||||||
func NewRole(name string, threshold int, keyIDs, paths, pathHashPrefixes []string) (*Role, error) {
|
func NewRole(name string, threshold int, keyIDs, paths []string) (*Role, error) {
|
||||||
if len(paths) > 0 && len(pathHashPrefixes) > 0 {
|
|
||||||
return nil, ErrInvalidRole{
|
|
||||||
Role: name,
|
|
||||||
Reason: "roles may not have both Paths and PathHashPrefixes",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if IsDelegation(name) {
|
if IsDelegation(name) {
|
||||||
if len(paths) == 0 && len(pathHashPrefixes) == 0 {
|
if len(paths) == 0 {
|
||||||
logrus.Debugf("role %s with no Paths and no PathHashPrefixes will never be able to publish content until one or more are added", name)
|
logrus.Debugf("role %s with no Paths will never be able to publish content until one or more are added", name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if threshold < 1 {
|
if threshold < 1 {
|
||||||
|
@ -124,52 +232,15 @@ func NewRole(name string, threshold int, keyIDs, paths, pathHashPrefixes []strin
|
||||||
KeyIDs: keyIDs,
|
KeyIDs: keyIDs,
|
||||||
Threshold: threshold,
|
Threshold: threshold,
|
||||||
},
|
},
|
||||||
Name: name,
|
Name: name,
|
||||||
Paths: paths,
|
Paths: paths,
|
||||||
PathHashPrefixes: pathHashPrefixes,
|
|
||||||
}, nil
|
}, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsValid checks if the role has defined both paths and path hash prefixes,
|
|
||||||
// having both is invalid
|
|
||||||
func (r Role) IsValid() bool {
|
|
||||||
return !(len(r.Paths) > 0 && len(r.PathHashPrefixes) > 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidKey checks if the given id is a recognized signing key for the role
|
|
||||||
func (r Role) ValidKey(id string) bool {
|
|
||||||
for _, key := range r.KeyIDs {
|
|
||||||
if key == id {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckPaths checks if a given path is valid for the role
|
// CheckPaths checks if a given path is valid for the role
|
||||||
func (r Role) CheckPaths(path string) bool {
|
func (r Role) CheckPaths(path string) bool {
|
||||||
for _, p := range r.Paths {
|
return checkPaths(path, r.Paths)
|
||||||
if strings.HasPrefix(path, p) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckPrefixes checks if a given hash matches the prefixes for the role
|
|
||||||
func (r Role) CheckPrefixes(hash string) bool {
|
|
||||||
for _, p := range r.PathHashPrefixes {
|
|
||||||
if strings.HasPrefix(hash, p) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsDelegation checks if the role is a delegation or a root role
|
|
||||||
func (r Role) IsDelegation() bool {
|
|
||||||
return IsDelegation(r.Name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddKeys merges the ids into the current list of role key ids
|
// AddKeys merges the ids into the current list of role key ids
|
||||||
|
@ -182,25 +253,10 @@ func (r *Role) AddPaths(paths []string) error {
|
||||||
if len(paths) == 0 {
|
if len(paths) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if len(r.PathHashPrefixes) > 0 {
|
|
||||||
return ErrInvalidRole{Role: r.Name, Reason: "attempted to add paths to role that already has hash prefixes"}
|
|
||||||
}
|
|
||||||
r.Paths = mergeStrSlices(r.Paths, paths)
|
r.Paths = mergeStrSlices(r.Paths, paths)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddPathHashPrefixes merges the prefixes into the list of role path hash prefixes
|
|
||||||
func (r *Role) AddPathHashPrefixes(prefixes []string) error {
|
|
||||||
if len(prefixes) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if len(r.Paths) > 0 {
|
|
||||||
return ErrInvalidRole{Role: r.Name, Reason: "attempted to add hash prefixes to role that already has paths"}
|
|
||||||
}
|
|
||||||
r.PathHashPrefixes = mergeStrSlices(r.PathHashPrefixes, prefixes)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveKeys removes the ids from the current list of key ids
|
// RemoveKeys removes the ids from the current list of key ids
|
||||||
func (r *Role) RemoveKeys(ids []string) {
|
func (r *Role) RemoveKeys(ids []string) {
|
||||||
r.KeyIDs = subtractStrSlices(r.KeyIDs, ids)
|
r.KeyIDs = subtractStrSlices(r.KeyIDs, ids)
|
||||||
|
@ -211,11 +267,6 @@ func (r *Role) RemovePaths(paths []string) {
|
||||||
r.Paths = subtractStrSlices(r.Paths, paths)
|
r.Paths = subtractStrSlices(r.Paths, paths)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemovePathHashPrefixes removes the prefixes from the current list of path hash prefixes
|
|
||||||
func (r *Role) RemovePathHashPrefixes(prefixes []string) {
|
|
||||||
r.PathHashPrefixes = subtractStrSlices(r.PathHashPrefixes, prefixes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func mergeStrSlices(orig, new []string) []string {
|
func mergeStrSlices(orig, new []string) []string {
|
||||||
have := make(map[string]bool)
|
have := make(map[string]bool)
|
||||||
for _, e := range orig {
|
for _, e := range orig {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package data
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/go/canonical/json"
|
"github.com/docker/go/canonical/json"
|
||||||
|
@ -23,14 +24,57 @@ type Root struct {
|
||||||
ConsistentSnapshot bool `json:"consistent_snapshot"`
|
ConsistentSnapshot bool `json:"consistent_snapshot"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isValidRootStructure returns an error, or nil, depending on whether the content of the struct
|
||||||
|
// is valid for root metadata. This does not check signatures or expiry, just that
|
||||||
|
// the metadata content is valid.
|
||||||
|
func isValidRootStructure(r Root) error {
|
||||||
|
expectedType := TUFTypes[CanonicalRootRole]
|
||||||
|
if r.Type != expectedType {
|
||||||
|
return ErrInvalidMetadata{
|
||||||
|
role: CanonicalRootRole, msg: fmt.Sprintf("expected type %s, not %s", expectedType, r.Type)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// all the base roles MUST appear in the root.json - other roles are allowed,
|
||||||
|
// but other than the mirror role (not currently supported) are out of spec
|
||||||
|
for _, roleName := range BaseRoles {
|
||||||
|
roleObj, ok := r.Roles[roleName]
|
||||||
|
if !ok || roleObj == nil {
|
||||||
|
return ErrInvalidMetadata{
|
||||||
|
role: CanonicalRootRole, msg: fmt.Sprintf("missing %s role specification", roleName)}
|
||||||
|
}
|
||||||
|
if err := isValidRootRoleStructure(CanonicalRootRole, roleName, *roleObj, r.Keys); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValidRootRoleStructure(metaContainingRole, rootRoleName string, r RootRole, validKeys Keys) error {
|
||||||
|
if r.Threshold < 1 {
|
||||||
|
return ErrInvalidMetadata{
|
||||||
|
role: metaContainingRole,
|
||||||
|
msg: fmt.Sprintf("invalid threshold specified for %s: %v ", rootRoleName, r.Threshold),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, keyID := range r.KeyIDs {
|
||||||
|
if _, ok := validKeys[keyID]; !ok {
|
||||||
|
return ErrInvalidMetadata{
|
||||||
|
role: metaContainingRole,
|
||||||
|
msg: fmt.Sprintf("key ID %s specified in %s without corresponding key", keyID, rootRoleName),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// NewRoot initializes a new SignedRoot with a set of keys, roles, and the consistent flag
|
// NewRoot initializes a new SignedRoot with a set of keys, roles, and the consistent flag
|
||||||
func NewRoot(keys map[string]PublicKey, roles map[string]*RootRole, consistent bool) (*SignedRoot, error) {
|
func NewRoot(keys map[string]PublicKey, roles map[string]*RootRole, consistent bool) (*SignedRoot, error) {
|
||||||
signedRoot := &SignedRoot{
|
signedRoot := &SignedRoot{
|
||||||
Signatures: make([]Signature, 0),
|
Signatures: make([]Signature, 0),
|
||||||
Signed: Root{
|
Signed: Root{
|
||||||
Type: TUFTypes["root"],
|
Type: TUFTypes[CanonicalRootRole],
|
||||||
Version: 0,
|
Version: 0,
|
||||||
Expires: DefaultExpires("root"),
|
Expires: DefaultExpires(CanonicalRootRole),
|
||||||
Keys: keys,
|
Keys: keys,
|
||||||
Roles: roles,
|
Roles: roles,
|
||||||
ConsistentSnapshot: consistent,
|
ConsistentSnapshot: consistent,
|
||||||
|
@ -41,6 +85,34 @@ func NewRoot(keys map[string]PublicKey, roles map[string]*RootRole, consistent b
|
||||||
return signedRoot, nil
|
return signedRoot, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BuildBaseRole returns a copy of a BaseRole using the information in this SignedRoot for the specified role name.
|
||||||
|
// Will error for invalid role name or key metadata within this SignedRoot
|
||||||
|
func (r SignedRoot) BuildBaseRole(roleName string) (BaseRole, error) {
|
||||||
|
roleData, ok := r.Signed.Roles[roleName]
|
||||||
|
if !ok {
|
||||||
|
return BaseRole{}, ErrInvalidRole{Role: roleName, Reason: "role not found in root file"}
|
||||||
|
}
|
||||||
|
// Get all public keys for the base role from TUF metadata
|
||||||
|
keyIDs := roleData.KeyIDs
|
||||||
|
pubKeys := make(map[string]PublicKey)
|
||||||
|
for _, keyID := range keyIDs {
|
||||||
|
pubKey, ok := r.Signed.Keys[keyID]
|
||||||
|
if !ok {
|
||||||
|
return BaseRole{}, ErrInvalidRole{
|
||||||
|
Role: roleName,
|
||||||
|
Reason: fmt.Sprintf("key with ID %s was not found in root metadata", keyID),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pubKeys[keyID] = pubKey
|
||||||
|
}
|
||||||
|
|
||||||
|
return BaseRole{
|
||||||
|
Name: roleName,
|
||||||
|
Keys: pubKeys,
|
||||||
|
Threshold: roleData.Threshold,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ToSigned partially serializes a SignedRoot for further signing
|
// ToSigned partially serializes a SignedRoot for further signing
|
||||||
func (r SignedRoot) ToSigned() (*Signed, error) {
|
func (r SignedRoot) ToSigned() (*Signed, error) {
|
||||||
s, err := defaultSerializer.MarshalCanonical(r.Signed)
|
s, err := defaultSerializer.MarshalCanonical(r.Signed)
|
||||||
|
@ -70,11 +142,14 @@ func (r SignedRoot) MarshalJSON() ([]byte, error) {
|
||||||
return defaultSerializer.Marshal(signed)
|
return defaultSerializer.Marshal(signed)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RootFromSigned fully unpacks a Signed object into a SignedRoot
|
// RootFromSigned fully unpacks a Signed object into a SignedRoot and ensures
|
||||||
|
// that it is a valid SignedRoot
|
||||||
func RootFromSigned(s *Signed) (*SignedRoot, error) {
|
func RootFromSigned(s *Signed) (*SignedRoot, error) {
|
||||||
r := Root{}
|
r := Root{}
|
||||||
err := json.Unmarshal(s.Signed, &r)
|
if err := defaultSerializer.Unmarshal(s.Signed, &r); err != nil {
|
||||||
if err != nil {
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := isValidRootStructure(r); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
sigs := make([]Signature, len(s.Signatures))
|
sigs := make([]Signature, len(s.Signatures))
|
||||||
|
|
|
@ -2,6 +2,8 @@ package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/sha256"
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
|
@ -23,6 +25,30 @@ type Snapshot struct {
|
||||||
Meta Files `json:"meta"`
|
Meta Files `json:"meta"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isValidSnapshotStructure returns an error, or nil, depending on whether the content of the
|
||||||
|
// struct is valid for snapshot metadata. This does not check signatures or expiry, just that
|
||||||
|
// the metadata content is valid.
|
||||||
|
func isValidSnapshotStructure(s Snapshot) error {
|
||||||
|
expectedType := TUFTypes[CanonicalSnapshotRole]
|
||||||
|
if s.Type != expectedType {
|
||||||
|
return ErrInvalidMetadata{
|
||||||
|
role: CanonicalSnapshotRole, msg: fmt.Sprintf("expected type %s, not %s", expectedType, s.Type)}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, role := range []string{CanonicalRootRole, CanonicalTargetsRole} {
|
||||||
|
// Meta is a map of FileMeta, so if the role isn't in the map it returns
|
||||||
|
// an empty FileMeta, which has an empty map, and you can check on keys
|
||||||
|
// from an empty map.
|
||||||
|
if checksum, ok := s.Meta[role].Hashes["sha256"]; !ok || len(checksum) != sha256.Size {
|
||||||
|
return ErrInvalidMetadata{
|
||||||
|
role: CanonicalSnapshotRole,
|
||||||
|
msg: fmt.Sprintf("missing or invalid %s sha256 checksum information", role),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// NewSnapshot initilizes a SignedSnapshot with a given top level root
|
// NewSnapshot initilizes a SignedSnapshot with a given top level root
|
||||||
// and targets objects
|
// and targets objects
|
||||||
func NewSnapshot(root *Signed, targets *Signed) (*SignedSnapshot, error) {
|
func NewSnapshot(root *Signed, targets *Signed) (*SignedSnapshot, error) {
|
||||||
|
@ -64,8 +90,8 @@ func (sp *SignedSnapshot) hashForRole(role string) []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToSigned partially serializes a SignedSnapshot for further signing
|
// ToSigned partially serializes a SignedSnapshot for further signing
|
||||||
func (sp SignedSnapshot) ToSigned() (*Signed, error) {
|
func (sp *SignedSnapshot) ToSigned() (*Signed, error) {
|
||||||
s, err := json.MarshalCanonical(sp.Signed)
|
s, err := defaultSerializer.MarshalCanonical(sp.Signed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -88,6 +114,15 @@ func (sp *SignedSnapshot) AddMeta(role string, meta FileMeta) {
|
||||||
sp.Dirty = true
|
sp.Dirty = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetMeta gets the metadata for a particular role, returning an error if it's
|
||||||
|
// not found
|
||||||
|
func (sp *SignedSnapshot) GetMeta(role string) (*FileMeta, error) {
|
||||||
|
if meta, ok := sp.Signed.Meta[role]; ok {
|
||||||
|
return &meta, nil
|
||||||
|
}
|
||||||
|
return nil, ErrMissingMeta{Role: role}
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteMeta removes a role from the snapshot. If the role doesn't
|
// DeleteMeta removes a role from the snapshot. If the role doesn't
|
||||||
// exist in the snapshot, it's a noop.
|
// exist in the snapshot, it's a noop.
|
||||||
func (sp *SignedSnapshot) DeleteMeta(role string) {
|
func (sp *SignedSnapshot) DeleteMeta(role string) {
|
||||||
|
@ -97,11 +132,22 @@ func (sp *SignedSnapshot) DeleteMeta(role string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalJSON returns the serialized form of SignedSnapshot as bytes
|
||||||
|
func (sp *SignedSnapshot) MarshalJSON() ([]byte, error) {
|
||||||
|
signed, err := sp.ToSigned()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return defaultSerializer.Marshal(signed)
|
||||||
|
}
|
||||||
|
|
||||||
// SnapshotFromSigned fully unpacks a Signed object into a SignedSnapshot
|
// SnapshotFromSigned fully unpacks a Signed object into a SignedSnapshot
|
||||||
func SnapshotFromSigned(s *Signed) (*SignedSnapshot, error) {
|
func SnapshotFromSigned(s *Signed) (*SignedSnapshot, error) {
|
||||||
sp := Snapshot{}
|
sp := Snapshot{}
|
||||||
err := json.Unmarshal(s.Signed, &sp)
|
if err := defaultSerializer.Unmarshal(s.Signed, &sp); err != nil {
|
||||||
if err != nil {
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := isValidSnapshotStructure(sp); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
sigs := make([]Signature, len(s.Signatures))
|
sigs := make([]Signature, len(s.Signatures))
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package data
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/hex"
|
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
|
||||||
"github.com/docker/go/canonical/json"
|
"github.com/docker/go/canonical/json"
|
||||||
)
|
)
|
||||||
|
@ -23,6 +23,33 @@ type Targets struct {
|
||||||
Delegations Delegations `json:"delegations,omitempty"`
|
Delegations Delegations `json:"delegations,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isValidTargetsStructure returns an error, or nil, depending on whether the content of the struct
|
||||||
|
// is valid for targets metadata. This does not check signatures or expiry, just that
|
||||||
|
// the metadata content is valid.
|
||||||
|
func isValidTargetsStructure(t Targets, roleName string) error {
|
||||||
|
if roleName != CanonicalTargetsRole && !IsDelegation(roleName) {
|
||||||
|
return ErrInvalidRole{Role: roleName}
|
||||||
|
}
|
||||||
|
|
||||||
|
// even if it's a delegated role, the metadata type is "Targets"
|
||||||
|
expectedType := TUFTypes[CanonicalTargetsRole]
|
||||||
|
if t.Type != expectedType {
|
||||||
|
return ErrInvalidMetadata{
|
||||||
|
role: roleName, msg: fmt.Sprintf("expected type %s, not %s", expectedType, t.Type)}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, roleObj := range t.Delegations.Roles {
|
||||||
|
if !IsDelegation(roleObj.Name) || path.Dir(roleObj.Name) != roleName {
|
||||||
|
return ErrInvalidMetadata{
|
||||||
|
role: roleName, msg: fmt.Sprintf("delegation role %s invalid", roleObj.Name)}
|
||||||
|
}
|
||||||
|
if err := isValidRootRoleStructure(roleName, roleObj.Name, roleObj.RootRole, t.Delegations.Keys); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// NewTargets intiializes a new empty SignedTargets object
|
// NewTargets intiializes a new empty SignedTargets object
|
||||||
func NewTargets() *SignedTargets {
|
func NewTargets() *SignedTargets {
|
||||||
return &SignedTargets{
|
return &SignedTargets{
|
||||||
|
@ -51,30 +78,58 @@ func (t SignedTargets) GetMeta(path string) *FileMeta {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDelegations filters the roles and associated keys that may be
|
// GetValidDelegations filters the delegation roles specified in the signed targets, and
|
||||||
// the signers for the given target path. If no appropriate roles
|
// only returns roles that are direct children and restricts their paths
|
||||||
// can be found, it will simply return nil for the return values.
|
func (t SignedTargets) GetValidDelegations(parent DelegationRole) []DelegationRole {
|
||||||
// The returned slice of Role will have order maintained relative
|
roles := t.buildDelegationRoles()
|
||||||
// to the role slice on Delegations per TUF spec proposal on using
|
result := []DelegationRole{}
|
||||||
// order to determine priority.
|
for _, r := range roles {
|
||||||
func (t SignedTargets) GetDelegations(path string) []*Role {
|
validRole, err := parent.Restrict(r)
|
||||||
var roles []*Role
|
if err != nil {
|
||||||
pathHashBytes := sha256.Sum256([]byte(path))
|
|
||||||
pathHash := hex.EncodeToString(pathHashBytes[:])
|
|
||||||
for _, r := range t.Signed.Delegations.Roles {
|
|
||||||
if !r.IsValid() {
|
|
||||||
// Role has both Paths and PathHashPrefixes.
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if r.CheckPaths(path) {
|
result = append(result, validRole)
|
||||||
roles = append(roles, r)
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildDelegationRole returns a copy of a DelegationRole using the information in this SignedTargets for the specified role name.
|
||||||
|
// Will error for invalid role name or key metadata within this SignedTargets. Path data is not validated.
|
||||||
|
func (t *SignedTargets) BuildDelegationRole(roleName string) (DelegationRole, error) {
|
||||||
|
for _, role := range t.Signed.Delegations.Roles {
|
||||||
|
if role.Name == roleName {
|
||||||
|
pubKeys := make(map[string]PublicKey)
|
||||||
|
for _, keyID := range role.KeyIDs {
|
||||||
|
pubKey, ok := t.Signed.Delegations.Keys[keyID]
|
||||||
|
if !ok {
|
||||||
|
// Couldn't retrieve all keys, so stop walking and return invalid role
|
||||||
|
return DelegationRole{}, ErrInvalidRole{Role: roleName, Reason: "delegation does not exist with all specified keys"}
|
||||||
|
}
|
||||||
|
pubKeys[keyID] = pubKey
|
||||||
|
}
|
||||||
|
return DelegationRole{
|
||||||
|
BaseRole: BaseRole{
|
||||||
|
Name: role.Name,
|
||||||
|
Keys: pubKeys,
|
||||||
|
Threshold: role.Threshold,
|
||||||
|
},
|
||||||
|
Paths: role.Paths,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return DelegationRole{}, ErrNoSuchRole{Role: roleName}
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper function to create DelegationRole structures from all delegations in a SignedTargets,
|
||||||
|
// these delegations are read directly from the SignedTargets and not modified or validated
|
||||||
|
func (t SignedTargets) buildDelegationRoles() []DelegationRole {
|
||||||
|
var roles []DelegationRole
|
||||||
|
for _, roleData := range t.Signed.Delegations.Roles {
|
||||||
|
delgRole, err := t.BuildDelegationRole(roleData.Name)
|
||||||
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if r.CheckPrefixes(pathHash) {
|
roles = append(roles, delgRole)
|
||||||
roles = append(roles, r)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
//keysDB.AddRole(r)
|
|
||||||
}
|
}
|
||||||
return roles
|
return roles
|
||||||
}
|
}
|
||||||
|
@ -93,8 +148,8 @@ func (t *SignedTargets) AddDelegation(role *Role, keys []*PublicKey) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToSigned partially serializes a SignedTargets for further signing
|
// ToSigned partially serializes a SignedTargets for further signing
|
||||||
func (t SignedTargets) ToSigned() (*Signed, error) {
|
func (t *SignedTargets) ToSigned() (*Signed, error) {
|
||||||
s, err := json.MarshalCanonical(t.Signed)
|
s, err := defaultSerializer.MarshalCanonical(t.Signed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -111,13 +166,25 @@ func (t SignedTargets) ToSigned() (*Signed, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TargetsFromSigned fully unpacks a Signed object into a SignedTargets
|
// MarshalJSON returns the serialized form of SignedTargets as bytes
|
||||||
func TargetsFromSigned(s *Signed) (*SignedTargets, error) {
|
func (t *SignedTargets) MarshalJSON() ([]byte, error) {
|
||||||
t := Targets{}
|
signed, err := t.ToSigned()
|
||||||
err := json.Unmarshal(s.Signed, &t)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return defaultSerializer.Marshal(signed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TargetsFromSigned fully unpacks a Signed object into a SignedTargets, given
|
||||||
|
// a role name (so it can validate the SignedTargets object)
|
||||||
|
func TargetsFromSigned(s *Signed, roleName string) (*SignedTargets, error) {
|
||||||
|
t := Targets{}
|
||||||
|
if err := defaultSerializer.Unmarshal(s.Signed, &t); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := isValidTargetsStructure(t, roleName); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
sigs := make([]Signature, len(s.Signatures))
|
sigs := make([]Signature, len(s.Signatures))
|
||||||
copy(sigs, s.Signatures)
|
copy(sigs, s.Signatures)
|
||||||
return &SignedTargets{
|
return &SignedTargets{
|
||||||
|
|
|
@ -2,6 +2,8 @@ package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/sha256"
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/go/canonical/json"
|
"github.com/docker/go/canonical/json"
|
||||||
|
@ -22,6 +24,26 @@ type Timestamp struct {
|
||||||
Meta Files `json:"meta"`
|
Meta Files `json:"meta"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isValidTimestampStructure returns an error, or nil, depending on whether the content of the struct
|
||||||
|
// is valid for timestamp metadata. This does not check signatures or expiry, just that
|
||||||
|
// the metadata content is valid.
|
||||||
|
func isValidTimestampStructure(t Timestamp) error {
|
||||||
|
expectedType := TUFTypes[CanonicalTimestampRole]
|
||||||
|
if t.Type != expectedType {
|
||||||
|
return ErrInvalidMetadata{
|
||||||
|
role: CanonicalTimestampRole, msg: fmt.Sprintf("expected type %s, not %s", expectedType, t.Type)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Meta is a map of FileMeta, so if the role isn't in the map it returns
|
||||||
|
// an empty FileMeta, which has an empty map, and you can check on keys
|
||||||
|
// from an empty map.
|
||||||
|
if cs, ok := t.Meta[CanonicalSnapshotRole].Hashes["sha256"]; !ok || len(cs) != sha256.Size {
|
||||||
|
return ErrInvalidMetadata{
|
||||||
|
role: CanonicalTimestampRole, msg: "missing or invalid snapshot sha256 checksum information"}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// NewTimestamp initializes a timestamp with an existing snapshot
|
// NewTimestamp initializes a timestamp with an existing snapshot
|
||||||
func NewTimestamp(snapshot *Signed) (*SignedTimestamp, error) {
|
func NewTimestamp(snapshot *Signed) (*SignedTimestamp, error) {
|
||||||
snapshotJSON, err := json.Marshal(snapshot)
|
snapshotJSON, err := json.Marshal(snapshot)
|
||||||
|
@ -47,8 +69,8 @@ func NewTimestamp(snapshot *Signed) (*SignedTimestamp, error) {
|
||||||
|
|
||||||
// ToSigned partially serializes a SignedTimestamp such that it can
|
// ToSigned partially serializes a SignedTimestamp such that it can
|
||||||
// be signed
|
// be signed
|
||||||
func (ts SignedTimestamp) ToSigned() (*Signed, error) {
|
func (ts *SignedTimestamp) ToSigned() (*Signed, error) {
|
||||||
s, err := json.MarshalCanonical(ts.Signed)
|
s, err := defaultSerializer.MarshalCanonical(ts.Signed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -65,12 +87,33 @@ func (ts SignedTimestamp) ToSigned() (*Signed, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetSnapshot gets the expected snapshot metadata hashes in the timestamp metadata,
|
||||||
|
// or nil if it doesn't exist
|
||||||
|
func (ts *SignedTimestamp) GetSnapshot() (*FileMeta, error) {
|
||||||
|
snapshotExpected, ok := ts.Signed.Meta[CanonicalSnapshotRole]
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrMissingMeta{Role: CanonicalSnapshotRole}
|
||||||
|
}
|
||||||
|
return &snapshotExpected, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON returns the serialized form of SignedTimestamp as bytes
|
||||||
|
func (ts *SignedTimestamp) MarshalJSON() ([]byte, error) {
|
||||||
|
signed, err := ts.ToSigned()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return defaultSerializer.Marshal(signed)
|
||||||
|
}
|
||||||
|
|
||||||
// TimestampFromSigned parsed a Signed object into a fully unpacked
|
// TimestampFromSigned parsed a Signed object into a fully unpacked
|
||||||
// SignedTimestamp
|
// SignedTimestamp
|
||||||
func TimestampFromSigned(s *Signed) (*SignedTimestamp, error) {
|
func TimestampFromSigned(s *Signed) (*SignedTimestamp, error) {
|
||||||
ts := Timestamp{}
|
ts := Timestamp{}
|
||||||
err := json.Unmarshal(s.Signed, &ts)
|
if err := defaultSerializer.Unmarshal(s.Signed, &ts); err != nil {
|
||||||
if err != nil {
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := isValidTimestampStructure(ts); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
sigs := make([]Signature, len(s.Signatures))
|
sigs := make([]Signature, len(s.Signatures))
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/go/canonical/json"
|
"github.com/docker/go/canonical/json"
|
||||||
|
"github.com/docker/notary"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SigAlgorithm for types of signatures
|
// SigAlgorithm for types of signatures
|
||||||
|
@ -171,16 +172,16 @@ func NewDelegations() *Delegations {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// defines number of days in which something should expire
|
// These values are recommended TUF expiry times.
|
||||||
var defaultExpiryTimes = map[string]int{
|
var defaultExpiryTimes = map[string]time.Duration{
|
||||||
CanonicalRootRole: 365,
|
CanonicalRootRole: notary.Year,
|
||||||
CanonicalTargetsRole: 90,
|
CanonicalTargetsRole: 90 * notary.Day,
|
||||||
CanonicalSnapshotRole: 7,
|
CanonicalSnapshotRole: 7 * notary.Day,
|
||||||
CanonicalTimestampRole: 1,
|
CanonicalTimestampRole: notary.Day,
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDefaultExpiryTimes allows one to change the default expiries.
|
// SetDefaultExpiryTimes allows one to change the default expiries.
|
||||||
func SetDefaultExpiryTimes(times map[string]int) {
|
func SetDefaultExpiryTimes(times map[string]time.Duration) {
|
||||||
for key, value := range times {
|
for key, value := range times {
|
||||||
if _, ok := defaultExpiryTimes[key]; !ok {
|
if _, ok := defaultExpiryTimes[key]; !ok {
|
||||||
logrus.Errorf("Attempted to set default expiry for an unknown role: %s", key)
|
logrus.Errorf("Attempted to set default expiry for an unknown role: %s", key)
|
||||||
|
@ -192,10 +193,10 @@ func SetDefaultExpiryTimes(times map[string]int) {
|
||||||
|
|
||||||
// DefaultExpires gets the default expiry time for the given role
|
// DefaultExpires gets the default expiry time for the given role
|
||||||
func DefaultExpires(role string) time.Time {
|
func DefaultExpires(role string) time.Time {
|
||||||
var t time.Time
|
if d, ok := defaultExpiryTimes[role]; ok {
|
||||||
if t, ok := defaultExpiryTimes[role]; ok {
|
return time.Now().Add(d)
|
||||||
return time.Now().AddDate(0, 0, t)
|
|
||||||
}
|
}
|
||||||
|
var t time.Time
|
||||||
return t.UTC().Round(time.Second)
|
return t.UTC().Round(time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,78 +0,0 @@
|
||||||
package keys
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/docker/notary/tuf/data"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Various basic key database errors
|
|
||||||
var (
|
|
||||||
ErrWrongType = errors.New("tuf: invalid key type")
|
|
||||||
ErrExists = errors.New("tuf: key already in db")
|
|
||||||
ErrWrongID = errors.New("tuf: key id mismatch")
|
|
||||||
ErrInvalidKey = errors.New("tuf: invalid key")
|
|
||||||
ErrInvalidKeyID = errors.New("tuf: invalid key id")
|
|
||||||
ErrInvalidThreshold = errors.New("tuf: invalid role threshold")
|
|
||||||
)
|
|
||||||
|
|
||||||
// KeyDB is an in memory database of public keys and role associations.
|
|
||||||
// It is populated when parsing TUF files and used during signature
|
|
||||||
// verification to look up the keys for a given role
|
|
||||||
type KeyDB struct {
|
|
||||||
roles map[string]*data.Role
|
|
||||||
keys map[string]data.PublicKey
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDB initializes an empty KeyDB
|
|
||||||
func NewDB() *KeyDB {
|
|
||||||
return &KeyDB{
|
|
||||||
roles: make(map[string]*data.Role),
|
|
||||||
keys: make(map[string]data.PublicKey),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddKey adds a public key to the database
|
|
||||||
func (db *KeyDB) AddKey(k data.PublicKey) {
|
|
||||||
db.keys[k.ID()] = k
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddRole adds a role to the database. Any keys associated with the
|
|
||||||
// role must have already been added.
|
|
||||||
func (db *KeyDB) AddRole(r *data.Role) error {
|
|
||||||
if !data.ValidRole(r.Name) {
|
|
||||||
return data.ErrInvalidRole{Role: r.Name}
|
|
||||||
}
|
|
||||||
if r.Threshold < 1 {
|
|
||||||
return ErrInvalidThreshold
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate all key ids are in the keys maps
|
|
||||||
for _, id := range r.KeyIDs {
|
|
||||||
if _, ok := db.keys[id]; !ok {
|
|
||||||
return ErrInvalidKeyID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
db.roles[r.Name] = r
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAllRoles gets all roles from the database
|
|
||||||
func (db *KeyDB) GetAllRoles() []*data.Role {
|
|
||||||
roles := []*data.Role{}
|
|
||||||
for _, role := range db.roles {
|
|
||||||
roles = append(roles, role)
|
|
||||||
}
|
|
||||||
return roles
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetKey pulls a key out of the database by its ID
|
|
||||||
func (db *KeyDB) GetKey(id string) data.PublicKey {
|
|
||||||
return db.keys[id]
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRole retrieves a role based on its name
|
|
||||||
func (db *KeyDB) GetRole(name string) *data.Role {
|
|
||||||
return db.roles[name]
|
|
||||||
}
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/go/canonical/json"
|
"github.com/docker/go/canonical/json"
|
||||||
"github.com/docker/notary/tuf/data"
|
"github.com/docker/notary/tuf/data"
|
||||||
"github.com/docker/notary/tuf/keys"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Various basic signing errors
|
// Various basic signing errors
|
||||||
|
@ -57,18 +56,18 @@ func VerifyRoot(s *data.Signed, minVersion int, keys map[string]data.PublicKey)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// threshold of 1 so return on first success
|
// threshold of 1 so return on first success
|
||||||
return verifyMeta(s, "root", minVersion)
|
return verifyMeta(s, data.CanonicalRootRole, minVersion)
|
||||||
}
|
}
|
||||||
return ErrRoleThreshold{}
|
return ErrRoleThreshold{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify checks the signatures and metadata (expiry, version) for the signed role
|
// Verify checks the signatures and metadata (expiry, version) for the signed role
|
||||||
// data
|
// data
|
||||||
func Verify(s *data.Signed, role string, minVersion int, db *keys.KeyDB) error {
|
func Verify(s *data.Signed, role data.BaseRole, minVersion int) error {
|
||||||
if err := verifyMeta(s, role, minVersion); err != nil {
|
if err := verifyMeta(s, role.Name, minVersion); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return VerifySignatures(s, role, db)
|
return VerifySignatures(s, role)
|
||||||
}
|
}
|
||||||
|
|
||||||
func verifyMeta(s *data.Signed, role string, minVersion int) error {
|
func verifyMeta(s *data.Signed, role string, minVersion int) error {
|
||||||
|
@ -96,21 +95,18 @@ func IsExpired(t time.Time) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifySignatures checks the we have sufficient valid signatures for the given role
|
// VerifySignatures checks the we have sufficient valid signatures for the given role
|
||||||
func VerifySignatures(s *data.Signed, role string, db *keys.KeyDB) error {
|
func VerifySignatures(s *data.Signed, roleData data.BaseRole) error {
|
||||||
if len(s.Signatures) == 0 {
|
if len(s.Signatures) == 0 {
|
||||||
return ErrNoSignatures
|
return ErrNoSignatures
|
||||||
}
|
}
|
||||||
|
|
||||||
roleData := db.GetRole(role)
|
|
||||||
if roleData == nil {
|
|
||||||
return ErrUnknownRole
|
|
||||||
}
|
|
||||||
|
|
||||||
if roleData.Threshold < 1 {
|
if roleData.Threshold < 1 {
|
||||||
return ErrRoleThreshold{}
|
return ErrRoleThreshold{}
|
||||||
}
|
}
|
||||||
logrus.Debugf("%s role has key IDs: %s", role, strings.Join(roleData.KeyIDs, ","))
|
logrus.Debugf("%s role has key IDs: %s", roleData.Name, strings.Join(roleData.ListKeyIDs(), ","))
|
||||||
|
|
||||||
|
// remarshal the signed part so we can verify the signature, since the signature has
|
||||||
|
// to be of a canonically marshalled signed object
|
||||||
var decoded map[string]interface{}
|
var decoded map[string]interface{}
|
||||||
if err := json.Unmarshal(s.Signed, &decoded); err != nil {
|
if err := json.Unmarshal(s.Signed, &decoded); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -123,12 +119,8 @@ func VerifySignatures(s *data.Signed, role string, db *keys.KeyDB) error {
|
||||||
valid := make(map[string]struct{})
|
valid := make(map[string]struct{})
|
||||||
for _, sig := range s.Signatures {
|
for _, sig := range s.Signatures {
|
||||||
logrus.Debug("verifying signature for key ID: ", sig.KeyID)
|
logrus.Debug("verifying signature for key ID: ", sig.KeyID)
|
||||||
if !roleData.ValidKey(sig.KeyID) {
|
key, ok := roleData.Keys[sig.KeyID]
|
||||||
logrus.Debugf("continuing b/c keyid was invalid: %s for roledata %s\n", sig.KeyID, roleData)
|
if !ok {
|
||||||
continue
|
|
||||||
}
|
|
||||||
key := db.GetKey(sig.KeyID)
|
|
||||||
if key == nil {
|
|
||||||
logrus.Debugf("continuing b/c keyid lookup was nil: %s\n", sig.KeyID)
|
logrus.Debugf("continuing b/c keyid lookup was nil: %s\n", sig.KeyID)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -153,28 +145,3 @@ func VerifySignatures(s *data.Signed, role string, db *keys.KeyDB) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unmarshal unmarshals and verifys the raw bytes for a given role's metadata
|
|
||||||
func Unmarshal(b []byte, v interface{}, role string, minVersion int, db *keys.KeyDB) error {
|
|
||||||
s := &data.Signed{}
|
|
||||||
if err := json.Unmarshal(b, s); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := Verify(s, role, minVersion, db); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return json.Unmarshal(s.Signed, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalTrusted unmarshals and verifies signatures only, not metadata, for a
|
|
||||||
// given role's metadata
|
|
||||||
func UnmarshalTrusted(b []byte, v interface{}, role string, db *keys.KeyDB) error {
|
|
||||||
s := &data.Signed{}
|
|
||||||
if err := json.Unmarshal(b, s); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := VerifySignatures(s, role, db); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return json.Unmarshal(s.Signed, v)
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package store
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/docker/notary"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
@ -9,25 +10,19 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewFilesystemStore creates a new store in a directory tree
|
// NewFilesystemStore creates a new store in a directory tree
|
||||||
func NewFilesystemStore(baseDir, metaSubDir, metaExtension, targetsSubDir string) (*FilesystemStore, error) {
|
func NewFilesystemStore(baseDir, metaSubDir, metaExtension string) (*FilesystemStore, error) {
|
||||||
metaDir := path.Join(baseDir, metaSubDir)
|
metaDir := path.Join(baseDir, metaSubDir)
|
||||||
targetsDir := path.Join(baseDir, targetsSubDir)
|
|
||||||
|
|
||||||
// Make sure we can create the necessary dirs and they are writable
|
// Make sure we can create the necessary dirs and they are writable
|
||||||
err := os.MkdirAll(metaDir, 0700)
|
err := os.MkdirAll(metaDir, 0700)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
err = os.MkdirAll(targetsDir, 0700)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &FilesystemStore{
|
return &FilesystemStore{
|
||||||
baseDir: baseDir,
|
baseDir: baseDir,
|
||||||
metaDir: metaDir,
|
metaDir: metaDir,
|
||||||
metaExtension: metaExtension,
|
metaExtension: metaExtension,
|
||||||
targetsDir: targetsDir,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +31,6 @@ type FilesystemStore struct {
|
||||||
baseDir string
|
baseDir string
|
||||||
metaDir string
|
metaDir string
|
||||||
metaExtension string
|
metaExtension string
|
||||||
targetsDir string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FilesystemStore) getPath(name string) string {
|
func (f *FilesystemStore) getPath(name string) string {
|
||||||
|
@ -44,7 +38,8 @@ func (f *FilesystemStore) getPath(name string) string {
|
||||||
return filepath.Join(f.metaDir, fileName)
|
return filepath.Join(f.metaDir, fileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMeta returns the meta for the given name (a role)
|
// GetMeta returns the meta for the given name (a role) up to size bytes
|
||||||
|
// If size is -1, this corresponds to "infinite," but we cut off at 100MB
|
||||||
func (f *FilesystemStore) GetMeta(name string, size int64) ([]byte, error) {
|
func (f *FilesystemStore) GetMeta(name string, size int64) ([]byte, error) {
|
||||||
meta, err := ioutil.ReadFile(f.getPath(name))
|
meta, err := ioutil.ReadFile(f.getPath(name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -53,7 +48,14 @@ func (f *FilesystemStore) GetMeta(name string, size int64) ([]byte, error) {
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return meta, nil
|
if size == -1 {
|
||||||
|
size = notary.MaxDownloadSize
|
||||||
|
}
|
||||||
|
// Only return up to size bytes
|
||||||
|
if int64(len(meta)) < size {
|
||||||
|
return meta, nil
|
||||||
|
}
|
||||||
|
return meta[:size], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetMultiMeta sets the metadata for multiple roles in one operation
|
// SetMultiMeta sets the metadata for multiple roles in one operation
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/notary"
|
||||||
"github.com/docker/notary/tuf/validation"
|
"github.com/docker/notary/tuf/validation"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -33,6 +34,9 @@ type ErrServerUnavailable struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (err ErrServerUnavailable) Error() string {
|
func (err ErrServerUnavailable) Error() string {
|
||||||
|
if err.code == 401 {
|
||||||
|
return fmt.Sprintf("you are not authorized to perform this operation: server returned 401.")
|
||||||
|
}
|
||||||
return fmt.Sprintf("unable to reach trust server at this time: %d.", err.code)
|
return fmt.Sprintf("unable to reach trust server at this time: %d.", err.code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,13 +75,12 @@ type HTTPStore struct {
|
||||||
baseURL url.URL
|
baseURL url.URL
|
||||||
metaPrefix string
|
metaPrefix string
|
||||||
metaExtension string
|
metaExtension string
|
||||||
targetsPrefix string
|
|
||||||
keyExtension string
|
keyExtension string
|
||||||
roundTrip http.RoundTripper
|
roundTrip http.RoundTripper
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHTTPStore initializes a new store against a URL and a number of configuration options
|
// NewHTTPStore initializes a new store against a URL and a number of configuration options
|
||||||
func NewHTTPStore(baseURL, metaPrefix, metaExtension, targetsPrefix, keyExtension string, roundTrip http.RoundTripper) (RemoteStore, error) {
|
func NewHTTPStore(baseURL, metaPrefix, metaExtension, keyExtension string, roundTrip http.RoundTripper) (RemoteStore, error) {
|
||||||
base, err := url.Parse(baseURL)
|
base, err := url.Parse(baseURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -92,7 +95,6 @@ func NewHTTPStore(baseURL, metaPrefix, metaExtension, targetsPrefix, keyExtensio
|
||||||
baseURL: *base,
|
baseURL: *base,
|
||||||
metaPrefix: metaPrefix,
|
metaPrefix: metaPrefix,
|
||||||
metaExtension: metaExtension,
|
metaExtension: metaExtension,
|
||||||
targetsPrefix: targetsPrefix,
|
|
||||||
keyExtension: keyExtension,
|
keyExtension: keyExtension,
|
||||||
roundTrip: roundTrip,
|
roundTrip: roundTrip,
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -137,6 +139,7 @@ func translateStatusToError(resp *http.Response, resource string) error {
|
||||||
// GetMeta downloads the named meta file with the given size. A short body
|
// GetMeta downloads the named meta file with the given size. A short body
|
||||||
// is acceptable because in the case of timestamp.json, the size is a cap,
|
// is acceptable because in the case of timestamp.json, the size is a cap,
|
||||||
// not an exact length.
|
// not an exact length.
|
||||||
|
// If size is -1, this corresponds to "infinite," but we cut off at 100MB
|
||||||
func (s HTTPStore) GetMeta(name string, size int64) ([]byte, error) {
|
func (s HTTPStore) GetMeta(name string, size int64) ([]byte, error) {
|
||||||
url, err := s.buildMetaURL(name)
|
url, err := s.buildMetaURL(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -155,6 +158,9 @@ func (s HTTPStore) GetMeta(name string, size int64) ([]byte, error) {
|
||||||
logrus.Debugf("received HTTP status %d when requesting %s.", resp.StatusCode, name)
|
logrus.Debugf("received HTTP status %d when requesting %s.", resp.StatusCode, name)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if size == -1 {
|
||||||
|
size = notary.MaxDownloadSize
|
||||||
|
}
|
||||||
if resp.ContentLength > size {
|
if resp.ContentLength > size {
|
||||||
return nil, ErrMaliciousServer{}
|
return nil, ErrMaliciousServer{}
|
||||||
}
|
}
|
||||||
|
@ -250,11 +256,6 @@ func (s HTTPStore) buildMetaURL(name string) (*url.URL, error) {
|
||||||
return s.buildURL(uri)
|
return s.buildURL(uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s HTTPStore) buildTargetsURL(name string) (*url.URL, error) {
|
|
||||||
uri := path.Join(s.targetsPrefix, name)
|
|
||||||
return s.buildURL(uri)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s HTTPStore) buildKeyURL(name string) (*url.URL, error) {
|
func (s HTTPStore) buildKeyURL(name string) (*url.URL, error) {
|
||||||
filename := fmt.Sprintf("%s.%s", name, s.keyExtension)
|
filename := fmt.Sprintf("%s.%s", name, s.keyExtension)
|
||||||
uri := path.Join(s.metaPrefix, filename)
|
uri := path.Join(s.metaPrefix, filename)
|
||||||
|
@ -269,29 +270,6 @@ func (s HTTPStore) buildURL(uri string) (*url.URL, error) {
|
||||||
return s.baseURL.ResolveReference(sub), nil
|
return s.baseURL.ResolveReference(sub), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTarget returns a reader for the desired target or an error.
|
|
||||||
// N.B. The caller is responsible for closing the reader.
|
|
||||||
func (s HTTPStore) GetTarget(path string) (io.ReadCloser, error) {
|
|
||||||
url, err := s.buildTargetsURL(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
logrus.Debug("Attempting to download target: ", url.String())
|
|
||||||
req, err := http.NewRequest("GET", url.String(), nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
resp, err := s.roundTrip.RoundTrip(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
if err := translateStatusToError(resp, path); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return resp.Body, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetKey retrieves a public key from the remote server
|
// GetKey retrieves a public key from the remote server
|
||||||
func (s HTTPStore) GetKey(role string) ([]byte, error) {
|
func (s HTTPStore) GetKey(role string) ([]byte, error) {
|
||||||
url, err := s.buildKeyURL(role)
|
url, err := s.buildKeyURL(role)
|
||||||
|
|
|
@ -1,13 +1,5 @@
|
||||||
package store
|
package store
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/docker/notary/tuf/data"
|
|
||||||
)
|
|
||||||
|
|
||||||
type targetsWalkFunc func(path string, meta data.FileMeta) error
|
|
||||||
|
|
||||||
// MetadataStore must be implemented by anything that intends to interact
|
// MetadataStore must be implemented by anything that intends to interact
|
||||||
// with a store of TUF files
|
// with a store of TUF files
|
||||||
type MetadataStore interface {
|
type MetadataStore interface {
|
||||||
|
@ -23,17 +15,9 @@ type PublicKeyStore interface {
|
||||||
GetKey(role string) ([]byte, error)
|
GetKey(role string) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TargetStore represents a collection of targets that can be walked similarly
|
|
||||||
// to walking a directory, passing a callback that receives the path and meta
|
|
||||||
// for each target
|
|
||||||
type TargetStore interface {
|
|
||||||
WalkStagedTargets(paths []string, targetsFn targetsWalkFunc) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// LocalStore represents a local TUF sture
|
// LocalStore represents a local TUF sture
|
||||||
type LocalStore interface {
|
type LocalStore interface {
|
||||||
MetadataStore
|
MetadataStore
|
||||||
TargetStore
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoteStore is similar to LocalStore with the added expectation that it should
|
// RemoteStore is similar to LocalStore with the added expectation that it should
|
||||||
|
@ -41,5 +25,4 @@ type LocalStore interface {
|
||||||
type RemoteStore interface {
|
type RemoteStore interface {
|
||||||
MetadataStore
|
MetadataStore
|
||||||
PublicKeyStore
|
PublicKeyStore
|
||||||
GetTarget(path string) (io.ReadCloser, error)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,38 +1,59 @@
|
||||||
package store
|
package store
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
|
|
||||||
|
"github.com/docker/notary"
|
||||||
"github.com/docker/notary/tuf/data"
|
"github.com/docker/notary/tuf/data"
|
||||||
"github.com/docker/notary/tuf/utils"
|
"github.com/docker/notary/tuf/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewMemoryStore returns a MetadataStore that operates entirely in memory.
|
// NewMemoryStore returns a MetadataStore that operates entirely in memory.
|
||||||
// Very useful for testing
|
// Very useful for testing
|
||||||
func NewMemoryStore(meta map[string][]byte, files map[string][]byte) RemoteStore {
|
func NewMemoryStore(meta map[string][]byte) *MemoryStore {
|
||||||
|
var consistent = make(map[string][]byte)
|
||||||
if meta == nil {
|
if meta == nil {
|
||||||
meta = make(map[string][]byte)
|
meta = make(map[string][]byte)
|
||||||
|
} else {
|
||||||
|
// add all seed meta to consistent
|
||||||
|
for name, data := range meta {
|
||||||
|
checksum := sha256.Sum256(data)
|
||||||
|
path := utils.ConsistentName(name, checksum[:])
|
||||||
|
consistent[path] = data
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if files == nil {
|
return &MemoryStore{
|
||||||
files = make(map[string][]byte)
|
meta: meta,
|
||||||
}
|
consistent: consistent,
|
||||||
return &memoryStore{
|
keys: make(map[string][]data.PrivateKey),
|
||||||
meta: meta,
|
|
||||||
files: files,
|
|
||||||
keys: make(map[string][]data.PrivateKey),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type memoryStore struct {
|
// MemoryStore implements a mock RemoteStore entirely in memory.
|
||||||
meta map[string][]byte
|
// For testing purposes only.
|
||||||
files map[string][]byte
|
type MemoryStore struct {
|
||||||
keys map[string][]data.PrivateKey
|
meta map[string][]byte
|
||||||
|
consistent map[string][]byte
|
||||||
|
keys map[string][]data.PrivateKey
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *memoryStore) GetMeta(name string, size int64) ([]byte, error) {
|
// GetMeta returns up to size bytes of data references by name.
|
||||||
|
// If size is -1, this corresponds to "infinite," but we cut off at 100MB
|
||||||
|
// as we will always know the size for everything but a timestamp and
|
||||||
|
// sometimes a root, neither of which should be exceptionally large
|
||||||
|
func (m *MemoryStore) GetMeta(name string, size int64) ([]byte, error) {
|
||||||
d, ok := m.meta[name]
|
d, ok := m.meta[name]
|
||||||
|
if ok {
|
||||||
|
if size == -1 {
|
||||||
|
size = notary.MaxDownloadSize
|
||||||
|
}
|
||||||
|
if int64(len(d)) < size {
|
||||||
|
return d, nil
|
||||||
|
}
|
||||||
|
return d[:size], nil
|
||||||
|
}
|
||||||
|
d, ok = m.consistent[name]
|
||||||
if ok {
|
if ok {
|
||||||
if int64(len(d)) < size {
|
if int64(len(d)) < size {
|
||||||
return d, nil
|
return d, nil
|
||||||
|
@ -42,12 +63,19 @@ func (m *memoryStore) GetMeta(name string, size int64) ([]byte, error) {
|
||||||
return nil, ErrMetaNotFound{Resource: name}
|
return nil, ErrMetaNotFound{Resource: name}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *memoryStore) SetMeta(name string, meta []byte) error {
|
// SetMeta sets the metadata value for the given name
|
||||||
|
func (m *MemoryStore) SetMeta(name string, meta []byte) error {
|
||||||
m.meta[name] = meta
|
m.meta[name] = meta
|
||||||
|
|
||||||
|
checksum := sha256.Sum256(meta)
|
||||||
|
path := utils.ConsistentName(name, checksum[:])
|
||||||
|
m.consistent[path] = meta
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *memoryStore) SetMultiMeta(metas map[string][]byte) error {
|
// SetMultiMeta sets multiple pieces of metadata for multiple names
|
||||||
|
// in a single operation.
|
||||||
|
func (m *MemoryStore) SetMultiMeta(metas map[string][]byte) error {
|
||||||
for role, blob := range metas {
|
for role, blob := range metas {
|
||||||
m.SetMeta(role, blob)
|
m.SetMeta(role, blob)
|
||||||
}
|
}
|
||||||
|
@ -56,57 +84,23 @@ func (m *memoryStore) SetMultiMeta(metas map[string][]byte) error {
|
||||||
|
|
||||||
// RemoveMeta removes the metadata for a single role - if the metadata doesn't
|
// RemoveMeta removes the metadata for a single role - if the metadata doesn't
|
||||||
// exist, no error is returned
|
// exist, no error is returned
|
||||||
func (m *memoryStore) RemoveMeta(name string) error {
|
func (m *MemoryStore) RemoveMeta(name string) error {
|
||||||
delete(m.meta, name)
|
if meta, ok := m.meta[name]; ok {
|
||||||
return nil
|
checksum := sha256.Sum256(meta)
|
||||||
}
|
path := utils.ConsistentName(name, checksum[:])
|
||||||
|
delete(m.meta, name)
|
||||||
func (m *memoryStore) GetTarget(path string) (io.ReadCloser, error) {
|
delete(m.consistent, path)
|
||||||
return &utils.NoopCloser{Reader: bytes.NewReader(m.files[path])}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *memoryStore) WalkStagedTargets(paths []string, targetsFn targetsWalkFunc) error {
|
|
||||||
if len(paths) == 0 {
|
|
||||||
for path, dat := range m.files {
|
|
||||||
meta, err := data.NewFileMeta(bytes.NewReader(dat), "sha256")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = targetsFn(path, meta); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, path := range paths {
|
|
||||||
dat, ok := m.files[path]
|
|
||||||
if !ok {
|
|
||||||
return ErrMetaNotFound{Resource: path}
|
|
||||||
}
|
|
||||||
meta, err := data.NewFileMeta(bytes.NewReader(dat), "sha256")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = targetsFn(path, meta); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *memoryStore) Commit(map[string][]byte, bool, map[string]data.Hashes) error {
|
// GetKey returns the public key for the given role
|
||||||
return nil
|
func (m *MemoryStore) GetKey(role string) ([]byte, error) {
|
||||||
|
return nil, fmt.Errorf("GetKey is not implemented for the MemoryStore")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *memoryStore) GetKey(role string) ([]byte, error) {
|
// RemoveAll clears the existing memory store by setting this store as new empty one
|
||||||
return nil, fmt.Errorf("GetKey is not implemented for the memoryStore")
|
func (m *MemoryStore) RemoveAll() error {
|
||||||
}
|
*m = *NewMemoryStore(nil)
|
||||||
|
|
||||||
// Clear this existing memory store by setting this store as new empty one
|
|
||||||
func (m *memoryStore) RemoveAll() error {
|
|
||||||
m.meta = make(map[string][]byte)
|
|
||||||
m.files = make(map[string][]byte)
|
|
||||||
m.keys = make(map[string][]data.PrivateKey)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
651
vendor/src/github.com/docker/notary/tuf/tuf.go
vendored
651
vendor/src/github.com/docker/notary/tuf/tuf.go
vendored
|
@ -3,8 +3,6 @@ package tuf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
|
@ -12,8 +10,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/notary"
|
||||||
"github.com/docker/notary/tuf/data"
|
"github.com/docker/notary/tuf/data"
|
||||||
"github.com/docker/notary/tuf/keys"
|
|
||||||
"github.com/docker/notary/tuf/signed"
|
"github.com/docker/notary/tuf/signed"
|
||||||
"github.com/docker/notary/tuf/utils"
|
"github.com/docker/notary/tuf/utils"
|
||||||
)
|
)
|
||||||
|
@ -40,15 +38,19 @@ func (e ErrLocalRootExpired) Error() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrNotLoaded - attempted to access data that has not been loaded into
|
// ErrNotLoaded - attempted to access data that has not been loaded into
|
||||||
// the repo
|
// the repo. This means specifically that the relevant JSON file has not
|
||||||
|
// been loaded.
|
||||||
type ErrNotLoaded struct {
|
type ErrNotLoaded struct {
|
||||||
role string
|
Role string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (err ErrNotLoaded) Error() string {
|
func (err ErrNotLoaded) Error() string {
|
||||||
return fmt.Sprintf("%s role has not been loaded", err.role)
|
return fmt.Sprintf("%s role has not been loaded", err.Role)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StopWalk - used by visitor functions to signal WalkTargets to stop walking
|
||||||
|
type StopWalk struct{}
|
||||||
|
|
||||||
// Repo is an in memory representation of the TUF Repo.
|
// Repo is an in memory representation of the TUF Repo.
|
||||||
// It operates at the data.Signed level, accepting and producing
|
// It operates at the data.Signed level, accepting and producing
|
||||||
// data.Signed objects. Users of a Repo are responsible for
|
// data.Signed objects. Users of a Repo are responsible for
|
||||||
|
@ -59,16 +61,15 @@ type Repo struct {
|
||||||
Targets map[string]*data.SignedTargets
|
Targets map[string]*data.SignedTargets
|
||||||
Snapshot *data.SignedSnapshot
|
Snapshot *data.SignedSnapshot
|
||||||
Timestamp *data.SignedTimestamp
|
Timestamp *data.SignedTimestamp
|
||||||
keysDB *keys.KeyDB
|
|
||||||
cryptoService signed.CryptoService
|
cryptoService signed.CryptoService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRepo initializes a Repo instance with a keysDB and a signer.
|
// NewRepo initializes a Repo instance with a CryptoService.
|
||||||
// If the Repo will only be used for reading, the signer should be nil.
|
// If the Repo will only be used for reading, the CryptoService
|
||||||
func NewRepo(keysDB *keys.KeyDB, cryptoService signed.CryptoService) *Repo {
|
// can be nil.
|
||||||
|
func NewRepo(cryptoService signed.CryptoService) *Repo {
|
||||||
repo := &Repo{
|
repo := &Repo{
|
||||||
Targets: make(map[string]*data.SignedTargets),
|
Targets: make(map[string]*data.SignedTargets),
|
||||||
keysDB: keysDB,
|
|
||||||
cryptoService: cryptoService,
|
cryptoService: cryptoService,
|
||||||
}
|
}
|
||||||
return repo
|
return repo
|
||||||
|
@ -77,27 +78,15 @@ func NewRepo(keysDB *keys.KeyDB, cryptoService signed.CryptoService) *Repo {
|
||||||
// AddBaseKeys is used to add keys to the role in root.json
|
// AddBaseKeys is used to add keys to the role in root.json
|
||||||
func (tr *Repo) AddBaseKeys(role string, keys ...data.PublicKey) error {
|
func (tr *Repo) AddBaseKeys(role string, keys ...data.PublicKey) error {
|
||||||
if tr.Root == nil {
|
if tr.Root == nil {
|
||||||
return ErrNotLoaded{role: "root"}
|
return ErrNotLoaded{Role: data.CanonicalRootRole}
|
||||||
}
|
}
|
||||||
ids := []string{}
|
ids := []string{}
|
||||||
for _, k := range keys {
|
for _, k := range keys {
|
||||||
// Store only the public portion
|
// Store only the public portion
|
||||||
tr.Root.Signed.Keys[k.ID()] = k
|
tr.Root.Signed.Keys[k.ID()] = k
|
||||||
tr.keysDB.AddKey(k)
|
|
||||||
tr.Root.Signed.Roles[role].KeyIDs = append(tr.Root.Signed.Roles[role].KeyIDs, k.ID())
|
tr.Root.Signed.Roles[role].KeyIDs = append(tr.Root.Signed.Roles[role].KeyIDs, k.ID())
|
||||||
ids = append(ids, k.ID())
|
ids = append(ids, k.ID())
|
||||||
}
|
}
|
||||||
r, err := data.NewRole(
|
|
||||||
role,
|
|
||||||
tr.Root.Signed.Roles[role].Threshold,
|
|
||||||
ids,
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
tr.keysDB.AddRole(r)
|
|
||||||
tr.Root.Dirty = true
|
tr.Root.Dirty = true
|
||||||
|
|
||||||
// also, whichever role was switched out needs to be re-signed
|
// 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 {
|
||||||
|
|
||||||
// ReplaceBaseKeys is used to replace all keys for the given role with the new keys
|
// ReplaceBaseKeys is used to replace all keys for the given role with the new keys
|
||||||
func (tr *Repo) ReplaceBaseKeys(role string, keys ...data.PublicKey) error {
|
func (tr *Repo) ReplaceBaseKeys(role string, keys ...data.PublicKey) error {
|
||||||
r := tr.keysDB.GetRole(role)
|
r, err := tr.GetBaseRole(role)
|
||||||
err := tr.RemoveBaseKeys(role, r.KeyIDs...)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = tr.RemoveBaseKeys(role, r.ListKeyIDs()...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -132,7 +124,7 @@ func (tr *Repo) ReplaceBaseKeys(role string, keys ...data.PublicKey) error {
|
||||||
// RemoveBaseKeys is used to remove keys from the roles in root.json
|
// RemoveBaseKeys is used to remove keys from the roles in root.json
|
||||||
func (tr *Repo) RemoveBaseKeys(role string, keyIDs ...string) error {
|
func (tr *Repo) RemoveBaseKeys(role string, keyIDs ...string) error {
|
||||||
if tr.Root == nil {
|
if tr.Root == nil {
|
||||||
return ErrNotLoaded{role: "root"}
|
return ErrNotLoaded{Role: data.CanonicalRootRole}
|
||||||
}
|
}
|
||||||
var keep []string
|
var keep []string
|
||||||
toDelete := make(map[string]struct{})
|
toDelete := make(map[string]struct{})
|
||||||
|
@ -173,117 +165,253 @@ func (tr *Repo) RemoveBaseKeys(role string, keyIDs ...string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetBaseRole gets a base role from this repo's metadata
|
||||||
|
func (tr *Repo) GetBaseRole(name string) (data.BaseRole, error) {
|
||||||
|
if !data.ValidRole(name) {
|
||||||
|
return data.BaseRole{}, data.ErrInvalidRole{Role: name, Reason: "invalid base role name"}
|
||||||
|
}
|
||||||
|
if tr.Root == nil {
|
||||||
|
return data.BaseRole{}, ErrNotLoaded{data.CanonicalRootRole}
|
||||||
|
}
|
||||||
|
// Find the role data public keys for the base role from TUF metadata
|
||||||
|
baseRole, err := tr.Root.BuildBaseRole(name)
|
||||||
|
if err != nil {
|
||||||
|
return data.BaseRole{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseRole, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDelegationRole gets a delegation role from this repo's metadata, walking from the targets role down to the delegation itself
|
||||||
|
func (tr *Repo) GetDelegationRole(name string) (data.DelegationRole, error) {
|
||||||
|
if !data.IsDelegation(name) {
|
||||||
|
return data.DelegationRole{}, data.ErrInvalidRole{Role: name, Reason: "invalid delegation name"}
|
||||||
|
}
|
||||||
|
if tr.Root == nil {
|
||||||
|
return data.DelegationRole{}, ErrNotLoaded{data.CanonicalRootRole}
|
||||||
|
}
|
||||||
|
_, ok := tr.Root.Signed.Roles[data.CanonicalTargetsRole]
|
||||||
|
if !ok {
|
||||||
|
return data.DelegationRole{}, ErrNotLoaded{data.CanonicalTargetsRole}
|
||||||
|
}
|
||||||
|
// Traverse target metadata, down to delegation itself
|
||||||
|
// Get all public keys for the base role from TUF metadata
|
||||||
|
_, ok = tr.Targets[data.CanonicalTargetsRole]
|
||||||
|
if !ok {
|
||||||
|
return data.DelegationRole{}, ErrNotLoaded{data.CanonicalTargetsRole}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start with top level roles in targets. Walk the chain of ancestors
|
||||||
|
// until finding the desired role, or we run out of targets files to search.
|
||||||
|
var foundRole *data.DelegationRole
|
||||||
|
buildDelegationRoleVisitor := func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} {
|
||||||
|
// Try to find the delegation and build a DelegationRole structure
|
||||||
|
for _, role := range tgt.Signed.Delegations.Roles {
|
||||||
|
if role.Name == name {
|
||||||
|
delgRole, err := tgt.BuildDelegationRole(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
foundRole = &delgRole
|
||||||
|
return StopWalk{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk to the parent of this delegation, since that is where its role metadata exists
|
||||||
|
err := tr.WalkTargets("", path.Dir(name), buildDelegationRoleVisitor)
|
||||||
|
if err != nil {
|
||||||
|
return data.DelegationRole{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We never found the delegation. In the context of this repo it is considered
|
||||||
|
// invalid. N.B. it may be that it existed at one point but an ancestor has since
|
||||||
|
// been modified/removed.
|
||||||
|
if foundRole == nil {
|
||||||
|
return data.DelegationRole{}, data.ErrInvalidRole{Role: name, Reason: "delegation does not exist"}
|
||||||
|
}
|
||||||
|
|
||||||
|
return *foundRole, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetAllLoadedRoles returns a list of all role entries loaded in this TUF repo, could be empty
|
// GetAllLoadedRoles returns a list of all role entries loaded in this TUF repo, could be empty
|
||||||
func (tr *Repo) GetAllLoadedRoles() []*data.Role {
|
func (tr *Repo) GetAllLoadedRoles() []*data.Role {
|
||||||
return tr.keysDB.GetAllRoles()
|
var res []*data.Role
|
||||||
|
if tr.Root == nil {
|
||||||
|
// if root isn't loaded, we should consider we have no loaded roles because we can't
|
||||||
|
// trust any other state that might be present
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
for name, rr := range tr.Root.Signed.Roles {
|
||||||
|
res = append(res, &data.Role{
|
||||||
|
RootRole: *rr,
|
||||||
|
Name: name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, delegate := range tr.Targets {
|
||||||
|
for _, r := range delegate.Signed.Delegations.Roles {
|
||||||
|
res = append(res, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDelegation finds the role entry representing the provided
|
// Walk to parent, and either create or update this delegation. We can only create a new delegation if we're given keys
|
||||||
// role name or ErrInvalidRole
|
// Ensure all updates are valid, by checking against parent ancestor paths and ensuring the keys meet the role threshold.
|
||||||
func (tr *Repo) GetDelegation(role string) (*data.Role, error) {
|
func delegationUpdateVisitor(roleName string, addKeys data.KeyList, removeKeys, addPaths, removePaths []string, clearAllPaths bool, newThreshold int) walkVisitorFunc {
|
||||||
r := data.Role{Name: role}
|
return func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} {
|
||||||
if !r.IsDelegation() {
|
var err error
|
||||||
return nil, data.ErrInvalidRole{Role: role, Reason: "not a valid delegated role"}
|
// Validate the changes underneath this restricted validRole for adding paths, reject invalid path additions
|
||||||
|
if len(addPaths) != len(data.RestrictDelegationPathPrefixes(validRole.Paths, addPaths)) {
|
||||||
|
return data.ErrInvalidRole{Role: roleName, Reason: "invalid paths to add to role"}
|
||||||
|
}
|
||||||
|
// Try to find the delegation and amend it using our changelist
|
||||||
|
var delgRole *data.Role
|
||||||
|
for _, role := range tgt.Signed.Delegations.Roles {
|
||||||
|
if role.Name == roleName {
|
||||||
|
// Make a copy and operate on this role until we validate the changes
|
||||||
|
keyIDCopy := make([]string, len(role.KeyIDs))
|
||||||
|
copy(keyIDCopy, role.KeyIDs)
|
||||||
|
pathsCopy := make([]string, len(role.Paths))
|
||||||
|
copy(pathsCopy, role.Paths)
|
||||||
|
delgRole = &data.Role{
|
||||||
|
RootRole: data.RootRole{
|
||||||
|
KeyIDs: keyIDCopy,
|
||||||
|
Threshold: role.Threshold,
|
||||||
|
},
|
||||||
|
Name: role.Name,
|
||||||
|
Paths: pathsCopy,
|
||||||
|
}
|
||||||
|
delgRole.RemovePaths(removePaths)
|
||||||
|
if clearAllPaths {
|
||||||
|
delgRole.Paths = []string{}
|
||||||
|
}
|
||||||
|
delgRole.AddPaths(addPaths)
|
||||||
|
delgRole.RemoveKeys(removeKeys)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We didn't find the role earlier, so create it only if we have keys to add
|
||||||
|
if delgRole == nil {
|
||||||
|
if len(addKeys) > 0 {
|
||||||
|
delgRole, err = data.NewRole(roleName, newThreshold, addKeys.IDs(), addPaths)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If we can't find the role and didn't specify keys to add, this is an error
|
||||||
|
return data.ErrInvalidRole{Role: roleName, Reason: "cannot create new delegation without keys"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add the key IDs to the role and the keys themselves to the parent
|
||||||
|
for _, k := range addKeys {
|
||||||
|
if !utils.StrSliceContains(delgRole.KeyIDs, k.ID()) {
|
||||||
|
delgRole.KeyIDs = append(delgRole.KeyIDs, k.ID())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Make sure we have a valid role still
|
||||||
|
if len(delgRole.KeyIDs) < delgRole.Threshold {
|
||||||
|
return data.ErrInvalidRole{Role: roleName, Reason: "insufficient keys to meet threshold"}
|
||||||
|
}
|
||||||
|
// NOTE: this closure CANNOT error after this point, as we've committed to editing the SignedTargets metadata in the repo object.
|
||||||
|
// Any errors related to updating this delegation must occur before this point.
|
||||||
|
// If all of our changes were valid, we should edit the actual SignedTargets to match our copy
|
||||||
|
for _, k := range addKeys {
|
||||||
|
tgt.Signed.Delegations.Keys[k.ID()] = k
|
||||||
|
}
|
||||||
|
foundAt := utils.FindRoleIndex(tgt.Signed.Delegations.Roles, delgRole.Name)
|
||||||
|
if foundAt < 0 {
|
||||||
|
tgt.Signed.Delegations.Roles = append(tgt.Signed.Delegations.Roles, delgRole)
|
||||||
|
} else {
|
||||||
|
tgt.Signed.Delegations.Roles[foundAt] = delgRole
|
||||||
|
}
|
||||||
|
tgt.Dirty = true
|
||||||
|
utils.RemoveUnusedKeys(tgt)
|
||||||
|
return StopWalk{}
|
||||||
}
|
}
|
||||||
|
|
||||||
parent := path.Dir(role)
|
|
||||||
|
|
||||||
// check the parent role
|
|
||||||
if parentRole := tr.keysDB.GetRole(parent); parentRole == nil {
|
|
||||||
return nil, data.ErrInvalidRole{Role: role, Reason: "parent role not found"}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check the parent role's metadata
|
|
||||||
p, ok := tr.Targets[parent]
|
|
||||||
if !ok { // the parent targetfile may not exist yet, so it can't be in the list
|
|
||||||
return nil, data.ErrNoSuchRole{Role: role}
|
|
||||||
}
|
|
||||||
|
|
||||||
foundAt := utils.FindRoleIndex(p.Signed.Delegations.Roles, role)
|
|
||||||
if foundAt < 0 {
|
|
||||||
return nil, data.ErrNoSuchRole{Role: role}
|
|
||||||
}
|
|
||||||
return p.Signed.Delegations.Roles[foundAt], nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateDelegations updates the appropriate delegations, either adding
|
// UpdateDelegationKeys updates the appropriate delegations, either adding
|
||||||
// a new delegation or updating an existing one. If keys are
|
// a new delegation or updating an existing one. If keys are
|
||||||
// provided, the IDs will be added to the role (if they do not exist
|
// provided, the IDs will be added to the role (if they do not exist
|
||||||
// there already), and the keys will be added to the targets file.
|
// there already), and the keys will be added to the targets file.
|
||||||
func (tr *Repo) UpdateDelegations(role *data.Role, keys []data.PublicKey) error {
|
func (tr *Repo) UpdateDelegationKeys(roleName string, addKeys data.KeyList, removeKeys []string, newThreshold int) error {
|
||||||
if !role.IsDelegation() || !role.IsValid() {
|
if !data.IsDelegation(roleName) {
|
||||||
return data.ErrInvalidRole{Role: role.Name, Reason: "not a valid delegated role"}
|
return data.ErrInvalidRole{Role: roleName, Reason: "not a valid delegated role"}
|
||||||
}
|
}
|
||||||
parent := path.Dir(role.Name)
|
parent := path.Dir(roleName)
|
||||||
|
|
||||||
if err := tr.VerifyCanSign(parent); err != nil {
|
if err := tr.VerifyCanSign(parent); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// check the parent role's metadata
|
// check the parent role's metadata
|
||||||
p, ok := tr.Targets[parent]
|
_, ok := tr.Targets[parent]
|
||||||
if !ok { // the parent targetfile may not exist yet - if not, then create it
|
if !ok { // the parent targetfile may not exist yet - if not, then create it
|
||||||
var err error
|
var err error
|
||||||
p, err = tr.InitTargets(parent)
|
_, err = tr.InitTargets(parent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, k := range keys {
|
// Walk to the parent of this delegation, since that is where its role metadata exists
|
||||||
if !utils.StrSliceContains(role.KeyIDs, k.ID()) {
|
// We do not have to verify that the walker reached its desired role in this scenario
|
||||||
role.KeyIDs = append(role.KeyIDs, k.ID())
|
// since we've already done another walk to the parent role in VerifyCanSign, and potentially made a targets file
|
||||||
}
|
err := tr.WalkTargets("", parent, delegationUpdateVisitor(roleName, addKeys, removeKeys, []string{}, []string{}, false, newThreshold))
|
||||||
p.Signed.Delegations.Keys[k.ID()] = k
|
if err != nil {
|
||||||
tr.keysDB.AddKey(k)
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateDelegationPaths updates the appropriate delegation's paths.
|
||||||
|
// It is not allowed to create a new delegation.
|
||||||
|
func (tr *Repo) UpdateDelegationPaths(roleName string, addPaths, removePaths []string, clearPaths bool) error {
|
||||||
|
if !data.IsDelegation(roleName) {
|
||||||
|
return data.ErrInvalidRole{Role: roleName, Reason: "not a valid delegated role"}
|
||||||
|
}
|
||||||
|
parent := path.Dir(roleName)
|
||||||
|
|
||||||
|
if err := tr.VerifyCanSign(parent); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the role has fewer keys than the threshold, it
|
// check the parent role's metadata
|
||||||
// will never be able to create a valid targets file
|
_, ok := tr.Targets[parent]
|
||||||
// and should be considered invalid.
|
if !ok { // the parent targetfile may not exist yet
|
||||||
if len(role.KeyIDs) < role.Threshold {
|
// if not, this is an error because a delegation must exist to edit only paths
|
||||||
return data.ErrInvalidRole{Role: role.Name, Reason: "insufficient keys to meet threshold"}
|
return data.ErrInvalidRole{Role: roleName, Reason: "no valid delegated role exists"}
|
||||||
}
|
}
|
||||||
|
|
||||||
foundAt := utils.FindRoleIndex(p.Signed.Delegations.Roles, role.Name)
|
// Walk to the parent of this delegation, since that is where its role metadata exists
|
||||||
|
// We do not have to verify that the walker reached its desired role in this scenario
|
||||||
if foundAt >= 0 {
|
// since we've already done another walk to the parent role in VerifyCanSign
|
||||||
p.Signed.Delegations.Roles[foundAt] = role
|
err := tr.WalkTargets("", parent, delegationUpdateVisitor(roleName, data.KeyList{}, []string{}, addPaths, removePaths, clearPaths, notary.MinThreshold))
|
||||||
} else {
|
if err != nil {
|
||||||
p.Signed.Delegations.Roles = append(p.Signed.Delegations.Roles, role)
|
return err
|
||||||
}
|
}
|
||||||
// We've made a change to parent. Set it to dirty
|
|
||||||
p.Dirty = true
|
|
||||||
|
|
||||||
// We don't actually want to create the new delegation metadata yet.
|
|
||||||
// When we add a delegation, it may only be signable by a key we don't have
|
|
||||||
// (hence we are delegating signing).
|
|
||||||
|
|
||||||
tr.keysDB.AddRole(role)
|
|
||||||
utils.RemoveUnusedKeys(p)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteDelegation removes a delegated targets role from its parent
|
// DeleteDelegation removes a delegated targets role from its parent
|
||||||
// targets object. It also deletes the delegation from the snapshot.
|
// targets object. It also deletes the delegation from the snapshot.
|
||||||
// DeleteDelegation will only make use of the role Name field.
|
// DeleteDelegation will only make use of the role Name field.
|
||||||
func (tr *Repo) DeleteDelegation(role data.Role) error {
|
func (tr *Repo) DeleteDelegation(roleName string) error {
|
||||||
if !role.IsDelegation() {
|
if !data.IsDelegation(roleName) {
|
||||||
return data.ErrInvalidRole{Role: role.Name, Reason: "not a valid delegated role"}
|
return data.ErrInvalidRole{Role: roleName, Reason: "not a valid delegated role"}
|
||||||
}
|
}
|
||||||
// the role variable must not be used past this assignment for safety
|
|
||||||
name := role.Name
|
|
||||||
|
|
||||||
parent := path.Dir(name)
|
parent := path.Dir(roleName)
|
||||||
if err := tr.VerifyCanSign(parent); err != nil {
|
if err := tr.VerifyCanSign(parent); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// delete delegated data from Targets map and Snapshot - if they don't
|
// delete delegated data from Targets map and Snapshot - if they don't
|
||||||
// exist, these are no-op
|
// exist, these are no-op
|
||||||
delete(tr.Targets, name)
|
delete(tr.Targets, roleName)
|
||||||
tr.Snapshot.DeleteMeta(name)
|
tr.Snapshot.DeleteMeta(roleName)
|
||||||
|
|
||||||
p, ok := tr.Targets[parent]
|
p, ok := tr.Targets[parent]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -292,7 +420,7 @@ func (tr *Repo) DeleteDelegation(role data.Role) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
foundAt := utils.FindRoleIndex(p.Signed.Delegations.Roles, name)
|
foundAt := utils.FindRoleIndex(p.Signed.Delegations.Roles, roleName)
|
||||||
|
|
||||||
if foundAt >= 0 {
|
if foundAt >= 0 {
|
||||||
var roles []*data.Role
|
var roles []*data.Role
|
||||||
|
@ -311,53 +439,32 @@ func (tr *Repo) DeleteDelegation(role data.Role) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitRepo creates the base files for a repo. It inspects data.BaseRoles and
|
// InitRoot initializes an empty root file with the 4 core roles passed to the
|
||||||
// data.ValidTypes to determine what the role names and filename should be. It
|
// method, and the consistent flag.
|
||||||
// also relies on the keysDB having already been populated with the keys and
|
func (tr *Repo) InitRoot(root, timestamp, snapshot, targets data.BaseRole, consistent bool) error {
|
||||||
// roles.
|
|
||||||
func (tr *Repo) InitRepo(consistent bool) error {
|
|
||||||
if err := tr.InitRoot(consistent); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := tr.InitTargets(data.CanonicalTargetsRole); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := tr.InitSnapshot(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return tr.InitTimestamp()
|
|
||||||
}
|
|
||||||
|
|
||||||
// InitRoot initializes an empty root file with the 4 core roles based
|
|
||||||
// on the current content of th ekey db
|
|
||||||
func (tr *Repo) InitRoot(consistent bool) error {
|
|
||||||
rootRoles := make(map[string]*data.RootRole)
|
rootRoles := make(map[string]*data.RootRole)
|
||||||
rootKeys := make(map[string]data.PublicKey)
|
rootKeys := make(map[string]data.PublicKey)
|
||||||
for _, r := range data.BaseRoles {
|
|
||||||
role := tr.keysDB.GetRole(r)
|
for _, r := range []data.BaseRole{root, timestamp, snapshot, targets} {
|
||||||
if role == nil {
|
rootRoles[r.Name] = &data.RootRole{
|
||||||
return data.ErrInvalidRole{Role: data.CanonicalRootRole, Reason: "root role not initialized in key database"}
|
Threshold: r.Threshold,
|
||||||
|
KeyIDs: r.ListKeyIDs(),
|
||||||
}
|
}
|
||||||
rootRoles[r] = &role.RootRole
|
for kid, k := range r.Keys {
|
||||||
for _, kid := range role.KeyIDs {
|
rootKeys[kid] = k
|
||||||
// don't need to check if GetKey returns nil, Key presence was
|
|
||||||
// checked by KeyDB when role was added.
|
|
||||||
key := tr.keysDB.GetKey(kid)
|
|
||||||
rootKeys[kid] = key
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
root, err := data.NewRoot(rootKeys, rootRoles, consistent)
|
r, err := data.NewRoot(rootKeys, rootRoles, consistent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tr.Root = root
|
tr.Root = r
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitTargets initializes an empty targets, and returns the new empty target
|
// InitTargets initializes an empty targets, and returns the new empty target
|
||||||
func (tr *Repo) InitTargets(role string) (*data.SignedTargets, error) {
|
func (tr *Repo) InitTargets(role string) (*data.SignedTargets, error) {
|
||||||
r := data.Role{Name: role}
|
if !data.IsDelegation(role) && role != data.CanonicalTargetsRole {
|
||||||
if !r.IsDelegation() && role != data.CanonicalTargetsRole {
|
|
||||||
return nil, data.ErrInvalidRole{
|
return nil, data.ErrInvalidRole{
|
||||||
Role: role,
|
Role: role,
|
||||||
Reason: fmt.Sprintf("role is not a valid targets role name: %s", role),
|
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) {
|
||||||
// InitSnapshot initializes a snapshot based on the current root and targets
|
// InitSnapshot initializes a snapshot based on the current root and targets
|
||||||
func (tr *Repo) InitSnapshot() error {
|
func (tr *Repo) InitSnapshot() error {
|
||||||
if tr.Root == nil {
|
if tr.Root == nil {
|
||||||
return ErrNotLoaded{role: "root"}
|
return ErrNotLoaded{Role: data.CanonicalRootRole}
|
||||||
}
|
}
|
||||||
root, err := tr.Root.ToSigned()
|
root, err := tr.Root.ToSigned()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -379,7 +486,7 @@ func (tr *Repo) InitSnapshot() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := tr.Targets[data.CanonicalTargetsRole]; !ok {
|
if _, ok := tr.Targets[data.CanonicalTargetsRole]; !ok {
|
||||||
return ErrNotLoaded{role: "targets"}
|
return ErrNotLoaded{Role: data.CanonicalTargetsRole}
|
||||||
}
|
}
|
||||||
targets, err := tr.Targets[data.CanonicalTargetsRole].ToSigned()
|
targets, err := tr.Targets[data.CanonicalTargetsRole].ToSigned()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -408,31 +515,8 @@ func (tr *Repo) InitTimestamp() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetRoot parses the Signed object into a SignedRoot object, sets
|
// SetRoot sets the Repo.Root field to the SignedRoot object.
|
||||||
// the keys and roles in the KeyDB, and sets the Repo.Root field
|
|
||||||
// to the SignedRoot object.
|
|
||||||
func (tr *Repo) SetRoot(s *data.SignedRoot) error {
|
func (tr *Repo) SetRoot(s *data.SignedRoot) error {
|
||||||
for _, key := range s.Signed.Keys {
|
|
||||||
logrus.Debug("Adding key ", key.ID())
|
|
||||||
tr.keysDB.AddKey(key)
|
|
||||||
}
|
|
||||||
for roleName, role := range s.Signed.Roles {
|
|
||||||
logrus.Debugf("Adding role %s with keys %s", roleName, strings.Join(role.KeyIDs, ","))
|
|
||||||
baseRole, err := data.NewRole(
|
|
||||||
roleName,
|
|
||||||
role.Threshold,
|
|
||||||
role.KeyIDs,
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = tr.keysDB.AddRole(baseRole)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tr.Root = s
|
tr.Root = s
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -451,16 +535,9 @@ func (tr *Repo) SetSnapshot(s *data.SignedSnapshot) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTargets parses the Signed object into a SignedTargets object,
|
// SetTargets sets the SignedTargets object agaist the role in the
|
||||||
// reads the delegated roles and keys into the KeyDB, and sets the
|
// Repo.Targets map.
|
||||||
// SignedTargets object agaist the role in the Repo.Targets map.
|
|
||||||
func (tr *Repo) SetTargets(role string, s *data.SignedTargets) error {
|
func (tr *Repo) SetTargets(role string, s *data.SignedTargets) error {
|
||||||
for _, k := range s.Signed.Delegations.Keys {
|
|
||||||
tr.keysDB.AddKey(k)
|
|
||||||
}
|
|
||||||
for _, r := range s.Signed.Delegations.Roles {
|
|
||||||
tr.keysDB.AddRole(r)
|
|
||||||
}
|
|
||||||
tr.Targets[role] = s
|
tr.Targets[role] = s
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -479,15 +556,11 @@ func (tr Repo) TargetMeta(role, path string) *data.FileMeta {
|
||||||
|
|
||||||
// TargetDelegations returns a slice of Roles that are valid publishers
|
// TargetDelegations returns a slice of Roles that are valid publishers
|
||||||
// for the target path provided.
|
// for the target path provided.
|
||||||
func (tr Repo) TargetDelegations(role, path, pathHex string) []*data.Role {
|
func (tr Repo) TargetDelegations(role, path string) []*data.Role {
|
||||||
if pathHex == "" {
|
|
||||||
pathDigest := sha256.Sum256([]byte(path))
|
|
||||||
pathHex = hex.EncodeToString(pathDigest[:])
|
|
||||||
}
|
|
||||||
var roles []*data.Role
|
var roles []*data.Role
|
||||||
if t, ok := tr.Targets[role]; ok {
|
if t, ok := tr.Targets[role]; ok {
|
||||||
for _, r := range t.Signed.Delegations.Roles {
|
for _, r := range t.Signed.Delegations.Roles {
|
||||||
if r.CheckPrefixes(pathHex) || r.CheckPaths(path) {
|
if r.CheckPaths(path) {
|
||||||
roles = append(roles, r)
|
roles = append(roles, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -495,50 +568,34 @@ func (tr Repo) TargetDelegations(role, path, pathHex string) []*data.Role {
|
||||||
return roles
|
return roles
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindTarget attempts to find the target represented by the given
|
|
||||||
// path by starting at the top targets file and traversing
|
|
||||||
// appropriate delegations until the first entry is found or it
|
|
||||||
// runs out of locations to search.
|
|
||||||
// N.B. Multiple entries may exist in different delegated roles
|
|
||||||
// for the same target. Only the first one encountered is returned.
|
|
||||||
func (tr Repo) FindTarget(path string) *data.FileMeta {
|
|
||||||
pathDigest := sha256.Sum256([]byte(path))
|
|
||||||
pathHex := hex.EncodeToString(pathDigest[:])
|
|
||||||
|
|
||||||
var walkTargets func(role string) *data.FileMeta
|
|
||||||
walkTargets = func(role string) *data.FileMeta {
|
|
||||||
if m := tr.TargetMeta(role, path); m != nil {
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
// Depth first search of delegations based on order
|
|
||||||
// as presented in current targets file for role:
|
|
||||||
for _, r := range tr.TargetDelegations(role, path, pathHex) {
|
|
||||||
if m := walkTargets(r.Name); m != nil {
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return walkTargets("targets")
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyCanSign returns nil if the role exists and we have at least one
|
// VerifyCanSign returns nil if the role exists and we have at least one
|
||||||
// signing key for the role, false otherwise. This does not check that we have
|
// signing key for the role, false otherwise. This does not check that we have
|
||||||
// enough signing keys to meet the threshold, since we want to support the use
|
// enough signing keys to meet the threshold, since we want to support the use
|
||||||
// case of multiple signers for a role. It returns an error if the role doesn't
|
// case of multiple signers for a role. It returns an error if the role doesn't
|
||||||
// exist or if there are no signing keys.
|
// exist or if there are no signing keys.
|
||||||
func (tr *Repo) VerifyCanSign(roleName string) error {
|
func (tr *Repo) VerifyCanSign(roleName string) error {
|
||||||
role := tr.keysDB.GetRole(roleName)
|
var (
|
||||||
if role == nil {
|
role data.BaseRole
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
// we only need the BaseRole part of a delegation because we're just
|
||||||
|
// checking KeyIDs
|
||||||
|
if data.IsDelegation(roleName) {
|
||||||
|
r, err := tr.GetDelegationRole(roleName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
role = r.BaseRole
|
||||||
|
} else {
|
||||||
|
role, err = tr.GetBaseRole(roleName)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
return data.ErrInvalidRole{Role: roleName, Reason: "does not exist"}
|
return data.ErrInvalidRole{Role: roleName, Reason: "does not exist"}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, keyID := range role.KeyIDs {
|
for keyID, k := range role.Keys {
|
||||||
k := tr.keysDB.GetKey(keyID)
|
|
||||||
canonicalID, err := utils.CanonicalKeyID(k)
|
|
||||||
check := []string{keyID}
|
check := []string{keyID}
|
||||||
if err == nil {
|
if canonicalID, err := utils.CanonicalKeyID(k); err == nil {
|
||||||
check = append(check, canonicalID)
|
check = append(check, canonicalID)
|
||||||
}
|
}
|
||||||
for _, id := range check {
|
for _, id := range check {
|
||||||
|
@ -548,45 +605,123 @@ func (tr *Repo) VerifyCanSign(roleName string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return signed.ErrNoKeys{KeyIDs: role.KeyIDs}
|
return signed.ErrNoKeys{KeyIDs: role.ListKeyIDs()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// used for walking the targets/delegations tree, potentially modifying the underlying SignedTargets for the repo
|
||||||
|
type walkVisitorFunc func(*data.SignedTargets, data.DelegationRole) interface{}
|
||||||
|
|
||||||
|
// WalkTargets will apply the specified visitor function to iteratively walk the targets/delegation metadata tree,
|
||||||
|
// until receiving a StopWalk. The walk starts from the base "targets" role, and searches for the correct targetPath and/or rolePath
|
||||||
|
// to call the visitor function on. Any roles passed into skipRoles will be excluded from the walk, as well as roles in those subtrees
|
||||||
|
func (tr *Repo) WalkTargets(targetPath, rolePath string, visitTargets walkVisitorFunc, skipRoles ...string) error {
|
||||||
|
// Start with the base targets role, which implicitly has the "" targets path
|
||||||
|
targetsRole, err := tr.GetBaseRole(data.CanonicalTargetsRole)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Make the targets role have the empty path, when we treat it as a delegation role
|
||||||
|
roles := []data.DelegationRole{
|
||||||
|
{
|
||||||
|
BaseRole: targetsRole,
|
||||||
|
Paths: []string{""},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for len(roles) > 0 {
|
||||||
|
role := roles[0]
|
||||||
|
roles = roles[1:]
|
||||||
|
|
||||||
|
// Check the role metadata
|
||||||
|
signedTgt, ok := tr.Targets[role.Name]
|
||||||
|
if !ok {
|
||||||
|
// The role meta doesn't exist in the repo so continue onward
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're at a prefix of the desired role subtree, so add its delegation role children and continue walking
|
||||||
|
if strings.HasPrefix(rolePath, role.Name+"/") {
|
||||||
|
roles = append(roles, signedTgt.GetValidDelegations(role)...)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine whether to visit this role or not:
|
||||||
|
// If the paths validate against the specified targetPath and the rolePath is empty or is in the subtree
|
||||||
|
// Also check if we are choosing to skip visiting this role on this walk (see ListTargets and GetTargetByName priority)
|
||||||
|
if isValidPath(targetPath, role) && isAncestorRole(role.Name, rolePath) && !utils.StrSliceContains(skipRoles, role.Name) {
|
||||||
|
// If we had matching path or role name, visit this target and determine whether or not to keep walking
|
||||||
|
res := visitTargets(signedTgt, role)
|
||||||
|
switch typedRes := res.(type) {
|
||||||
|
case StopWalk:
|
||||||
|
// If the visitor function signalled a stop, return nil to finish the walk
|
||||||
|
return nil
|
||||||
|
case nil:
|
||||||
|
// If the visitor function signalled to continue, add this role's delegation to the walk
|
||||||
|
roles = append(roles, signedTgt.GetValidDelegations(role)...)
|
||||||
|
case error:
|
||||||
|
// Propagate any errors from the visitor
|
||||||
|
return typedRes
|
||||||
|
default:
|
||||||
|
// Return out with an error if we got a different result
|
||||||
|
return fmt.Errorf("unexpected return while walking: %v", res)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper function that returns whether the candidateChild role name is an ancestor or equal to the candidateAncestor role name
|
||||||
|
// Will return true if given an empty candidateAncestor role name
|
||||||
|
// The HasPrefix check is for determining whether the role name for candidateChild is a child (direct or further down the chain)
|
||||||
|
// of candidateAncestor, for ex: candidateAncestor targets/a and candidateChild targets/a/b/c
|
||||||
|
func isAncestorRole(candidateChild, candidateAncestor string) bool {
|
||||||
|
return candidateAncestor == "" || candidateAncestor == candidateChild || strings.HasPrefix(candidateChild, candidateAncestor+"/")
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper function that returns whether the delegation Role is valid against the given path
|
||||||
|
// Will return true if given an empty candidatePath
|
||||||
|
func isValidPath(candidatePath string, delgRole data.DelegationRole) bool {
|
||||||
|
return candidatePath == "" || delgRole.CheckPaths(candidatePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddTargets will attempt to add the given targets specifically to
|
// AddTargets will attempt to add the given targets specifically to
|
||||||
// the directed role. If the metadata for the role doesn't exist yet,
|
// the directed role. If the metadata for the role doesn't exist yet,
|
||||||
// AddTargets will create one.
|
// AddTargets will create one.
|
||||||
func (tr *Repo) AddTargets(role string, targets data.Files) (data.Files, error) {
|
func (tr *Repo) AddTargets(role string, targets data.Files) (data.Files, error) {
|
||||||
|
|
||||||
err := tr.VerifyCanSign(role)
|
err := tr.VerifyCanSign(role)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// check the role's metadata
|
// check existence of the role's metadata
|
||||||
t, ok := tr.Targets[role]
|
_, ok := tr.Targets[role]
|
||||||
if !ok { // the targetfile may not exist yet - if not, then create it
|
if !ok { // the targetfile may not exist yet - if not, then create it
|
||||||
var err error
|
var err error
|
||||||
t, err = tr.InitTargets(role)
|
_, err = tr.InitTargets(role)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifyCanSign already makes sure this is not nil
|
addedTargets := make(data.Files)
|
||||||
r := tr.keysDB.GetRole(role)
|
addTargetVisitor := func(targetPath string, targetMeta data.FileMeta) func(*data.SignedTargets, data.DelegationRole) interface{} {
|
||||||
|
return func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} {
|
||||||
invalid := make(data.Files)
|
// We've already validated the role's target path in our walk, so just modify the metadata
|
||||||
for path, target := range targets {
|
tgt.Signed.Targets[targetPath] = targetMeta
|
||||||
pathDigest := sha256.Sum256([]byte(path))
|
tgt.Dirty = true
|
||||||
pathHex := hex.EncodeToString(pathDigest[:])
|
// Also add to our new addedTargets map to keep track of every target we've added successfully
|
||||||
if role == data.CanonicalTargetsRole || (r.CheckPaths(path) || r.CheckPrefixes(pathHex)) {
|
addedTargets[targetPath] = targetMeta
|
||||||
t.Signed.Targets[path] = target
|
return StopWalk{}
|
||||||
} else {
|
|
||||||
invalid[path] = target
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.Dirty = true
|
|
||||||
if len(invalid) > 0 {
|
// Walk the role tree while validating the target paths, and add all of our targets
|
||||||
return invalid, fmt.Errorf("Could not add all targets")
|
for path, target := range targets {
|
||||||
|
tr.WalkTargets(path, role, addTargetVisitor(path, target))
|
||||||
|
}
|
||||||
|
if len(addedTargets) != len(targets) {
|
||||||
|
return nil, fmt.Errorf("Could not add all targets")
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
@ -597,13 +732,23 @@ func (tr *Repo) RemoveTargets(role string, targets ...string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removeTargetVisitor := func(targetPath string) func(*data.SignedTargets, data.DelegationRole) interface{} {
|
||||||
|
return func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} {
|
||||||
|
// We've already validated the role path in our walk, so just modify the metadata
|
||||||
|
// We don't check against the target path against the valid role paths because it's
|
||||||
|
// possible we got into an invalid state and are trying to fix it
|
||||||
|
delete(tgt.Signed.Targets, targetPath)
|
||||||
|
tgt.Dirty = true
|
||||||
|
return StopWalk{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// if the role exists but metadata does not yet, then our work is done
|
// if the role exists but metadata does not yet, then our work is done
|
||||||
t, ok := tr.Targets[role]
|
_, ok := tr.Targets[role]
|
||||||
if ok {
|
if ok {
|
||||||
for _, path := range targets {
|
for _, path := range targets {
|
||||||
delete(t.Signed.Targets, path)
|
tr.WalkTargets("", role, removeTargetVisitor(path))
|
||||||
}
|
}
|
||||||
t.Dirty = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -644,12 +789,15 @@ func (tr *Repo) SignRoot(expires time.Time) (*data.Signed, error) {
|
||||||
logrus.Debug("signing root...")
|
logrus.Debug("signing root...")
|
||||||
tr.Root.Signed.Expires = expires
|
tr.Root.Signed.Expires = expires
|
||||||
tr.Root.Signed.Version++
|
tr.Root.Signed.Version++
|
||||||
root := tr.keysDB.GetRole(data.CanonicalRootRole)
|
root, err := tr.GetBaseRole(data.CanonicalRootRole)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
signed, err := tr.Root.ToSigned()
|
signed, err := tr.Root.ToSigned()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
signed, err = tr.sign(signed, *root)
|
signed, err = tr.sign(signed, root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -673,8 +821,22 @@ func (tr *Repo) SignTargets(role string, expires time.Time) (*data.Signed, error
|
||||||
logrus.Debug("errored getting targets data.Signed object")
|
logrus.Debug("errored getting targets data.Signed object")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
targets := tr.keysDB.GetRole(role)
|
|
||||||
signed, err = tr.sign(signed, *targets)
|
var targets data.BaseRole
|
||||||
|
if role == data.CanonicalTargetsRole {
|
||||||
|
targets, err = tr.GetBaseRole(role)
|
||||||
|
} else {
|
||||||
|
tr, err := tr.GetDelegationRole(role)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
targets = tr.BaseRole
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
signed, err = tr.sign(signed, targets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Debug("errored signing ", role)
|
logrus.Debug("errored signing ", role)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -712,8 +874,11 @@ func (tr *Repo) SignSnapshot(expires time.Time) (*data.Signed, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
snapshot := tr.keysDB.GetRole(data.CanonicalSnapshotRole)
|
snapshot, err := tr.GetBaseRole(data.CanonicalSnapshotRole)
|
||||||
signed, err = tr.sign(signed, *snapshot)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
signed, err = tr.sign(signed, snapshot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -738,8 +903,11 @@ func (tr *Repo) SignTimestamp(expires time.Time) (*data.Signed, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
timestamp := tr.keysDB.GetRole(data.CanonicalTimestampRole)
|
timestamp, err := tr.GetBaseRole(data.CanonicalTimestampRole)
|
||||||
signed, err = tr.sign(signed, *timestamp)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
signed, err = tr.sign(signed, timestamp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -748,17 +916,10 @@ func (tr *Repo) SignTimestamp(expires time.Time) (*data.Signed, error) {
|
||||||
return signed, nil
|
return signed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tr Repo) sign(signedData *data.Signed, role data.Role) (*data.Signed, error) {
|
func (tr Repo) sign(signedData *data.Signed, role data.BaseRole) (*data.Signed, error) {
|
||||||
ks := make([]data.PublicKey, 0, len(role.KeyIDs))
|
ks := role.ListKeys()
|
||||||
for _, kid := range role.KeyIDs {
|
|
||||||
k := tr.keysDB.GetKey(kid)
|
|
||||||
if k == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ks = append(ks, k)
|
|
||||||
}
|
|
||||||
if len(ks) < 1 {
|
if len(ks) < 1 {
|
||||||
return nil, keys.ErrInvalidKey
|
return nil, signed.ErrNoKeys{}
|
||||||
}
|
}
|
||||||
err := signed.Sign(tr.cryptoService, signedData, ks...)
|
err := signed.Sign(tr.cryptoService, signedData, ks...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -61,6 +62,17 @@ func StrSliceContains(ss []string, s string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StrSliceRemove removes the the given string from the slice, returning a new slice
|
||||||
|
func StrSliceRemove(ss []string, s string) []string {
|
||||||
|
res := []string{}
|
||||||
|
for _, v := range ss {
|
||||||
|
if v != s {
|
||||||
|
res = append(res, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
// StrSliceContainsI checks if the given string appears in the slice
|
// StrSliceContainsI checks if the given string appears in the slice
|
||||||
// in a case insensitive manner
|
// in a case insensitive manner
|
||||||
func StrSliceContainsI(ss []string, s string) bool {
|
func StrSliceContainsI(ss []string, s string) bool {
|
||||||
|
@ -146,3 +158,14 @@ func FindRoleIndex(rs []*data.Role, name string) int {
|
||||||
}
|
}
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ConsistentName generates the appropriate HTTP URL path for the role,
|
||||||
|
// based on whether the repo is marked as consistent. The RemoteStore
|
||||||
|
// is responsible for adding file extensions.
|
||||||
|
func ConsistentName(role string, hashSha256 []byte) string {
|
||||||
|
if len(hashSha256) > 0 {
|
||||||
|
hash := hex.EncodeToString(hashSha256)
|
||||||
|
return fmt.Sprintf("%s.%s", role, hash)
|
||||||
|
}
|
||||||
|
return role
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue