зеркало из https://github.com/microsoft/docker.git
Vendor in notary v0.2.0
Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
This commit is contained in:
Родитель
6fa5576e30
Коммит
84dc2d9e70
|
@ -169,7 +169,7 @@ RUN set -x \
|
|||
&& rm -rf "$GOPATH"
|
||||
|
||||
# Install notary server
|
||||
ENV NOTARY_VERSION docker-v1.10-5
|
||||
ENV NOTARY_VERSION v0.2.0
|
||||
RUN set -x \
|
||||
&& export GOPATH="$(mktemp -d)" \
|
||||
&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
|
||||
|
|
|
@ -119,7 +119,7 @@ RUN set -x \
|
|||
&& rm -rf "$GOPATH"
|
||||
|
||||
# Install notary server
|
||||
ENV NOTARY_VERSION docker-v1.10-5
|
||||
ENV NOTARY_VERSION v0.2.0
|
||||
RUN set -x \
|
||||
&& export GOPATH="$(mktemp -d)" \
|
||||
&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
|
||||
|
|
|
@ -135,7 +135,7 @@ RUN set -x \
|
|||
&& rm -rf "$GOPATH"
|
||||
|
||||
# Install notary server
|
||||
ENV NOTARY_VERSION docker-v1.10-5
|
||||
ENV NOTARY_VERSION v0.2.0
|
||||
RUN set -x \
|
||||
&& export GOPATH="$(mktemp -d)" \
|
||||
&& 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 \
|
||||
&& rm -rf "$GOPATH"
|
||||
|
||||
# TODO update this when we upgrade to Go 1.5.1+
|
||||
|
||||
# Install notary server
|
||||
#ENV NOTARY_VERSION docker-v1.10-5
|
||||
#RUN set -x \
|
||||
# && export GOPATH="$(mktemp -d)" \
|
||||
# && 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") \
|
||||
# && 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 \
|
||||
# && rm -rf "$GOPATH"
|
||||
ENV NOTARY_VERSION v0.2.0
|
||||
RUN set -x \
|
||||
&& export GOPATH="$(mktemp -d)" \
|
||||
&& 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") \
|
||||
&& 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 \
|
||||
&& rm -rf "$GOPATH"
|
||||
|
||||
# Get the "docker-py" source so we can run their integration tests
|
||||
ENV DOCKER_PY_COMMIT e2878cbcc3a7eef99917adc1be252800b0e41ece
|
||||
|
|
|
@ -108,7 +108,7 @@ RUN set -x \
|
|||
&& rm -rf "$GOPATH"
|
||||
|
||||
# Install notary server
|
||||
ENV NOTARY_VERSION docker-v1.10-5
|
||||
ENV NOTARY_VERSION v0.2.0
|
||||
RUN set -x \
|
||||
&& export GOPATH="$(mktemp -d)" \
|
||||
&& 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
|
||||
|
||||
# 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 github.com/miekg/pkcs11 80f102b5cac759de406949c47f0928b99bd64cdf
|
||||
|
|
|
@ -365,7 +365,7 @@ func (s *DockerTrustSuite) TestCreateWhenCertExpired(c *check.C) {
|
|||
|
||||
func (s *DockerTrustSuite) TestTrustedCreateFromBadTrustServer(c *check.C) {
|
||||
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)
|
||||
|
||||
// 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(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)
|
||||
s.trustedCmd(createCmd)
|
||||
out, _, err = runCommandWithOutput(createCmd)
|
||||
c.Assert(err, check.Not(check.IsNil))
|
||||
c.Assert(string(out), checker.Contains, "valid signatures did not meet threshold", check.Commentf("Missing expected output on trusted push:\n%s", out))
|
||||
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 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(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)
|
||||
s.trustedCmd(pullCmd)
|
||||
out, _, err = runCommandWithOutput(pullCmd)
|
||||
|
||||
c.Assert(err, check.NotNil, check.Commentf(out))
|
||||
c.Assert(string(out), checker.Contains, "valid signatures did not meet threshold", check.Commentf(out))
|
||||
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 pull:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DockerTrustSuite) TestTrustedPullWithExpiredSnapshot(c *check.C) {
|
||||
|
|
|
@ -3260,7 +3260,7 @@ func (s *DockerTrustSuite) TestTrustedRunFromBadTrustServer(c *check.C) {
|
|||
// Windows does not support this functionality
|
||||
testRequires(c, DaemonIsLinux)
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
s.trustedCmd(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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -237,7 +237,7 @@ func (s *DockerTrustSuite) setupDelegations(c *check.C, repoName, pwd string) {
|
|||
if err != nil {
|
||||
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 {
|
||||
c.Fatalf("Error creating delegation: %s\n", err)
|
||||
}
|
||||
|
|
|
@ -42,14 +42,6 @@ GO_VERSION = $(shell go version | awk '{print $$3}')
|
|||
.DELETE_ON_ERROR: cover
|
||||
.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
|
||||
|
||||
AUTHORS: .git/HEAD
|
||||
|
@ -71,7 +63,23 @@ ${PREFIX}/bin/notary-signer: NOTARY_VERSION $(shell find . -type f -name '*.go')
|
|||
@echo "+ $@"
|
||||
@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 "+ $@"
|
||||
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)"
|
||||
|
@ -88,14 +96,24 @@ lint:
|
|||
@echo "+ $@"
|
||||
@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 "+ $@"
|
||||
@go build -tags "${NOTARY_BUILDTAGS}" -v ${GO_LDFLAGS} ./...
|
||||
|
||||
# When running `go test ./...`, it runs all the suites in parallel, which causes
|
||||
# problems when running with a yubikey
|
||||
test: TESTOPTS =
|
||||
test: go_version
|
||||
test:
|
||||
@echo Note: when testing with a yubikey plugged in, make sure to include 'TESTOPTS="-p 1"'
|
||||
@echo "+ $@ $(TESTOPTS)"
|
||||
@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;
|
||||
endef
|
||||
|
||||
gen-cover: go_version
|
||||
gen-cover:
|
||||
@mkdir -p "$(COVERDIR)"
|
||||
$(foreach PKG,$(PKGS),$(call gocover,$(PKG)))
|
||||
rm -f "$(COVERDIR)"/*testutils*.coverage.txt
|
||||
|
@ -150,7 +168,10 @@ covmerge:
|
|||
clean-protos:
|
||||
@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 "+ $@"
|
||||
|
||||
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;
|
||||
endef
|
||||
|
||||
cross: go_version
|
||||
cross:
|
||||
$(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
|
||||
with trusted collections.
|
||||
|
|
|
@ -6,7 +6,7 @@ machine:
|
|||
|
||||
post:
|
||||
# Install many go versions
|
||||
- gvm install go1.5.1 -B --name=stable
|
||||
- gvm install go1.6 -B --name=stable
|
||||
|
||||
environment:
|
||||
# Convenient shortcuts to "common" locations
|
||||
|
@ -37,10 +37,11 @@ dependencies:
|
|||
pwd: $BASE_STABLE
|
||||
|
||||
post:
|
||||
# For the stable go version, additionally install linting tools
|
||||
# For the stable go version, additionally install linting and misspell tools
|
||||
- >
|
||||
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:
|
||||
pre:
|
||||
# Output the go versions we are going to test
|
||||
|
@ -62,6 +63,10 @@ test:
|
|||
- gvm use stable && make lint:
|
||||
pwd: $BASE_STABLE
|
||||
|
||||
# MISSPELL
|
||||
- gvm use stable && make misspell:
|
||||
pwd: $BASE_STABLE
|
||||
|
||||
override:
|
||||
# Test stable, and report
|
||||
# hacking this to be parallel
|
||||
|
|
|
@ -17,7 +17,7 @@ const (
|
|||
// Types for TufChanges are namespaced by the Role they
|
||||
// are relevant for. The Root and Targets roles are the
|
||||
// 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.
|
||||
const (
|
||||
TypeRootRole = "role"
|
||||
|
@ -82,14 +82,13 @@ func (c TufChange) Content() []byte {
|
|||
// this includes creating a delegations. This format is used to avoid
|
||||
// unexpected race conditions between humans modifying the same delegation
|
||||
type TufDelegation struct {
|
||||
NewName string `json:"new_name,omitempty"`
|
||||
NewThreshold int `json:"threshold, omitempty"`
|
||||
AddKeys data.KeyList `json:"add_keys, omitempty"`
|
||||
RemoveKeys []string `json:"remove_keys,omitempty"`
|
||||
AddPaths []string `json:"add_paths,omitempty"`
|
||||
RemovePaths []string `json:"remove_paths,omitempty"`
|
||||
AddPathHashPrefixes []string `json:"add_prefixes,omitempty"`
|
||||
RemovePathHashPrefixes []string `json:"remove_prefixes,omitempty"`
|
||||
NewName string `json:"new_name,omitempty"`
|
||||
NewThreshold int `json:"threshold, omitempty"`
|
||||
AddKeys data.KeyList `json:"add_keys, omitempty"`
|
||||
RemoveKeys []string `json:"remove_keys,omitempty"`
|
||||
AddPaths []string `json:"add_paths,omitempty"`
|
||||
RemovePaths []string `json:"remove_paths,omitempty"`
|
||||
ClearAllPaths bool `json:"clear_paths,omitempty"`
|
||||
}
|
||||
|
||||
// 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 != "" {
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
|
@ -20,23 +21,13 @@ import (
|
|||
"github.com/docker/notary/tuf"
|
||||
tufclient "github.com/docker/notary/tuf/client"
|
||||
"github.com/docker/notary/tuf/data"
|
||||
"github.com/docker/notary/tuf/keys"
|
||||
"github.com/docker/notary/tuf/signed"
|
||||
"github.com/docker/notary/tuf/store"
|
||||
)
|
||||
|
||||
const (
|
||||
maxSize = 5 << 20
|
||||
"github.com/docker/notary/tuf/utils"
|
||||
)
|
||||
|
||||
func init() {
|
||||
data.SetDefaultExpiryTimes(
|
||||
map[string]int{
|
||||
"root": 3650,
|
||||
"targets": 1095,
|
||||
"snapshot": 1095,
|
||||
},
|
||||
)
|
||||
data.SetDefaultExpiryTimes(notary.NotaryDefaultExpiries)
|
||||
}
|
||||
|
||||
// ErrRepoNotInitialized is returned when trying to publish an uninitialized
|
||||
|
@ -118,7 +109,6 @@ func repositoryFromKeystores(baseDir, gun, baseURL string, rt http.RoundTripper,
|
|||
nRepo.tufRepoPath,
|
||||
"metadata",
|
||||
"json",
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
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())
|
||||
}
|
||||
|
||||
kdb := keys.NewDB()
|
||||
err = addKeyForRole(kdb, data.CanonicalRootRole, rootKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var (
|
||||
rootRole = data.NewBaseRole(
|
||||
data.CanonicalRootRole,
|
||||
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
|
||||
// make unnecessary network calls
|
||||
|
@ -232,8 +227,19 @@ func (r *NotaryRepository) Initialize(rootKeyID string, serverManagedRoles ...st
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := addKeyForRole(kdb, role, key); err != nil {
|
||||
return err
|
||||
switch role {
|
||||
case data.CanonicalSnapshotRole:
|
||||
snapshotRole = data.NewBaseRole(
|
||||
role,
|
||||
notary.MinThreshold,
|
||||
key,
|
||||
)
|
||||
case data.CanonicalTargetsRole:
|
||||
targetsRole = data.NewBaseRole(
|
||||
role,
|
||||
notary.MinThreshold,
|
||||
key,
|
||||
)
|
||||
}
|
||||
}
|
||||
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",
|
||||
role, key.Algorithm(), key.ID())
|
||||
if err := addKeyForRole(kdb, role, key); err != nil {
|
||||
return err
|
||||
switch role {
|
||||
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 {
|
||||
logrus.Debug("Error on InitRoot: ", err.Error())
|
||||
return err
|
||||
|
@ -305,96 +328,6 @@ func addChange(cl *changelist.FileChangelist, c changelist.Change, roles ...stri
|
|||
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
|
||||
// in the repository when the changelist gets applied at publish time.
|
||||
// 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)
|
||||
for _, role := range roles {
|
||||
// we don't need to do anything special with removing role from
|
||||
// roles because listSubtree always processes role and only excludes
|
||||
// descendant delegations that appear in roles.
|
||||
r.listSubtree(targets, role, roles...)
|
||||
// Define an array of roles to skip for this walk (see IMPORTANT comment above)
|
||||
skipRoles := utils.StrSliceRemove(roles, role)
|
||||
|
||||
// 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
|
||||
|
@ -466,34 +413,6 @@ func (r *NotaryRepository) ListTargets(roles ...string) ([]*TargetWithRole, erro
|
|||
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
|
||||
// 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.
|
||||
|
@ -502,7 +421,7 @@ func (r *NotaryRepository) listSubtree(targets map[string]*TargetWithRole, role
|
|||
// will be returned
|
||||
// See the IMPORTANT section on ListTargets above. Those roles also apply here.
|
||||
func (r *NotaryRepository) GetTargetByName(name string, roles ...string) (*TargetWithRole, error) {
|
||||
c, err := r.Update(false)
|
||||
_, err := r.Update(false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -510,11 +429,30 @@ func (r *NotaryRepository) GetTargetByName(name string, roles ...string) (*Targe
|
|||
if len(roles) == 0 {
|
||||
roles = append(roles, data.CanonicalTargetsRole)
|
||||
}
|
||||
var resultMeta data.FileMeta
|
||||
var resultRoleName string
|
||||
var foundTarget bool
|
||||
for _, role := range roles {
|
||||
meta, foundRole := c.TargetMeta(role, name, roles...)
|
||||
if meta != nil {
|
||||
return &TargetWithRole{
|
||||
Target: Target{Name: name, Hashes: meta.Hashes, Length: meta.Length}, Role: foundRole}, nil
|
||||
// Define an array of roles to skip for this walk (see IMPORTANT comment above)
|
||||
skipRoles := utils.StrSliceRemove(roles, role)
|
||||
|
||||
// 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)
|
||||
|
@ -532,45 +470,6 @@ func (r *NotaryRepository) GetChangelist() (changelist.Changelist, error) {
|
|||
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
|
||||
type RoleWithSignatures struct {
|
||||
Signatures []data.Signature
|
||||
|
@ -604,7 +503,7 @@ func (r *NotaryRepository) ListRoles() ([]RoleWithSignatures, error) {
|
|||
case data.CanonicalTimestampRole:
|
||||
roleWithSig.Signatures = r.tufRepo.Timestamp.Signatures
|
||||
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) {
|
||||
return nil, data.ErrInvalidRole{Role: role.Name, Reason: "invalid role name"}
|
||||
}
|
||||
|
@ -705,7 +604,7 @@ func (r *NotaryRepository) Publish() error {
|
|||
r.tufRepo, data.CanonicalSnapshotRole)
|
||||
|
||||
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
|
||||
} else if _, ok := err.(signed.ErrNoKeys); ok {
|
||||
// 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 assumes that bootstrapRepo is only used by Publish()
|
||||
func (r *NotaryRepository) bootstrapRepo() error {
|
||||
kdb := keys.NewDB()
|
||||
tufRepo := tuf.NewRepo(kdb, r.CryptoService)
|
||||
tufRepo := tuf.NewRepo(r.CryptoService)
|
||||
|
||||
logrus.Debugf("Loading trusted collection.")
|
||||
rootJSON, err := r.fileStore.GetMeta("root", 0)
|
||||
rootJSON, err := r.fileStore.GetMeta("root", -1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -760,7 +658,7 @@ func (r *NotaryRepository) bootstrapRepo() error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targetsJSON, err := r.fileStore.GetMeta("targets", 0)
|
||||
targetsJSON, err := r.fileStore.GetMeta("targets", -1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -771,7 +669,7 @@ func (r *NotaryRepository) bootstrapRepo() error {
|
|||
}
|
||||
tufRepo.SetTargets("targets", targets)
|
||||
|
||||
snapshotJSON, err := r.fileStore.GetMeta("snapshot", 0)
|
||||
snapshotJSON, err := r.fileStore.GetMeta("snapshot", -1)
|
||||
if err == nil {
|
||||
snapshot := &data.SignedSnapshot{}
|
||||
err = json.Unmarshal(snapshotJSON, snapshot)
|
||||
|
@ -854,7 +752,10 @@ func (r *NotaryRepository) Update(forWrite bool) (*tufclient.Client, error) {
|
|||
}
|
||||
err = c.Update()
|
||||
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, 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
|
||||
// until we detect a problem during update which will cause
|
||||
// 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 {
|
||||
signedRoot, cachedRootErr = r.validateRoot(rootJSON)
|
||||
|
@ -890,7 +791,8 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl
|
|||
// checking for initialization of the repo).
|
||||
|
||||
// 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 {
|
||||
// we didn't have a root in cache and were unable to load one from
|
||||
// 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(kdb, r.CryptoService)
|
||||
r.tufRepo = tuf.NewRepo(r.CryptoService)
|
||||
|
||||
if signedRoot == nil {
|
||||
return nil, ErrRepoNotInitialized{}
|
||||
|
@ -927,7 +828,6 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl
|
|||
return tufclient.NewClient(
|
||||
r.tufRepo,
|
||||
remote,
|
||||
kdb,
|
||||
r.fileStore,
|
||||
), nil
|
||||
}
|
||||
|
@ -1020,7 +920,7 @@ func (r *NotaryRepository) DeleteTrustData() error {
|
|||
if err := r.fileStore.RemoveAll(); err != nil {
|
||||
return fmt.Errorf("error clearing TUF repo data: %v", err)
|
||||
}
|
||||
r.tufRepo = tuf.NewRepo(nil, nil)
|
||||
r.tufRepo = tuf.NewRepo(nil)
|
||||
// Clear certificates
|
||||
certificates, err := r.CertStore.GetCertificatesByCN(r.gun)
|
||||
if err != nil {
|
||||
|
|
|
@ -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"
|
||||
tuf "github.com/docker/notary/tuf"
|
||||
"github.com/docker/notary/tuf/data"
|
||||
"github.com/docker/notary/tuf/keys"
|
||||
"github.com/docker/notary/tuf/store"
|
||||
"github.com/docker/notary/tuf/utils"
|
||||
)
|
||||
|
||||
// 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/",
|
||||
"",
|
||||
"json",
|
||||
"",
|
||||
"key",
|
||||
rt,
|
||||
)
|
||||
|
@ -80,53 +79,51 @@ func changeTargetsDelegation(repo *tuf.Repo, c changelist.Change) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r, err := repo.GetDelegation(c.Scope())
|
||||
if _, ok := err.(data.ErrNoSuchRole); err != nil && !ok {
|
||||
// error that wasn't ErrNoSuchRole
|
||||
return err
|
||||
}
|
||||
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())
|
||||
|
||||
// Try to create brand new role or update one
|
||||
// First add the keys, then the paths. We can only add keys and paths in this scenario
|
||||
err = repo.UpdateDelegationKeys(c.Scope(), td.AddKeys, []string{}, td.NewThreshold)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return repo.UpdateDelegations(r, td.AddKeys)
|
||||
return repo.UpdateDelegationPaths(c.Scope(), td.AddPaths, []string{}, false)
|
||||
case changelist.ActionUpdate:
|
||||
td := changelist.TufDelegation{}
|
||||
err := json.Unmarshal(c.Content(), &td)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r, err := repo.GetDelegation(c.Scope())
|
||||
delgRole, err := repo.GetDelegationRole(c.Scope())
|
||||
if err != nil {
|
||||
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 strings.Join(r.KeyIDs, ";") == strings.Join(td.RemoveKeys, ";") && len(td.AddKeys) == 0 {
|
||||
r := data.Role{Name: c.Scope()}
|
||||
return repo.DeleteDelegation(r)
|
||||
if strings.Join(delgRole.ListKeyIDs(), ";") == strings.Join(removeTUFKeyIDs, ";") && len(td.AddKeys) == 0 {
|
||||
return repo.DeleteDelegation(c.Scope())
|
||||
}
|
||||
// if we aren't deleting and the role exists, merge
|
||||
if err := r.AddPaths(td.AddPaths); err != nil {
|
||||
err = repo.UpdateDelegationKeys(c.Scope(), td.AddKeys, removeTUFKeyIDs, td.NewThreshold)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.AddPathHashPrefixes(td.AddPathHashPrefixes); err != nil {
|
||||
return err
|
||||
}
|
||||
r.RemoveKeys(td.RemoveKeys)
|
||||
r.RemovePaths(td.RemovePaths)
|
||||
r.RemovePathHashPrefixes(td.RemovePathHashPrefixes)
|
||||
return repo.UpdateDelegations(r, td.AddKeys)
|
||||
return repo.UpdateDelegationPaths(c.Scope(), td.AddPaths, td.RemovePaths, td.ClearAllPaths)
|
||||
case changelist.ActionDelete:
|
||||
r := data.Role{Name: c.Scope()}
|
||||
return repo.DeleteDelegation(r)
|
||||
return repo.DeleteDelegation(c.Scope())
|
||||
default:
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
func serializeCanonicalRole(tufRepo *tuf.Repo, role string) (out []byte, err error) {
|
||||
var s *data.Signed
|
||||
|
|
|
@ -1,7 +1,15 @@
|
|||
package notary
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// application wide constants
|
||||
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 = 2048
|
||||
// MinThreshold requires a minimum of one threshold for roles; currently we do not support a higher threshold
|
||||
|
@ -14,4 +22,29 @@ const (
|
|||
Sha256HexSize = 64
|
||||
// TrustedCertsDir is the directory, under the notary repo base directory, where trusted certs are stored
|
||||
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"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/notary"
|
||||
"github.com/docker/notary/passphrase"
|
||||
"github.com/docker/notary/trustmanager"
|
||||
"github.com/docker/notary/tuf/data"
|
||||
)
|
||||
|
||||
const zipMadeByUNIX = 3 << 8
|
||||
|
@ -31,14 +33,17 @@ var (
|
|||
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.
|
||||
func (cs *CryptoService) ExportRootKey(dest io.Writer, keyID string) error {
|
||||
func (cs *CryptoService) ExportKey(dest io.Writer, keyID, role string) error {
|
||||
var (
|
||||
pemBytes []byte
|
||||
err error
|
||||
)
|
||||
|
||||
if role != data.CanonicalRootRole {
|
||||
keyID = filepath.Join(cs.gun, keyID)
|
||||
}
|
||||
for _, ks := range cs.keyStores {
|
||||
pemBytes, err = ks.ExportKey(keyID)
|
||||
if err != nil {
|
||||
|
@ -59,9 +64,9 @@ func (cs *CryptoService) ExportRootKey(dest io.Writer, keyID string) error {
|
|||
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.
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -103,14 +108,41 @@ func (cs *CryptoService) ImportRootKey(source io.Reader) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return cs.ImportRoleKey(pemBytes, data.CanonicalRootRole, nil)
|
||||
}
|
||||
|
||||
if err = checkRootKeyIsEncrypted(pemBytes); err != nil {
|
||||
return err
|
||||
// ImportRoleKey imports a private key in PEM format key from a byte array
|
||||
// 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 {
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,27 +1,34 @@
|
|||
notaryserver:
|
||||
server:
|
||||
build: .
|
||||
dockerfile: server.Dockerfile
|
||||
links:
|
||||
- notarymysql
|
||||
- notarysigner
|
||||
ports:
|
||||
- "8080"
|
||||
- "4443:4443"
|
||||
- mysql
|
||||
- signer
|
||||
- signer:notarysigner
|
||||
environment:
|
||||
- SERVICE_NAME=notary
|
||||
command: -config=fixtures/server-config.json
|
||||
notarysigner:
|
||||
volumes:
|
||||
- /dev/bus/usb/003/010:/dev/bus/usb/002/010
|
||||
- /var/run/pcscd/pcscd.comm:/var/run/pcscd/pcscd.comm
|
||||
- SERVICE_NAME=notary_server
|
||||
ports:
|
||||
- "8080"
|
||||
- "4443:4443"
|
||||
entrypoint: /bin/bash
|
||||
command: -c "./migrations/migrate.sh && notary-server -config=fixtures/server-config.json"
|
||||
signer:
|
||||
build: .
|
||||
dockerfile: signer.Dockerfile
|
||||
links:
|
||||
- notarymysql
|
||||
command: -config=fixtures/signer-config.json
|
||||
notarymysql:
|
||||
- mysql
|
||||
environment:
|
||||
- SERVICE_NAME=notary_signer
|
||||
entrypoint: /bin/bash
|
||||
command: -c "./migrations/migrate.sh && notary-signer -config=fixtures/signer-config.json"
|
||||
mysql:
|
||||
volumes:
|
||||
- notarymysql:/var/lib/mysql
|
||||
build: ./notarymysql/
|
||||
- ./notarymysql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
|
||||
- notary_data:/var/lib/mysql
|
||||
image: mariadb:10.1.10
|
||||
ports:
|
||||
- "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 \
|
||||
libltdl-dev \
|
||||
|
@ -7,13 +8,20 @@ RUN apt-get update && apt-get install -y \
|
|||
|
||||
EXPOSE 4443
|
||||
|
||||
# Install DB migration tool
|
||||
RUN go get github.com/mattes/migrate
|
||||
|
||||
ENV NOTARYPKG github.com/docker/notary
|
||||
ENV GOPATH /go/src/${NOTARYPKG}/Godeps/_workspace:$GOPATH
|
||||
|
||||
|
||||
|
||||
# Copy the local repo to the expected go path
|
||||
COPY . /go/src/github.com/docker/notary
|
||||
|
||||
WORKDIR /go/src/${NOTARYPKG}
|
||||
|
||||
# Install notary-server
|
||||
RUN go install \
|
||||
-tags pkcs11 \
|
||||
-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
|
||||
MAINTAINER Diogo Monica "diogo@docker.com"
|
||||
FROM golang:1.5.3
|
||||
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 \
|
||||
libltdl-dev \
|
||||
libpcsclite-dev \
|
||||
opensc \
|
||||
usbutils \
|
||||
--no-install-recommends \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Initialize the SoftHSM2 token on slod 0, using PIN and SOPIN varaibles
|
||||
RUN softhsm2-util --init-token --slot 0 --label "test_token" --pin $NOTARY_SIGNER_PIN --so-pin $SOPIN
|
||||
EXPOSE 4444
|
||||
|
||||
# Install DB migration tool
|
||||
RUN go get github.com/mattes/migrate
|
||||
|
||||
ENV NOTARYPKG github.com/docker/notary
|
||||
ENV GOPATH /go/src/${NOTARYPKG}/Godeps/_workspace:$GOPATH
|
||||
|
||||
EXPOSE 4444
|
||||
ENV NOTARY_SIGNER_DEFAULT_ALIAS="timestamp_1"
|
||||
ENV NOTARY_SIGNER_TIMESTAMP_1="testpassword"
|
||||
|
||||
# Copy the local repo to the expected go path
|
||||
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`" \
|
||||
${NOTARYPKG}/cmd/notary-signer
|
||||
|
||||
|
||||
ENTRYPOINT [ "notary-signer" ]
|
||||
CMD [ "-config=fixtures/signer-config-local.json" ]
|
||||
|
|
|
@ -8,16 +8,11 @@ import (
|
|||
"sync"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/notary"
|
||||
"github.com/docker/notary/passphrase"
|
||||
"github.com/docker/notary/tuf/data"
|
||||
)
|
||||
|
||||
const (
|
||||
rootKeysSubdir = "root_keys"
|
||||
nonRootKeysSubdir = "tuf_keys"
|
||||
privDir = "private"
|
||||
)
|
||||
|
||||
// KeyFileStore persists and manages private keys on disk
|
||||
type KeyFileStore struct {
|
||||
sync.Mutex
|
||||
|
@ -37,7 +32,7 @@ type KeyMemoryStore struct {
|
|||
// NewKeyFileStore returns a new KeyFileStore creating a private directory to
|
||||
// hold the keys.
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -242,10 +237,10 @@ func listKeys(s LimitedFileStore) map[string]string {
|
|||
for _, f := range s.ListFiles() {
|
||||
// Remove the prefix of the directory from the filename
|
||||
var keyIDFull string
|
||||
if strings.HasPrefix(f, rootKeysSubdir+"/") {
|
||||
keyIDFull = strings.TrimPrefix(f, rootKeysSubdir+"/")
|
||||
if strings.HasPrefix(f, notary.RootKeysSubdir+"/") {
|
||||
keyIDFull = strings.TrimPrefix(f, notary.RootKeysSubdir+"/")
|
||||
} else {
|
||||
keyIDFull = strings.TrimPrefix(f, nonRootKeysSubdir+"/")
|
||||
keyIDFull = strings.TrimPrefix(f, notary.NonRootKeysSubdir+"/")
|
||||
}
|
||||
|
||||
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
|
||||
func getSubdir(alias string) string {
|
||||
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
|
||||
|
@ -327,7 +322,7 @@ func getRawKey(s LimitedFileStore, name string) ([]byte, string, error) {
|
|||
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
|
||||
func GetPasswdDecryptBytes(passphraseRetriever passphrase.Retriever, pemBytes []byte, name, alias string) (data.PrivateKey, string, error) {
|
||||
var (
|
||||
|
|
|
@ -470,12 +470,17 @@ func KeyToPEM(privKey data.PrivateKey, role string) ([]byte, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
block := &pem.Block{
|
||||
Type: bt,
|
||||
Headers: map[string]string{
|
||||
headers := map[string]string{}
|
||||
if role != "" {
|
||||
headers = map[string]string{
|
||||
"role": role,
|
||||
},
|
||||
Bytes: privKey.Private(),
|
||||
}
|
||||
}
|
||||
|
||||
block := &pem.Block{
|
||||
Type: bt,
|
||||
Headers: headers,
|
||||
Bytes: privKey.Private(),
|
||||
}
|
||||
|
||||
return pem.EncodeToMemory(block), nil
|
||||
|
@ -509,6 +514,19 @@ func EncryptPrivateKey(key data.PrivateKey, role, passphrase string) ([]byte, er
|
|||
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
|
||||
// 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
|
||||
func (s *YubiKeyStore) ImportKey(pemBytes []byte, keyPath string) error {
|
||||
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(
|
||||
s.passRetriever, pemBytes, "", "imported root")
|
||||
if err != nil {
|
||||
logrus.Debugf("Failed to get and retrieve a key from: %s", keyPath)
|
||||
return err
|
||||
}
|
||||
if keyPath != data.CanonicalRootRole {
|
||||
return fmt.Errorf("yubikey only supports storing root keys")
|
||||
}
|
||||
_, err = s.addKey(privKey.ID(), "root", privKey)
|
||||
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
|
||||
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.
|
||||
|
||||
This implementation retains the same 3 Clause BSD license present on
|
||||
|
|
|
@ -3,38 +3,31 @@ package client
|
|||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/notary"
|
||||
tuf "github.com/docker/notary/tuf"
|
||||
"github.com/docker/notary/tuf/data"
|
||||
"github.com/docker/notary/tuf/keys"
|
||||
"github.com/docker/notary/tuf/signed"
|
||||
"github.com/docker/notary/tuf/store"
|
||||
"github.com/docker/notary/tuf/utils"
|
||||
)
|
||||
|
||||
const maxSize int64 = 5 << 20
|
||||
|
||||
// Client is a usability wrapper around a raw TUF repo
|
||||
type Client struct {
|
||||
local *tuf.Repo
|
||||
remote store.RemoteStore
|
||||
keysDB *keys.KeyDB
|
||||
cache store.MetadataStore
|
||||
}
|
||||
|
||||
// NewClient initialized a Client with the given repo, remote source of content, key database, and cache
|
||||
func NewClient(local *tuf.Repo, remote store.RemoteStore, keysDB *keys.KeyDB, cache store.MetadataStore) *Client {
|
||||
// NewClient initialized a Client with the given repo, remote source of content, and cache
|
||||
func NewClient(local *tuf.Repo, remote store.RemoteStore, cache store.MetadataStore) *Client {
|
||||
return &Client{
|
||||
local: local,
|
||||
remote: remote,
|
||||
keysDB: keysDB,
|
||||
cache: cache,
|
||||
}
|
||||
}
|
||||
|
@ -131,11 +124,15 @@ func (c Client) checkRoot() error {
|
|||
func (c *Client) downloadRoot() error {
|
||||
logrus.Debug("Downloading Root...")
|
||||
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
|
||||
if c.local.Snapshot != nil {
|
||||
size = c.local.Snapshot.Signed.Meta[role].Length
|
||||
expectedSha256 = c.local.Snapshot.Signed.Meta[role].Hashes["sha256"]
|
||||
if prevRootMeta, ok := c.local.Snapshot.Signed.Meta[role]; ok {
|
||||
size = prevRootMeta.Length
|
||||
expectedSha256 = prevRootMeta.Hashes["sha256"]
|
||||
}
|
||||
}
|
||||
|
||||
// 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 raw []byte
|
||||
if download {
|
||||
// use consistent download if we have the checksum.
|
||||
raw, s, err = c.downloadSigned(role, size, expectedSha256)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -201,34 +199,45 @@ func (c *Client) downloadRoot() 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
|
||||
// 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
|
||||
// confirm signature with new root key
|
||||
logrus.Debug("verifying root with existing keys")
|
||||
err := signed.Verify(s, role, minVersion, c.keysDB)
|
||||
rootRole, err := c.local.GetBaseRole(role)
|
||||
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")
|
||||
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")
|
||||
root, err := data.RootFromSigned(s)
|
||||
if err != nil {
|
||||
logrus.Error(err.Error())
|
||||
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)
|
||||
if err != nil {
|
||||
logrus.Error(err.Error())
|
||||
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
|
||||
// there has been a change in root 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 {
|
||||
logrus.Debug("root did not verify with new keys")
|
||||
return err
|
||||
|
@ -248,11 +257,11 @@ func (c *Client) downloadTimestamp() error {
|
|||
// we're interacting with the repo. This will result in the
|
||||
// version being 0
|
||||
var (
|
||||
saveToCache bool
|
||||
old *data.Signed
|
||||
version = 0
|
||||
old *data.Signed
|
||||
ts *data.SignedTimestamp
|
||||
version = 0
|
||||
)
|
||||
cachedTS, err := c.cache.GetMeta(role, maxSize)
|
||||
cachedTS, err := c.cache.GetMeta(role, notary.MaxTimestampSize)
|
||||
if err == nil {
|
||||
cached := &data.Signed{}
|
||||
err := json.Unmarshal(cachedTS, cached)
|
||||
|
@ -266,49 +275,56 @@ func (c *Client) downloadTimestamp() error {
|
|||
}
|
||||
// unlike root, targets and snapshot, always try and download timestamps
|
||||
// from remote, only using the cache one if we couldn't reach remote.
|
||||
raw, s, err := c.downloadSigned(role, maxSize, nil)
|
||||
if err != nil || len(raw) == 0 {
|
||||
if old == nil {
|
||||
if err == nil {
|
||||
// couldn't retrieve data from server and don't have valid
|
||||
// data in cache.
|
||||
return store.ErrMetaNotFound{Resource: data.CanonicalTimestampRole}
|
||||
}
|
||||
return err
|
||||
raw, s, err := c.downloadSigned(role, notary.MaxTimestampSize, nil)
|
||||
if err == nil {
|
||||
ts, err = c.verifyTimestamp(s, version)
|
||||
if err == nil {
|
||||
logrus.Debug("successfully verified downloaded timestamp")
|
||||
c.cache.SetMeta(role, raw)
|
||||
c.local.SetTimestamp(ts)
|
||||
return nil
|
||||
}
|
||||
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 err != nil {
|
||||
return err
|
||||
}
|
||||
logrus.Debug("successfully verified timestamp")
|
||||
if saveToCache {
|
||||
c.cache.SetMeta(role, raw)
|
||||
}
|
||||
ts, err := data.TimestampFromSigned(s)
|
||||
if old == nil {
|
||||
// couldn't retrieve valid data from server and don't have unmarshallable data in cache.
|
||||
logrus.Debug("no cached timestamp available")
|
||||
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")
|
||||
ts, err = c.verifyTimestamp(old, version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logrus.Debug("successfully verified cached timestamp")
|
||||
c.local.SetTimestamp(ts)
|
||||
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
|
||||
func (c *Client) downloadSnapshot() error {
|
||||
logrus.Debug("Downloading Snapshot...")
|
||||
role := data.CanonicalSnapshotRole
|
||||
if c.local.Timestamp == nil {
|
||||
return ErrMissingMeta{role: "snapshot"}
|
||||
return tuf.ErrNotLoaded{Role: data.CanonicalTimestampRole}
|
||||
}
|
||||
size := c.local.Timestamp.Signed.Meta[role].Length
|
||||
expectedSha256, ok := c.local.Timestamp.Signed.Meta[role].Hashes["sha256"]
|
||||
if !ok {
|
||||
return ErrMissingMeta{role: "snapshot"}
|
||||
return data.ErrMissingMeta{Role: "snapshot"}
|
||||
}
|
||||
|
||||
var download bool
|
||||
|
@ -350,7 +366,12 @@ func (c *Client) downloadSnapshot() error {
|
|||
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 {
|
||||
return err
|
||||
}
|
||||
|
@ -382,18 +403,14 @@ func (c *Client) downloadTargets(role string) error {
|
|||
return err
|
||||
}
|
||||
if c.local.Snapshot == nil {
|
||||
return ErrMissingMeta{role: role}
|
||||
return tuf.ErrNotLoaded{Role: data.CanonicalSnapshotRole}
|
||||
}
|
||||
snap := c.local.Snapshot.Signed
|
||||
root := c.local.Root.Signed
|
||||
r := c.keysDB.GetRole(role)
|
||||
if r == nil {
|
||||
return fmt.Errorf("Invalid role: %s", role)
|
||||
}
|
||||
keyIDs := r.KeyIDs
|
||||
s, err := c.getTargetsFile(role, keyIDs, snap.Meta, root.ConsistentSnapshot, r.Threshold)
|
||||
|
||||
s, err := c.getTargetsFile(role, snap.Meta, root.ConsistentSnapshot)
|
||||
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,
|
||||
// that's ok, continue
|
||||
continue
|
||||
|
@ -401,7 +418,7 @@ func (c *Client) downloadTargets(role string) error {
|
|||
logrus.Error("Error getting targets file:", err)
|
||||
return err
|
||||
}
|
||||
t, err := data.TargetsFromSigned(s)
|
||||
t, err := data.TargetsFromSigned(s, role)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -412,14 +429,19 @@ func (c *Client) downloadTargets(role string) error {
|
|||
|
||||
// push delegated roles contained in the targets file onto the stack
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -437,15 +459,15 @@ func (c *Client) downloadSigned(role string, size int64, expectedSha256 []byte)
|
|||
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
|
||||
roleMeta, ok := snapshotMeta[role]
|
||||
if !ok {
|
||||
return nil, ErrMissingMeta{role: role}
|
||||
return nil, data.ErrMissingMeta{Role: role}
|
||||
}
|
||||
expectedSha256, ok := snapshotMeta[role].Hashes["sha256"]
|
||||
if !ok {
|
||||
return nil, ErrMissingMeta{role: role}
|
||||
return nil, data.ErrMissingMeta{Role: role}
|
||||
}
|
||||
|
||||
// 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)
|
||||
if err == nil {
|
||||
targ, err := data.TargetsFromSigned(old)
|
||||
targ, err := data.TargetsFromSigned(old, role)
|
||||
if err == nil {
|
||||
version = targ.Signed.Version
|
||||
} else {
|
||||
|
@ -478,11 +500,7 @@ func (c Client) getTargetsFile(role string, keyIDs []string, snapshotMeta data.F
|
|||
size := snapshotMeta[role].Length
|
||||
var s *data.Signed
|
||||
if download {
|
||||
rolePath, err := c.RoleTargetsPath(role, hex.EncodeToString(expectedSha256), consistent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
raw, s, err = c.downloadSigned(rolePath, size, expectedSha256)
|
||||
raw, s, err = c.downloadSigned(role, size, expectedSha256)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -490,9 +508,22 @@ func (c Client) getTargetsFile(role string, keyIDs []string, snapshotMeta data.F
|
|||
logrus.Debug("using cached ", role)
|
||||
s = old
|
||||
}
|
||||
|
||||
err = signed.Verify(s, role, version, c.keysDB)
|
||||
if err != nil {
|
||||
var targetOrDelgRole data.BaseRole
|
||||
if data.IsDelegation(role) {
|
||||
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
|
||||
}
|
||||
logrus.Debugf("successfully verified %s", role)
|
||||
|
@ -505,73 +536,3 @@ func (c Client) getTargetsFile(role string, keyIDs []string, snapshotMeta data.F
|
|||
}
|
||||
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)
|
||||
}
|
||||
|
||||
// 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
|
||||
type ErrCorruptedCache struct {
|
||||
file string
|
||||
|
|
|
@ -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
|
||||
func (ks *Keys) UnmarshalJSON(data []byte) error {
|
||||
parsed := make(map[string]tufKey)
|
||||
parsed := make(map[string]TUFKey)
|
||||
err := json.Unmarshal(data, &parsed)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -64,7 +64,7 @@ type KeyList []PublicKey
|
|||
|
||||
// UnmarshalJSON implements the json.Unmarshaller interface
|
||||
func (ks *KeyList) UnmarshalJSON(data []byte) error {
|
||||
parsed := make([]tufKey, 0, 1)
|
||||
parsed := make([]TUFKey, 0, 1)
|
||||
err := json.Unmarshal(data, &parsed)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -86,64 +86,64 @@ func (ks KeyList) IDs() []string {
|
|||
return keyIDs
|
||||
}
|
||||
|
||||
func typedPublicKey(tk tufKey) PublicKey {
|
||||
func typedPublicKey(tk TUFKey) PublicKey {
|
||||
switch tk.Algorithm() {
|
||||
case ECDSAKey:
|
||||
return &ECDSAPublicKey{tufKey: tk}
|
||||
return &ECDSAPublicKey{TUFKey: tk}
|
||||
case ECDSAx509Key:
|
||||
return &ECDSAx509PublicKey{tufKey: tk}
|
||||
return &ECDSAx509PublicKey{TUFKey: tk}
|
||||
case RSAKey:
|
||||
return &RSAPublicKey{tufKey: tk}
|
||||
return &RSAPublicKey{TUFKey: tk}
|
||||
case RSAx509Key:
|
||||
return &RSAx509PublicKey{tufKey: tk}
|
||||
return &RSAx509PublicKey{TUFKey: tk}
|
||||
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
|
||||
tk.Value.Private = nil
|
||||
switch tk.Algorithm() {
|
||||
case ECDSAKey:
|
||||
return NewECDSAPrivateKey(
|
||||
&ECDSAPublicKey{
|
||||
tufKey: tk,
|
||||
TUFKey: tk,
|
||||
},
|
||||
private,
|
||||
)
|
||||
case ECDSAx509Key:
|
||||
return NewECDSAPrivateKey(
|
||||
&ECDSAx509PublicKey{
|
||||
tufKey: tk,
|
||||
TUFKey: tk,
|
||||
},
|
||||
private,
|
||||
)
|
||||
case RSAKey:
|
||||
return NewRSAPrivateKey(
|
||||
&RSAPublicKey{
|
||||
tufKey: tk,
|
||||
TUFKey: tk,
|
||||
},
|
||||
private,
|
||||
)
|
||||
case RSAx509Key:
|
||||
return NewRSAPrivateKey(
|
||||
&RSAx509PublicKey{
|
||||
tufKey: tk,
|
||||
TUFKey: tk,
|
||||
},
|
||||
private,
|
||||
)
|
||||
case ED25519Key:
|
||||
return NewED25519PrivateKey(
|
||||
ED25519PublicKey{
|
||||
tufKey: tk,
|
||||
TUFKey: tk,
|
||||
},
|
||||
private,
|
||||
)
|
||||
}
|
||||
return &UnknownPrivateKey{
|
||||
tufKey: tk,
|
||||
TUFKey: tk,
|
||||
privateKey: privateKey{private: private},
|
||||
}, nil
|
||||
}
|
||||
|
@ -151,7 +151,7 @@ func typedPrivateKey(tk tufKey) (PrivateKey, error) {
|
|||
// NewPublicKey creates a new, correctly typed PublicKey, using the
|
||||
// UnknownPublicKey catchall for unsupported ciphers
|
||||
func NewPublicKey(alg string, public []byte) PublicKey {
|
||||
tk := tufKey{
|
||||
tk := TUFKey{
|
||||
Type: alg,
|
||||
Value: KeyPair{
|
||||
Public: public,
|
||||
|
@ -163,7 +163,7 @@ func NewPublicKey(alg string, public []byte) PublicKey {
|
|||
// NewPrivateKey creates a new, correctly typed PrivateKey, using the
|
||||
// UnknownPrivateKey catchall for unsupported ciphers
|
||||
func NewPrivateKey(pubKey PublicKey, private []byte) (PrivateKey, error) {
|
||||
tk := tufKey{
|
||||
tk := TUFKey{
|
||||
Type: pubKey.Algorithm(),
|
||||
Value: KeyPair{
|
||||
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
|
||||
func UnmarshalPublicKey(data []byte) (PublicKey, error) {
|
||||
var parsed tufKey
|
||||
var parsed TUFKey
|
||||
err := json.Unmarshal(data, &parsed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -185,7 +185,7 @@ func UnmarshalPublicKey(data []byte) (PublicKey, error) {
|
|||
|
||||
// UnmarshalPrivateKey is used to parse individual private keys in JSON
|
||||
func UnmarshalPrivateKey(data []byte) (PrivateKey, error) {
|
||||
var parsed tufKey
|
||||
var parsed TUFKey
|
||||
err := json.Unmarshal(data, &parsed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -193,26 +193,26 @@ func UnmarshalPrivateKey(data []byte) (PrivateKey, error) {
|
|||
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
|
||||
// private keys, but that would change the key ID algorithm (since the canonical
|
||||
// JSON would be different). This structure should normally be accessed through
|
||||
// the PublicKey or PrivateKey interfaces.
|
||||
type tufKey struct {
|
||||
type TUFKey struct {
|
||||
id string
|
||||
Type string `json:"keytype"`
|
||||
Value KeyPair `json:"keyval"`
|
||||
}
|
||||
|
||||
// Algorithm returns the algorithm of the key
|
||||
func (k tufKey) Algorithm() string {
|
||||
func (k TUFKey) Algorithm() string {
|
||||
return k.Type
|
||||
}
|
||||
|
||||
// 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 == "" {
|
||||
pubK := tufKey{
|
||||
pubK := TUFKey{
|
||||
Type: k.Algorithm(),
|
||||
Value: KeyPair{
|
||||
Public: k.Public(),
|
||||
|
@ -230,7 +230,7 @@ func (k *tufKey) ID() string {
|
|||
}
|
||||
|
||||
// Public returns the public bytes
|
||||
func (k tufKey) Public() []byte {
|
||||
func (k TUFKey) Public() []byte {
|
||||
return k.Value.Public
|
||||
}
|
||||
|
||||
|
@ -239,42 +239,42 @@ func (k tufKey) Public() []byte {
|
|||
// ECDSAPublicKey represents an ECDSA key using a raw serialization
|
||||
// of the public key
|
||||
type ECDSAPublicKey struct {
|
||||
tufKey
|
||||
TUFKey
|
||||
}
|
||||
|
||||
// ECDSAx509PublicKey represents an ECDSA key using an x509 cert
|
||||
// as the serialized format of the public key
|
||||
type ECDSAx509PublicKey struct {
|
||||
tufKey
|
||||
TUFKey
|
||||
}
|
||||
|
||||
// RSAPublicKey represents an RSA key using a raw serialization
|
||||
// of the public key
|
||||
type RSAPublicKey struct {
|
||||
tufKey
|
||||
TUFKey
|
||||
}
|
||||
|
||||
// RSAx509PublicKey represents an RSA key using an x509 cert
|
||||
// as the serialized format of the public key
|
||||
type RSAx509PublicKey struct {
|
||||
tufKey
|
||||
TUFKey
|
||||
}
|
||||
|
||||
// ED25519PublicKey represents an ED25519 key using a raw serialization
|
||||
// of the public key
|
||||
type ED25519PublicKey struct {
|
||||
tufKey
|
||||
TUFKey
|
||||
}
|
||||
|
||||
// UnknownPublicKey is a catchall for key types that are not supported
|
||||
type UnknownPublicKey struct {
|
||||
tufKey
|
||||
TUFKey
|
||||
}
|
||||
|
||||
// NewECDSAPublicKey initializes a new public key with the ECDSAKey type
|
||||
func NewECDSAPublicKey(public []byte) *ECDSAPublicKey {
|
||||
return &ECDSAPublicKey{
|
||||
tufKey: tufKey{
|
||||
TUFKey: TUFKey{
|
||||
Type: ECDSAKey,
|
||||
Value: KeyPair{
|
||||
Public: public,
|
||||
|
@ -287,7 +287,7 @@ func NewECDSAPublicKey(public []byte) *ECDSAPublicKey {
|
|||
// NewECDSAx509PublicKey initializes a new public key with the ECDSAx509Key type
|
||||
func NewECDSAx509PublicKey(public []byte) *ECDSAx509PublicKey {
|
||||
return &ECDSAx509PublicKey{
|
||||
tufKey: tufKey{
|
||||
TUFKey: TUFKey{
|
||||
Type: ECDSAx509Key,
|
||||
Value: KeyPair{
|
||||
Public: public,
|
||||
|
@ -300,7 +300,7 @@ func NewECDSAx509PublicKey(public []byte) *ECDSAx509PublicKey {
|
|||
// NewRSAPublicKey initializes a new public key with the RSA type
|
||||
func NewRSAPublicKey(public []byte) *RSAPublicKey {
|
||||
return &RSAPublicKey{
|
||||
tufKey: tufKey{
|
||||
TUFKey: TUFKey{
|
||||
Type: RSAKey,
|
||||
Value: KeyPair{
|
||||
Public: public,
|
||||
|
@ -313,7 +313,7 @@ func NewRSAPublicKey(public []byte) *RSAPublicKey {
|
|||
// NewRSAx509PublicKey initializes a new public key with the RSAx509Key type
|
||||
func NewRSAx509PublicKey(public []byte) *RSAx509PublicKey {
|
||||
return &RSAx509PublicKey{
|
||||
tufKey: tufKey{
|
||||
TUFKey: TUFKey{
|
||||
Type: RSAx509Key,
|
||||
Value: KeyPair{
|
||||
Public: public,
|
||||
|
@ -326,7 +326,7 @@ func NewRSAx509PublicKey(public []byte) *RSAx509PublicKey {
|
|||
// NewED25519PublicKey initializes a new public key with the ED25519Key type
|
||||
func NewED25519PublicKey(public []byte) *ED25519PublicKey {
|
||||
return &ED25519PublicKey{
|
||||
tufKey: tufKey{
|
||||
TUFKey: TUFKey{
|
||||
Type: ED25519Key,
|
||||
Value: KeyPair{
|
||||
Public: public,
|
||||
|
@ -367,7 +367,7 @@ type ED25519PrivateKey struct {
|
|||
|
||||
// UnknownPrivateKey is a catchall for unsupported key types
|
||||
type UnknownPrivateKey struct {
|
||||
tufKey
|
||||
TUFKey
|
||||
privateKey
|
||||
}
|
||||
|
||||
|
@ -515,10 +515,10 @@ func (k UnknownPrivateKey) SignatureAlgorithm() SigAlgorithm {
|
|||
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.
|
||||
func PublicKeyFromPrivate(pk PrivateKey) PublicKey {
|
||||
return typedPublicKey(tufKey{
|
||||
return typedPublicKey(TUFKey{
|
||||
Type: pk.Algorithm(),
|
||||
Value: KeyPair{
|
||||
Public: pk.Public(),
|
||||
|
|
|
@ -2,10 +2,11 @@ package data
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Sirupsen/logrus"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Canonical base role names
|
||||
|
@ -85,32 +86,139 @@ func IsDelegation(role string) bool {
|
|||
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
|
||||
// Eventually should only be used for immediately before and after serialization/deserialization
|
||||
type RootRole struct {
|
||||
KeyIDs []string `json:"keyids"`
|
||||
Threshold int `json:"threshold"`
|
||||
}
|
||||
|
||||
// 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 {
|
||||
RootRole
|
||||
Name string `json:"name"`
|
||||
Paths []string `json:"paths,omitempty"`
|
||||
PathHashPrefixes []string `json:"path_hash_prefixes,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Paths []string `json:"paths,omitempty"`
|
||||
}
|
||||
|
||||
// NewRole creates a new Role object from the given parameters
|
||||
func NewRole(name string, threshold int, keyIDs, paths, pathHashPrefixes []string) (*Role, error) {
|
||||
if len(paths) > 0 && len(pathHashPrefixes) > 0 {
|
||||
return nil, ErrInvalidRole{
|
||||
Role: name,
|
||||
Reason: "roles may not have both Paths and PathHashPrefixes",
|
||||
}
|
||||
}
|
||||
func NewRole(name string, threshold int, keyIDs, paths []string) (*Role, error) {
|
||||
if IsDelegation(name) {
|
||||
if len(paths) == 0 && len(pathHashPrefixes) == 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)
|
||||
if len(paths) == 0 {
|
||||
logrus.Debugf("role %s with no Paths will never be able to publish content until one or more are added", name)
|
||||
}
|
||||
}
|
||||
if threshold < 1 {
|
||||
|
@ -124,52 +232,15 @@ func NewRole(name string, threshold int, keyIDs, paths, pathHashPrefixes []strin
|
|||
KeyIDs: keyIDs,
|
||||
Threshold: threshold,
|
||||
},
|
||||
Name: name,
|
||||
Paths: paths,
|
||||
PathHashPrefixes: pathHashPrefixes,
|
||||
Name: name,
|
||||
Paths: paths,
|
||||
}, 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
|
||||
func (r Role) CheckPaths(path string) bool {
|
||||
for _, p := range 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)
|
||||
return checkPaths(path, r.Paths)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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)
|
||||
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
|
||||
func (r *Role) RemoveKeys(ids []string) {
|
||||
r.KeyIDs = subtractStrSlices(r.KeyIDs, ids)
|
||||
|
@ -211,11 +267,6 @@ func (r *Role) RemovePaths(paths []string) {
|
|||
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 {
|
||||
have := make(map[string]bool)
|
||||
for _, e := range orig {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package data
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/docker/go/canonical/json"
|
||||
|
@ -23,14 +24,57 @@ type Root struct {
|
|||
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
|
||||
func NewRoot(keys map[string]PublicKey, roles map[string]*RootRole, consistent bool) (*SignedRoot, error) {
|
||||
signedRoot := &SignedRoot{
|
||||
Signatures: make([]Signature, 0),
|
||||
Signed: Root{
|
||||
Type: TUFTypes["root"],
|
||||
Type: TUFTypes[CanonicalRootRole],
|
||||
Version: 0,
|
||||
Expires: DefaultExpires("root"),
|
||||
Expires: DefaultExpires(CanonicalRootRole),
|
||||
Keys: keys,
|
||||
Roles: roles,
|
||||
ConsistentSnapshot: consistent,
|
||||
|
@ -41,6 +85,34 @@ func NewRoot(keys map[string]PublicKey, roles map[string]*RootRole, consistent b
|
|||
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
|
||||
func (r SignedRoot) ToSigned() (*Signed, error) {
|
||||
s, err := defaultSerializer.MarshalCanonical(r.Signed)
|
||||
|
@ -70,11 +142,14 @@ func (r SignedRoot) MarshalJSON() ([]byte, error) {
|
|||
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) {
|
||||
r := Root{}
|
||||
err := json.Unmarshal(s.Signed, &r)
|
||||
if err != nil {
|
||||
if err := defaultSerializer.Unmarshal(s.Signed, &r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := isValidRootStructure(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sigs := make([]Signature, len(s.Signatures))
|
||||
|
|
|
@ -2,6 +2,8 @@ package data
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
|
@ -23,6 +25,30 @@ type Snapshot struct {
|
|||
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
|
||||
// and targets objects
|
||||
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
|
||||
func (sp SignedSnapshot) ToSigned() (*Signed, error) {
|
||||
s, err := json.MarshalCanonical(sp.Signed)
|
||||
func (sp *SignedSnapshot) ToSigned() (*Signed, error) {
|
||||
s, err := defaultSerializer.MarshalCanonical(sp.Signed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -88,6 +114,15 @@ func (sp *SignedSnapshot) AddMeta(role string, meta FileMeta) {
|
|||
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
|
||||
// exist in the snapshot, it's a noop.
|
||||
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
|
||||
func SnapshotFromSigned(s *Signed) (*SignedSnapshot, error) {
|
||||
sp := Snapshot{}
|
||||
err := json.Unmarshal(s.Signed, &sp)
|
||||
if err != nil {
|
||||
if err := defaultSerializer.Unmarshal(s.Signed, &sp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := isValidSnapshotStructure(sp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sigs := make([]Signature, len(s.Signatures))
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package data
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
|
||||
"github.com/docker/go/canonical/json"
|
||||
)
|
||||
|
@ -23,6 +23,33 @@ type Targets struct {
|
|||
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
|
||||
func NewTargets() *SignedTargets {
|
||||
return &SignedTargets{
|
||||
|
@ -51,30 +78,58 @@ func (t SignedTargets) GetMeta(path string) *FileMeta {
|
|||
return nil
|
||||
}
|
||||
|
||||
// GetDelegations filters the roles and associated keys that may be
|
||||
// the signers for the given target path. If no appropriate roles
|
||||
// can be found, it will simply return nil for the return values.
|
||||
// The returned slice of Role will have order maintained relative
|
||||
// to the role slice on Delegations per TUF spec proposal on using
|
||||
// order to determine priority.
|
||||
func (t SignedTargets) GetDelegations(path string) []*Role {
|
||||
var roles []*Role
|
||||
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.
|
||||
// GetValidDelegations filters the delegation roles specified in the signed targets, and
|
||||
// only returns roles that are direct children and restricts their paths
|
||||
func (t SignedTargets) GetValidDelegations(parent DelegationRole) []DelegationRole {
|
||||
roles := t.buildDelegationRoles()
|
||||
result := []DelegationRole{}
|
||||
for _, r := range roles {
|
||||
validRole, err := parent.Restrict(r)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if r.CheckPaths(path) {
|
||||
roles = append(roles, r)
|
||||
result = append(result, validRole)
|
||||
}
|
||||
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
|
||||
}
|
||||
if r.CheckPrefixes(pathHash) {
|
||||
roles = append(roles, r)
|
||||
continue
|
||||
}
|
||||
//keysDB.AddRole(r)
|
||||
roles = append(roles, delgRole)
|
||||
}
|
||||
return roles
|
||||
}
|
||||
|
@ -93,8 +148,8 @@ func (t *SignedTargets) AddDelegation(role *Role, keys []*PublicKey) error {
|
|||
}
|
||||
|
||||
// ToSigned partially serializes a SignedTargets for further signing
|
||||
func (t SignedTargets) ToSigned() (*Signed, error) {
|
||||
s, err := json.MarshalCanonical(t.Signed)
|
||||
func (t *SignedTargets) ToSigned() (*Signed, error) {
|
||||
s, err := defaultSerializer.MarshalCanonical(t.Signed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -111,13 +166,25 @@ func (t SignedTargets) ToSigned() (*Signed, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
// TargetsFromSigned fully unpacks a Signed object into a SignedTargets
|
||||
func TargetsFromSigned(s *Signed) (*SignedTargets, error) {
|
||||
t := Targets{}
|
||||
err := json.Unmarshal(s.Signed, &t)
|
||||
// MarshalJSON returns the serialized form of SignedTargets as bytes
|
||||
func (t *SignedTargets) MarshalJSON() ([]byte, error) {
|
||||
signed, err := t.ToSigned()
|
||||
if err != nil {
|
||||
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))
|
||||
copy(sigs, s.Signatures)
|
||||
return &SignedTargets{
|
||||
|
|
|
@ -2,6 +2,8 @@ package data
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/docker/go/canonical/json"
|
||||
|
@ -22,6 +24,26 @@ type Timestamp struct {
|
|||
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
|
||||
func NewTimestamp(snapshot *Signed) (*SignedTimestamp, error) {
|
||||
snapshotJSON, err := json.Marshal(snapshot)
|
||||
|
@ -47,8 +69,8 @@ func NewTimestamp(snapshot *Signed) (*SignedTimestamp, error) {
|
|||
|
||||
// ToSigned partially serializes a SignedTimestamp such that it can
|
||||
// be signed
|
||||
func (ts SignedTimestamp) ToSigned() (*Signed, error) {
|
||||
s, err := json.MarshalCanonical(ts.Signed)
|
||||
func (ts *SignedTimestamp) ToSigned() (*Signed, error) {
|
||||
s, err := defaultSerializer.MarshalCanonical(ts.Signed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -65,12 +87,33 @@ func (ts SignedTimestamp) ToSigned() (*Signed, error) {
|
|||
}, 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
|
||||
// SignedTimestamp
|
||||
func TimestampFromSigned(s *Signed) (*SignedTimestamp, error) {
|
||||
ts := Timestamp{}
|
||||
err := json.Unmarshal(s.Signed, &ts)
|
||||
if err != nil {
|
||||
if err := defaultSerializer.Unmarshal(s.Signed, &ts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := isValidTimestampStructure(ts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sigs := make([]Signature, len(s.Signatures))
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/go/canonical/json"
|
||||
"github.com/docker/notary"
|
||||
)
|
||||
|
||||
// SigAlgorithm for types of signatures
|
||||
|
@ -171,16 +172,16 @@ func NewDelegations() *Delegations {
|
|||
}
|
||||
}
|
||||
|
||||
// defines number of days in which something should expire
|
||||
var defaultExpiryTimes = map[string]int{
|
||||
CanonicalRootRole: 365,
|
||||
CanonicalTargetsRole: 90,
|
||||
CanonicalSnapshotRole: 7,
|
||||
CanonicalTimestampRole: 1,
|
||||
// These values are recommended TUF expiry times.
|
||||
var defaultExpiryTimes = map[string]time.Duration{
|
||||
CanonicalRootRole: notary.Year,
|
||||
CanonicalTargetsRole: 90 * notary.Day,
|
||||
CanonicalSnapshotRole: 7 * notary.Day,
|
||||
CanonicalTimestampRole: notary.Day,
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if _, ok := defaultExpiryTimes[key]; !ok {
|
||||
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
|
||||
func DefaultExpires(role string) time.Time {
|
||||
var t time.Time
|
||||
if t, ok := defaultExpiryTimes[role]; ok {
|
||||
return time.Now().AddDate(0, 0, t)
|
||||
if d, ok := defaultExpiryTimes[role]; ok {
|
||||
return time.Now().Add(d)
|
||||
}
|
||||
var t time.Time
|
||||
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/docker/go/canonical/json"
|
||||
"github.com/docker/notary/tuf/data"
|
||||
"github.com/docker/notary/tuf/keys"
|
||||
)
|
||||
|
||||
// Various basic signing errors
|
||||
|
@ -57,18 +56,18 @@ func VerifyRoot(s *data.Signed, minVersion int, keys map[string]data.PublicKey)
|
|||
continue
|
||||
}
|
||||
// threshold of 1 so return on first success
|
||||
return verifyMeta(s, "root", minVersion)
|
||||
return verifyMeta(s, data.CanonicalRootRole, minVersion)
|
||||
}
|
||||
return ErrRoleThreshold{}
|
||||
}
|
||||
|
||||
// Verify checks the signatures and metadata (expiry, version) for the signed role
|
||||
// data
|
||||
func Verify(s *data.Signed, role string, minVersion int, db *keys.KeyDB) error {
|
||||
if err := verifyMeta(s, role, minVersion); err != nil {
|
||||
func Verify(s *data.Signed, role data.BaseRole, minVersion int) error {
|
||||
if err := verifyMeta(s, role.Name, minVersion); err != nil {
|
||||
return err
|
||||
}
|
||||
return VerifySignatures(s, role, db)
|
||||
return VerifySignatures(s, role)
|
||||
}
|
||||
|
||||
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
|
||||
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 {
|
||||
return ErrNoSignatures
|
||||
}
|
||||
|
||||
roleData := db.GetRole(role)
|
||||
if roleData == nil {
|
||||
return ErrUnknownRole
|
||||
}
|
||||
|
||||
if roleData.Threshold < 1 {
|
||||
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{}
|
||||
if err := json.Unmarshal(s.Signed, &decoded); err != nil {
|
||||
return err
|
||||
|
@ -123,12 +119,8 @@ func VerifySignatures(s *data.Signed, role string, db *keys.KeyDB) error {
|
|||
valid := make(map[string]struct{})
|
||||
for _, sig := range s.Signatures {
|
||||
logrus.Debug("verifying signature for key ID: ", sig.KeyID)
|
||||
if !roleData.ValidKey(sig.KeyID) {
|
||||
logrus.Debugf("continuing b/c keyid was invalid: %s for roledata %s\n", sig.KeyID, roleData)
|
||||
continue
|
||||
}
|
||||
key := db.GetKey(sig.KeyID)
|
||||
if key == nil {
|
||||
key, ok := roleData.Keys[sig.KeyID]
|
||||
if !ok {
|
||||
logrus.Debugf("continuing b/c keyid lookup was nil: %s\n", sig.KeyID)
|
||||
continue
|
||||
}
|
||||
|
@ -153,28 +145,3 @@ func VerifySignatures(s *data.Signed, role string, db *keys.KeyDB) error {
|
|||
|
||||
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 (
|
||||
"fmt"
|
||||
"github.com/docker/notary"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
|
@ -9,25 +10,19 @@ import (
|
|||
)
|
||||
|
||||
// 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)
|
||||
targetsDir := path.Join(baseDir, targetsSubDir)
|
||||
|
||||
// Make sure we can create the necessary dirs and they are writable
|
||||
err := os.MkdirAll(metaDir, 0700)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = os.MkdirAll(targetsDir, 0700)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &FilesystemStore{
|
||||
baseDir: baseDir,
|
||||
metaDir: metaDir,
|
||||
metaExtension: metaExtension,
|
||||
targetsDir: targetsDir,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -36,7 +31,6 @@ type FilesystemStore struct {
|
|||
baseDir string
|
||||
metaDir string
|
||||
metaExtension string
|
||||
targetsDir 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)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
meta, err := ioutil.ReadFile(f.getPath(name))
|
||||
if err != nil {
|
||||
|
@ -53,7 +48,14 @@ func (f *FilesystemStore) GetMeta(name string, size int64) ([]byte, error) {
|
|||
}
|
||||
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
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
"path"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/notary"
|
||||
"github.com/docker/notary/tuf/validation"
|
||||
)
|
||||
|
||||
|
@ -33,6 +34,9 @@ type ErrServerUnavailable struct {
|
|||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -71,13 +75,12 @@ type HTTPStore struct {
|
|||
baseURL url.URL
|
||||
metaPrefix string
|
||||
metaExtension string
|
||||
targetsPrefix string
|
||||
keyExtension string
|
||||
roundTrip http.RoundTripper
|
||||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -92,7 +95,6 @@ func NewHTTPStore(baseURL, metaPrefix, metaExtension, targetsPrefix, keyExtensio
|
|||
baseURL: *base,
|
||||
metaPrefix: metaPrefix,
|
||||
metaExtension: metaExtension,
|
||||
targetsPrefix: targetsPrefix,
|
||||
keyExtension: keyExtension,
|
||||
roundTrip: roundTrip,
|
||||
}, 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
|
||||
// is acceptable because in the case of timestamp.json, the size is a cap,
|
||||
// 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) {
|
||||
url, err := s.buildMetaURL(name)
|
||||
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)
|
||||
return nil, err
|
||||
}
|
||||
if size == -1 {
|
||||
size = notary.MaxDownloadSize
|
||||
}
|
||||
if resp.ContentLength > size {
|
||||
return nil, ErrMaliciousServer{}
|
||||
}
|
||||
|
@ -250,11 +256,6 @@ func (s HTTPStore) buildMetaURL(name string) (*url.URL, error) {
|
|||
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) {
|
||||
filename := fmt.Sprintf("%s.%s", name, s.keyExtension)
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
func (s HTTPStore) GetKey(role string) ([]byte, error) {
|
||||
url, err := s.buildKeyURL(role)
|
||||
|
|
|
@ -1,13 +1,5 @@
|
|||
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
|
||||
// with a store of TUF files
|
||||
type MetadataStore interface {
|
||||
|
@ -23,17 +15,9 @@ type PublicKeyStore interface {
|
|||
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
|
||||
type LocalStore interface {
|
||||
MetadataStore
|
||||
TargetStore
|
||||
}
|
||||
|
||||
// RemoteStore is similar to LocalStore with the added expectation that it should
|
||||
|
@ -41,5 +25,4 @@ type LocalStore interface {
|
|||
type RemoteStore interface {
|
||||
MetadataStore
|
||||
PublicKeyStore
|
||||
GetTarget(path string) (io.ReadCloser, error)
|
||||
}
|
||||
|
|
|
@ -1,38 +1,59 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/docker/notary"
|
||||
"github.com/docker/notary/tuf/data"
|
||||
"github.com/docker/notary/tuf/utils"
|
||||
)
|
||||
|
||||
// NewMemoryStore returns a MetadataStore that operates entirely in memory.
|
||||
// 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 {
|
||||
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 {
|
||||
files = make(map[string][]byte)
|
||||
}
|
||||
return &memoryStore{
|
||||
meta: meta,
|
||||
files: files,
|
||||
keys: make(map[string][]data.PrivateKey),
|
||||
return &MemoryStore{
|
||||
meta: meta,
|
||||
consistent: consistent,
|
||||
keys: make(map[string][]data.PrivateKey),
|
||||
}
|
||||
}
|
||||
|
||||
type memoryStore struct {
|
||||
meta map[string][]byte
|
||||
files map[string][]byte
|
||||
keys map[string][]data.PrivateKey
|
||||
// MemoryStore implements a mock RemoteStore entirely in memory.
|
||||
// For testing purposes only.
|
||||
type MemoryStore struct {
|
||||
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]
|
||||
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 int64(len(d)) < size {
|
||||
return d, nil
|
||||
|
@ -42,12 +63,19 @@ func (m *memoryStore) GetMeta(name string, size int64) ([]byte, error) {
|
|||
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
|
||||
|
||||
checksum := sha256.Sum256(meta)
|
||||
path := utils.ConsistentName(name, checksum[:])
|
||||
m.consistent[path] = meta
|
||||
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 {
|
||||
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
|
||||
// exist, no error is returned
|
||||
func (m *memoryStore) RemoveMeta(name string) error {
|
||||
delete(m.meta, name)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *memoryStore) GetTarget(path string) (io.ReadCloser, error) {
|
||||
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
|
||||
}
|
||||
func (m *MemoryStore) RemoveMeta(name string) error {
|
||||
if meta, ok := m.meta[name]; ok {
|
||||
checksum := sha256.Sum256(meta)
|
||||
path := utils.ConsistentName(name, checksum[:])
|
||||
delete(m.meta, name)
|
||||
delete(m.consistent, path)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *memoryStore) Commit(map[string][]byte, bool, map[string]data.Hashes) error {
|
||||
return nil
|
||||
// GetKey returns the public key for the given role
|
||||
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) {
|
||||
return nil, fmt.Errorf("GetKey is not implemented for the memoryStore")
|
||||
}
|
||||
|
||||
// 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)
|
||||
// RemoveAll clears the existing memory store by setting this store as new empty one
|
||||
func (m *MemoryStore) RemoveAll() error {
|
||||
*m = *NewMemoryStore(nil)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -3,8 +3,6 @@ package tuf
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path"
|
||||
|
@ -12,8 +10,8 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/notary"
|
||||
"github.com/docker/notary/tuf/data"
|
||||
"github.com/docker/notary/tuf/keys"
|
||||
"github.com/docker/notary/tuf/signed"
|
||||
"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
|
||||
// the repo
|
||||
// the repo. This means specifically that the relevant JSON file has not
|
||||
// been loaded.
|
||||
type ErrNotLoaded struct {
|
||||
role string
|
||||
Role 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.
|
||||
// It operates at the data.Signed level, accepting and producing
|
||||
// data.Signed objects. Users of a Repo are responsible for
|
||||
|
@ -59,16 +61,15 @@ type Repo struct {
|
|||
Targets map[string]*data.SignedTargets
|
||||
Snapshot *data.SignedSnapshot
|
||||
Timestamp *data.SignedTimestamp
|
||||
keysDB *keys.KeyDB
|
||||
cryptoService signed.CryptoService
|
||||
}
|
||||
|
||||
// NewRepo initializes a Repo instance with a keysDB and a signer.
|
||||
// If the Repo will only be used for reading, the signer should be nil.
|
||||
func NewRepo(keysDB *keys.KeyDB, cryptoService signed.CryptoService) *Repo {
|
||||
// NewRepo initializes a Repo instance with a CryptoService.
|
||||
// If the Repo will only be used for reading, the CryptoService
|
||||
// can be nil.
|
||||
func NewRepo(cryptoService signed.CryptoService) *Repo {
|
||||
repo := &Repo{
|
||||
Targets: make(map[string]*data.SignedTargets),
|
||||
keysDB: keysDB,
|
||||
cryptoService: cryptoService,
|
||||
}
|
||||
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
|
||||
func (tr *Repo) AddBaseKeys(role string, keys ...data.PublicKey) error {
|
||||
if tr.Root == nil {
|
||||
return ErrNotLoaded{role: "root"}
|
||||
return ErrNotLoaded{Role: data.CanonicalRootRole}
|
||||
}
|
||||
ids := []string{}
|
||||
for _, k := range keys {
|
||||
// Store only the public portion
|
||||
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())
|
||||
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
|
||||
|
||||
// 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
|
||||
func (tr *Repo) ReplaceBaseKeys(role string, keys ...data.PublicKey) error {
|
||||
r := tr.keysDB.GetRole(role)
|
||||
err := tr.RemoveBaseKeys(role, r.KeyIDs...)
|
||||
r, err := tr.GetBaseRole(role)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = tr.RemoveBaseKeys(role, r.ListKeyIDs()...)
|
||||
if err != nil {
|
||||
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
|
||||
func (tr *Repo) RemoveBaseKeys(role string, keyIDs ...string) error {
|
||||
if tr.Root == nil {
|
||||
return ErrNotLoaded{role: "root"}
|
||||
return ErrNotLoaded{Role: data.CanonicalRootRole}
|
||||
}
|
||||
var keep []string
|
||||
toDelete := make(map[string]struct{})
|
||||
|
@ -173,117 +165,253 @@ func (tr *Repo) RemoveBaseKeys(role string, keyIDs ...string) error {
|
|||
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
|
||||
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
|
||||
// role name or ErrInvalidRole
|
||||
func (tr *Repo) GetDelegation(role string) (*data.Role, error) {
|
||||
r := data.Role{Name: role}
|
||||
if !r.IsDelegation() {
|
||||
return nil, data.ErrInvalidRole{Role: role, Reason: "not a valid delegated role"}
|
||||
// Walk to parent, and either create or update this delegation. We can only create a new delegation if we're given keys
|
||||
// Ensure all updates are valid, by checking against parent ancestor paths and ensuring the keys meet the role threshold.
|
||||
func delegationUpdateVisitor(roleName string, addKeys data.KeyList, removeKeys, addPaths, removePaths []string, clearAllPaths bool, newThreshold int) walkVisitorFunc {
|
||||
return func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} {
|
||||
var err error
|
||||
// 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
|
||||
// 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.
|
||||
func (tr *Repo) UpdateDelegations(role *data.Role, keys []data.PublicKey) error {
|
||||
if !role.IsDelegation() || !role.IsValid() {
|
||||
return data.ErrInvalidRole{Role: role.Name, Reason: "not a valid delegated role"}
|
||||
func (tr *Repo) UpdateDelegationKeys(roleName string, addKeys data.KeyList, removeKeys []string, newThreshold int) error {
|
||||
if !data.IsDelegation(roleName) {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
// 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
|
||||
var err error
|
||||
p, err = tr.InitTargets(parent)
|
||||
_, err = tr.InitTargets(parent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, k := range keys {
|
||||
if !utils.StrSliceContains(role.KeyIDs, k.ID()) {
|
||||
role.KeyIDs = append(role.KeyIDs, k.ID())
|
||||
}
|
||||
p.Signed.Delegations.Keys[k.ID()] = k
|
||||
tr.keysDB.AddKey(k)
|
||||
// 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
|
||||
// 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))
|
||||
if err != nil {
|
||||
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
|
||||
// will never be able to create a valid targets file
|
||||
// and should be considered invalid.
|
||||
if len(role.KeyIDs) < role.Threshold {
|
||||
return data.ErrInvalidRole{Role: role.Name, Reason: "insufficient keys to meet threshold"}
|
||||
// check the parent role's metadata
|
||||
_, ok := tr.Targets[parent]
|
||||
if !ok { // the parent targetfile may not exist yet
|
||||
// if not, this is an error because a delegation must exist to edit only paths
|
||||
return data.ErrInvalidRole{Role: roleName, Reason: "no valid delegated role exists"}
|
||||
}
|
||||
|
||||
foundAt := utils.FindRoleIndex(p.Signed.Delegations.Roles, role.Name)
|
||||
|
||||
if foundAt >= 0 {
|
||||
p.Signed.Delegations.Roles[foundAt] = role
|
||||
} else {
|
||||
p.Signed.Delegations.Roles = append(p.Signed.Delegations.Roles, role)
|
||||
// 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
|
||||
// since we've already done another walk to the parent role in VerifyCanSign
|
||||
err := tr.WalkTargets("", parent, delegationUpdateVisitor(roleName, data.KeyList{}, []string{}, addPaths, removePaths, clearPaths, notary.MinThreshold))
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
// DeleteDelegation removes a delegated targets role from its parent
|
||||
// targets object. It also deletes the delegation from the snapshot.
|
||||
// DeleteDelegation will only make use of the role Name field.
|
||||
func (tr *Repo) DeleteDelegation(role data.Role) error {
|
||||
if !role.IsDelegation() {
|
||||
return data.ErrInvalidRole{Role: role.Name, Reason: "not a valid delegated role"}
|
||||
func (tr *Repo) DeleteDelegation(roleName string) error {
|
||||
if !data.IsDelegation(roleName) {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
// delete delegated data from Targets map and Snapshot - if they don't
|
||||
// exist, these are no-op
|
||||
delete(tr.Targets, name)
|
||||
tr.Snapshot.DeleteMeta(name)
|
||||
delete(tr.Targets, roleName)
|
||||
tr.Snapshot.DeleteMeta(roleName)
|
||||
|
||||
p, ok := tr.Targets[parent]
|
||||
if !ok {
|
||||
|
@ -292,7 +420,7 @@ func (tr *Repo) DeleteDelegation(role data.Role) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
foundAt := utils.FindRoleIndex(p.Signed.Delegations.Roles, name)
|
||||
foundAt := utils.FindRoleIndex(p.Signed.Delegations.Roles, roleName)
|
||||
|
||||
if foundAt >= 0 {
|
||||
var roles []*data.Role
|
||||
|
@ -311,53 +439,32 @@ func (tr *Repo) DeleteDelegation(role data.Role) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// InitRepo creates the base files for a repo. It inspects data.BaseRoles and
|
||||
// data.ValidTypes to determine what the role names and filename should be. It
|
||||
// also relies on the keysDB having already been populated with the keys and
|
||||
// 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 {
|
||||
// InitRoot initializes an empty root file with the 4 core roles passed to the
|
||||
// method, and the consistent flag.
|
||||
func (tr *Repo) InitRoot(root, timestamp, snapshot, targets data.BaseRole, consistent bool) error {
|
||||
rootRoles := make(map[string]*data.RootRole)
|
||||
rootKeys := make(map[string]data.PublicKey)
|
||||
for _, r := range data.BaseRoles {
|
||||
role := tr.keysDB.GetRole(r)
|
||||
if role == nil {
|
||||
return data.ErrInvalidRole{Role: data.CanonicalRootRole, Reason: "root role not initialized in key database"}
|
||||
|
||||
for _, r := range []data.BaseRole{root, timestamp, snapshot, targets} {
|
||||
rootRoles[r.Name] = &data.RootRole{
|
||||
Threshold: r.Threshold,
|
||||
KeyIDs: r.ListKeyIDs(),
|
||||
}
|
||||
rootRoles[r] = &role.RootRole
|
||||
for _, kid := range role.KeyIDs {
|
||||
// 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
|
||||
for kid, k := range r.Keys {
|
||||
rootKeys[kid] = k
|
||||
}
|
||||
}
|
||||
root, err := data.NewRoot(rootKeys, rootRoles, consistent)
|
||||
r, err := data.NewRoot(rootKeys, rootRoles, consistent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tr.Root = root
|
||||
tr.Root = r
|
||||
return nil
|
||||
}
|
||||
|
||||
// InitTargets initializes an empty targets, and returns the new empty target
|
||||
func (tr *Repo) InitTargets(role string) (*data.SignedTargets, error) {
|
||||
r := data.Role{Name: role}
|
||||
if !r.IsDelegation() && role != data.CanonicalTargetsRole {
|
||||
if !data.IsDelegation(role) && role != data.CanonicalTargetsRole {
|
||||
return nil, data.ErrInvalidRole{
|
||||
Role: 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
|
||||
func (tr *Repo) InitSnapshot() error {
|
||||
if tr.Root == nil {
|
||||
return ErrNotLoaded{role: "root"}
|
||||
return ErrNotLoaded{Role: data.CanonicalRootRole}
|
||||
}
|
||||
root, err := tr.Root.ToSigned()
|
||||
if err != nil {
|
||||
|
@ -379,7 +486,7 @@ func (tr *Repo) InitSnapshot() error {
|
|||
}
|
||||
|
||||
if _, ok := tr.Targets[data.CanonicalTargetsRole]; !ok {
|
||||
return ErrNotLoaded{role: "targets"}
|
||||
return ErrNotLoaded{Role: data.CanonicalTargetsRole}
|
||||
}
|
||||
targets, err := tr.Targets[data.CanonicalTargetsRole].ToSigned()
|
||||
if err != nil {
|
||||
|
@ -408,31 +515,8 @@ func (tr *Repo) InitTimestamp() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// SetRoot parses the Signed object into a SignedRoot object, sets
|
||||
// the keys and roles in the KeyDB, and sets the Repo.Root field
|
||||
// to the SignedRoot object.
|
||||
// SetRoot sets the Repo.Root field to the SignedRoot object.
|
||||
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
|
||||
return nil
|
||||
}
|
||||
|
@ -451,16 +535,9 @@ func (tr *Repo) SetSnapshot(s *data.SignedSnapshot) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// SetTargets parses the Signed object into a SignedTargets object,
|
||||
// reads the delegated roles and keys into the KeyDB, and sets the
|
||||
// SignedTargets object agaist the role in the Repo.Targets map.
|
||||
// SetTargets sets the SignedTargets object agaist the role in the
|
||||
// Repo.Targets map.
|
||||
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
|
||||
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
|
||||
// for the target path provided.
|
||||
func (tr Repo) TargetDelegations(role, path, pathHex string) []*data.Role {
|
||||
if pathHex == "" {
|
||||
pathDigest := sha256.Sum256([]byte(path))
|
||||
pathHex = hex.EncodeToString(pathDigest[:])
|
||||
}
|
||||
func (tr Repo) TargetDelegations(role, path string) []*data.Role {
|
||||
var roles []*data.Role
|
||||
if t, ok := tr.Targets[role]; ok {
|
||||
for _, r := range t.Signed.Delegations.Roles {
|
||||
if r.CheckPrefixes(pathHex) || r.CheckPaths(path) {
|
||||
if r.CheckPaths(path) {
|
||||
roles = append(roles, r)
|
||||
}
|
||||
}
|
||||
|
@ -495,50 +568,34 @@ func (tr Repo) TargetDelegations(role, path, pathHex string) []*data.Role {
|
|||
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
|
||||
// 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
|
||||
// case of multiple signers for a role. It returns an error if the role doesn't
|
||||
// exist or if there are no signing keys.
|
||||
func (tr *Repo) VerifyCanSign(roleName string) error {
|
||||
role := tr.keysDB.GetRole(roleName)
|
||||
if role == nil {
|
||||
var (
|
||||
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"}
|
||||
}
|
||||
|
||||
for _, keyID := range role.KeyIDs {
|
||||
k := tr.keysDB.GetKey(keyID)
|
||||
canonicalID, err := utils.CanonicalKeyID(k)
|
||||
for keyID, k := range role.Keys {
|
||||
check := []string{keyID}
|
||||
if err == nil {
|
||||
if canonicalID, err := utils.CanonicalKeyID(k); err == nil {
|
||||
check = append(check, canonicalID)
|
||||
}
|
||||
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
|
||||
// the directed role. If the metadata for the role doesn't exist yet,
|
||||
// AddTargets will create one.
|
||||
func (tr *Repo) AddTargets(role string, targets data.Files) (data.Files, error) {
|
||||
|
||||
err := tr.VerifyCanSign(role)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// check the role's metadata
|
||||
t, ok := tr.Targets[role]
|
||||
// check existence of the role's metadata
|
||||
_, ok := tr.Targets[role]
|
||||
if !ok { // the targetfile may not exist yet - if not, then create it
|
||||
var err error
|
||||
t, err = tr.InitTargets(role)
|
||||
_, err = tr.InitTargets(role)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// VerifyCanSign already makes sure this is not nil
|
||||
r := tr.keysDB.GetRole(role)
|
||||
|
||||
invalid := make(data.Files)
|
||||
for path, target := range targets {
|
||||
pathDigest := sha256.Sum256([]byte(path))
|
||||
pathHex := hex.EncodeToString(pathDigest[:])
|
||||
if role == data.CanonicalTargetsRole || (r.CheckPaths(path) || r.CheckPrefixes(pathHex)) {
|
||||
t.Signed.Targets[path] = target
|
||||
} else {
|
||||
invalid[path] = target
|
||||
addedTargets := make(data.Files)
|
||||
addTargetVisitor := func(targetPath string, targetMeta data.FileMeta) func(*data.SignedTargets, data.DelegationRole) interface{} {
|
||||
return func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} {
|
||||
// We've already validated the role's target path in our walk, so just modify the metadata
|
||||
tgt.Signed.Targets[targetPath] = targetMeta
|
||||
tgt.Dirty = true
|
||||
// Also add to our new addedTargets map to keep track of every target we've added successfully
|
||||
addedTargets[targetPath] = targetMeta
|
||||
return StopWalk{}
|
||||
}
|
||||
}
|
||||
t.Dirty = true
|
||||
if len(invalid) > 0 {
|
||||
return invalid, fmt.Errorf("Could not add all targets")
|
||||
|
||||
// Walk the role tree while validating the target paths, and add all of our 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
|
||||
}
|
||||
|
@ -597,13 +732,23 @@ func (tr *Repo) RemoveTargets(role string, targets ...string) error {
|
|||
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
|
||||
t, ok := tr.Targets[role]
|
||||
_, ok := tr.Targets[role]
|
||||
if ok {
|
||||
for _, path := range targets {
|
||||
delete(t.Signed.Targets, path)
|
||||
tr.WalkTargets("", role, removeTargetVisitor(path))
|
||||
}
|
||||
t.Dirty = true
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -644,12 +789,15 @@ func (tr *Repo) SignRoot(expires time.Time) (*data.Signed, error) {
|
|||
logrus.Debug("signing root...")
|
||||
tr.Root.Signed.Expires = expires
|
||||
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()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signed, err = tr.sign(signed, *root)
|
||||
signed, err = tr.sign(signed, root)
|
||||
if err != nil {
|
||||
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")
|
||||
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 {
|
||||
logrus.Debug("errored signing ", role)
|
||||
return nil, err
|
||||
|
@ -712,8 +874,11 @@ func (tr *Repo) SignSnapshot(expires time.Time) (*data.Signed, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
snapshot := tr.keysDB.GetRole(data.CanonicalSnapshotRole)
|
||||
signed, err = tr.sign(signed, *snapshot)
|
||||
snapshot, err := tr.GetBaseRole(data.CanonicalSnapshotRole)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signed, err = tr.sign(signed, snapshot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -738,8 +903,11 @@ func (tr *Repo) SignTimestamp(expires time.Time) (*data.Signed, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
timestamp := tr.keysDB.GetRole(data.CanonicalTimestampRole)
|
||||
signed, err = tr.sign(signed, *timestamp)
|
||||
timestamp, err := tr.GetBaseRole(data.CanonicalTimestampRole)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signed, err = tr.sign(signed, timestamp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -748,17 +916,10 @@ func (tr *Repo) SignTimestamp(expires time.Time) (*data.Signed, error) {
|
|||
return signed, nil
|
||||
}
|
||||
|
||||
func (tr Repo) sign(signedData *data.Signed, role data.Role) (*data.Signed, error) {
|
||||
ks := make([]data.PublicKey, 0, len(role.KeyIDs))
|
||||
for _, kid := range role.KeyIDs {
|
||||
k := tr.keysDB.GetKey(kid)
|
||||
if k == nil {
|
||||
continue
|
||||
}
|
||||
ks = append(ks, k)
|
||||
}
|
||||
func (tr Repo) sign(signedData *data.Signed, role data.BaseRole) (*data.Signed, error) {
|
||||
ks := role.ListKeys()
|
||||
if len(ks) < 1 {
|
||||
return nil, keys.ErrInvalidKey
|
||||
return nil, signed.ErrNoKeys{}
|
||||
}
|
||||
err := signed.Sign(tr.cryptoService, signedData, ks...)
|
||||
if err != nil {
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"crypto/tls"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
@ -61,6 +62,17 @@ func StrSliceContains(ss []string, s string) bool {
|
|||
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
|
||||
// in a case insensitive manner
|
||||
func StrSliceContainsI(ss []string, s string) bool {
|
||||
|
@ -146,3 +158,14 @@ func FindRoleIndex(rs []*data.Role, name string) int {
|
|||
}
|
||||
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
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче