vendor notary for docker1.11

Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
This commit is contained in:
Riyaz Faizullabhoy 2016-03-17 10:23:18 -07:00
parent 666563b190
commit ab3772f72f
35 changed files with 647 additions and 496 deletions

View file

@ -171,7 +171,7 @@ RUN set -x \
&& rm -rf "$GOPATH" && rm -rf "$GOPATH"
# Install notary server # Install notary server
ENV NOTARY_VERSION v0.2.0 ENV NOTARY_VERSION docker-v1.11-3
RUN set -x \ RUN set -x \
&& export GOPATH="$(mktemp -d)" \ && export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \ && git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \

View file

@ -117,7 +117,7 @@ RUN set -x \
&& rm -rf "$GOPATH" && rm -rf "$GOPATH"
# Install notary server # Install notary server
ENV NOTARY_VERSION v0.2.0 ENV NOTARY_VERSION docker-v1.11-3
RUN set -x \ RUN set -x \
&& export GOPATH="$(mktemp -d)" \ && export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \ && git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \

View file

@ -135,7 +135,7 @@ RUN set -x \
&& rm -rf "$GOPATH" && rm -rf "$GOPATH"
# Install notary server # Install notary server
ENV NOTARY_VERSION v0.2.0 ENV NOTARY_VERSION docker-v1.11-3
RUN set -x \ RUN set -x \
&& export GOPATH="$(mktemp -d)" \ && export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \ && git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \

View file

@ -127,7 +127,7 @@ RUN set -x \
&& rm -rf "$GOPATH" && rm -rf "$GOPATH"
# Install notary and notary-server # Install notary and notary-server
ENV NOTARY_VERSION v0.2.0 ENV NOTARY_VERSION docker-v1.11-3
RUN set -x \ RUN set -x \
&& export GOPATH="$(mktemp -d)" \ && export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \ && git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \

View file

@ -108,7 +108,7 @@ RUN set -x \
&& rm -rf "$GOPATH" && rm -rf "$GOPATH"
# Install notary server # Install notary server
ENV NOTARY_VERSION v0.2.0 ENV NOTARY_VERSION docker-v1.11-3
RUN set -x \ RUN set -x \
&& export GOPATH="$(mktemp -d)" \ && export GOPATH="$(mktemp -d)" \
&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \ && git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \

View file

@ -205,14 +205,14 @@ func (cli *DockerCli) getPassphraseRetriever() passphrase.Retriever {
"root": "root", "root": "root",
"snapshot": "repository", "snapshot": "repository",
"targets": "repository", "targets": "repository",
"targets/releases": "repository", "default": "repository",
} }
baseRetriever := passphrase.PromptRetrieverWithInOut(cli.in, cli.out, aliasMap) baseRetriever := passphrase.PromptRetrieverWithInOut(cli.in, cli.out, aliasMap)
env := map[string]string{ env := map[string]string{
"root": os.Getenv("DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE"), "root": os.Getenv("DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE"),
"snapshot": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"), "snapshot": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
"targets": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"), "targets": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
"targets/releases": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"), "default": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
} }
// Backwards compatibility with old env names. We should remove this in 1.10 // Backwards compatibility with old env names. We should remove this in 1.10
@ -222,11 +222,11 @@ func (cli *DockerCli) getPassphraseRetriever() passphrase.Retriever {
fmt.Fprintf(cli.err, "[DEPRECATED] The environment variable DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE has been deprecated and will be removed in v1.10. Please use DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE\n") fmt.Fprintf(cli.err, "[DEPRECATED] The environment variable DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE has been deprecated and will be removed in v1.10. Please use DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE\n")
} }
} }
if env["snapshot"] == "" || env["targets"] == "" || env["targets/releases"] == "" { if env["snapshot"] == "" || env["targets"] == "" || env["default"] == "" {
if passphrase := os.Getenv("DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE"); passphrase != "" { if passphrase := os.Getenv("DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE"); passphrase != "" {
env["snapshot"] = passphrase env["snapshot"] = passphrase
env["targets"] = passphrase env["targets"] = passphrase
env["targets/releases"] = passphrase env["default"] = passphrase
fmt.Fprintf(cli.err, "[DEPRECATED] The environment variable DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE has been deprecated and will be removed in v1.10. Please use DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE\n") fmt.Fprintf(cli.err, "[DEPRECATED] The environment variable DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE has been deprecated and will be removed in v1.10. Please use DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE\n")
} }
} }
@ -235,6 +235,10 @@ func (cli *DockerCli) getPassphraseRetriever() passphrase.Retriever {
if v := env[alias]; v != "" { if v := env[alias]; v != "" {
return v, numAttempts > 1, nil return v, numAttempts > 1, nil
} }
// For non-root roles, we can also try the "default" alias if it is specified
if v := env["default"]; v != "" && alias != data.CanonicalRootRole {
return v, numAttempts > 1, nil
}
return baseRetriever(keyName, alias, createNew, numAttempts) return baseRetriever(keyName, alias, createNew, numAttempts)
} }
} }
@ -473,7 +477,7 @@ func (cli *DockerCli) trustedPush(repoInfo *registry.RepositoryInfo, tag string,
sort.Strings(keys) sort.Strings(keys)
rootKeyID = keys[0] rootKeyID = keys[0]
} else { } else {
rootPublicKey, err := repo.CryptoService.Create(data.CanonicalRootRole, data.ECDSAKey) rootPublicKey, err := repo.CryptoService.Create(data.CanonicalRootRole, "", data.ECDSAKey)
if err != nil { if err != nil {
return err return err
} }

View file

@ -52,7 +52,7 @@ clone git github.com/docker/distribution d06d6d3b093302c02a93153ac7b06ebc0ffd179
clone git github.com/vbatts/tar-split v0.9.11 clone git github.com/vbatts/tar-split v0.9.11
# get desired notary commit, might also need to be updated in Dockerfile # get desired notary commit, might also need to be updated in Dockerfile
clone git github.com/docker/notary v0.2.0 clone git github.com/docker/notary docker-v1.11-3
clone git google.golang.org/grpc a22b6611561e9f0a3e0919690dd2caf48f14c517 https://github.com/grpc/grpc-go.git clone git google.golang.org/grpc a22b6611561e9f0a3e0919690dd2caf48f14c517 https://github.com/grpc/grpc-go.git
clone git github.com/miekg/pkcs11 df8ae6ca730422dba20c768ff38ef7d79077a59f clone git github.com/miekg/pkcs11 df8ae6ca730422dba20c768ff38ef7d79077a59f

View file

@ -232,6 +232,7 @@ func notaryClientEnv(cmd *exec.Cmd) {
fmt.Sprintf("NOTARY_ROOT_PASSPHRASE=%s", pwd), fmt.Sprintf("NOTARY_ROOT_PASSPHRASE=%s", pwd),
fmt.Sprintf("NOTARY_TARGETS_PASSPHRASE=%s", pwd), fmt.Sprintf("NOTARY_TARGETS_PASSPHRASE=%s", pwd),
fmt.Sprintf("NOTARY_SNAPSHOT_PASSPHRASE=%s", pwd), fmt.Sprintf("NOTARY_SNAPSHOT_PASSPHRASE=%s", pwd),
fmt.Sprintf("NOTARY_DELEGATION_PASSPHRASE=%s", pwd),
} }
cmd.Env = append(os.Environ(), env...) cmd.Env = append(os.Environ(), env...)
} }

View file

@ -0,0 +1,26 @@
# Changelog
## [v0.2](https://github.com/docker/notary/releases/tag/v0.2.0) 2/24/2016
+ Add support for delegation roles in `notary` server and client
+ Add `notary CLI` commands for managing delegation roles: `notary delegation`
+ `add`, `list` and `remove` subcommands
+ Enhance `notary CLI` commands for adding targets to delegation roles
+ `notary add --roles` and `notary remove --roles` to manipulate targets for delegations
+ Support for rotating the snapshot key to one managed by the `notary` server
+ Add consistent download functionality to download metadata and content by checksum
+ Update `docker-compose` configuration to use official mariadb image
+ deprecate `notarymysql`
+ default to using a volume for `data` directory
+ use separate databases for `notary-server` and `notary-signer` with separate users
+ Add `notary CLI` command for changing private key passphrases: `notary key passwd`
+ Enhance `notary CLI` commands for importing and exporting keys
+ Change default `notary CLI` log level to fatal, introduce new verbose (error-level) and debug-level settings
+ Store roles as PEM headers in private keys, incompatible with previous notary v0.1 key format
+ No longer store keys as `<KEY_ID>_role.key`, instead store as `<KEY_ID>.key`; new private keys from new notary clients will crash old notary clients
+ Support logging as JSON format on server and signer
+ Support mutual TLS between notary client and notary server
## [v0.1](https://github.com/docker/notary/releases/tag/v0.1) 11/15/2015
+ Initial non-alpha `notary` version
+ Implement TUF (the update framework) with support for root, targets, snapshot, and timestamp roles
+ Add PKCS11 interface to store and sign with keys in HSMs (i.e. Yubikey)

View file

@ -1,4 +1,4 @@
FROM golang:1.5.1 FROM golang:1.6.0
RUN apt-get update && apt-get install -y \ RUN apt-get update && apt-get install -y \
libltdl-dev \ libltdl-dev \
@ -12,6 +12,4 @@ RUN go get golang.org/x/tools/cmd/vet \
COPY . /go/src/github.com/docker/notary COPY . /go/src/github.com/docker/notary
ENV GOPATH /go/src/github.com/docker/notary/Godeps/_workspace:$GOPATH
WORKDIR /go/src/github.com/docker/notary WORKDIR /go/src/github.com/docker/notary

View file

@ -20,7 +20,7 @@ GO_EXC = go
NOTARYDIR := /go/src/github.com/docker/notary NOTARYDIR := /go/src/github.com/docker/notary
# check to be sure pkcs11 lib is always imported with a build tag # check to be sure pkcs11 lib is always imported with a build tag
GO_LIST_PKCS11 := $(shell go list -e -f '{{join .Deps "\n"}}' ./... | xargs go list -e -f '{{if not .Standard}}{{.ImportPath}}{{end}}' | grep -q pkcs11) GO_LIST_PKCS11 := $(shell go list -e -f '{{join .Deps "\n"}}' ./... | grep -v /vendor/ | xargs go list -e -f '{{if not .Standard}}{{.ImportPath}}{{end}}' | grep -q pkcs11)
ifeq ($(GO_LIST_PKCS11),) ifeq ($(GO_LIST_PKCS11),)
$(info pkcs11 import was not found anywhere without a build tag, yay) $(info pkcs11 import was not found anywhere without a build tag, yay)
else else
@ -34,7 +34,7 @@ _space := $(empty) $(empty)
COVERDIR=.cover COVERDIR=.cover
COVERPROFILE?=$(COVERDIR)/cover.out COVERPROFILE?=$(COVERDIR)/cover.out
COVERMODE=count COVERMODE=count
PKGS ?= $(shell go list ./... | tr '\n' ' ') PKGS ?= $(shell go list ./... | grep -v /vendor/ | tr '\n' ' ')
GO_VERSION = $(shell go version | awk '{print $$3}') GO_VERSION = $(shell go version | awk '{print $$3}')
@ -82,19 +82,19 @@ endif
vet: vet:
@echo "+ $@" @echo "+ $@"
ifeq ($(shell uname -s), Darwin) ifeq ($(shell uname -s), Darwin)
@test -z "$(shell find . -iname *test*.go | grep -v _test.go | grep -v Godeps | xargs echo "This file should end with '_test':" | tee /dev/stderr)" @test -z "$(shell find . -iname *test*.go | grep -v _test.go | grep -v vendor | xargs echo "This file should end with '_test':" | tee /dev/stderr)"
else else
@test -z "$(shell find . -iname *test*.go | grep -v _test.go | grep -v Godeps | xargs -r echo "This file should end with '_test':" | tee /dev/stderr)" @test -z "$(shell find . -iname *test*.go | grep -v _test.go | grep -v vendor | xargs -r echo "This file should end with '_test':" | tee /dev/stderr)"
endif endif
@test -z "$$(go tool vet -printf=false . 2>&1 | grep -v Godeps/_workspace/src/ | tee /dev/stderr)" @test -z "$$(go tool vet -printf=false . 2>&1 | grep -v vendor/ | tee /dev/stderr)"
fmt: fmt:
@echo "+ $@" @echo "+ $@"
@test -z "$$(gofmt -s -l .| grep -v .pb. | grep -v Godeps/_workspace/src/ | tee /dev/stderr)" @test -z "$$(gofmt -s -l .| grep -v .pb. | grep -v vendor/ | tee /dev/stderr)"
lint: lint:
@echo "+ $@" @echo "+ $@"
@test -z "$$(golint ./... | grep -v .pb. | grep -v Godeps/_workspace/src/ | tee /dev/stderr)" @test -z "$$(golint ./... | grep -v .pb. | grep -v vendor/ | tee /dev/stderr)"
# Requires that the following: # Requires that the following:
# go get -u github.com/client9/misspell/cmd/misspell # go get -u github.com/client9/misspell/cmd/misspell
@ -104,11 +104,11 @@ lint:
# misspell target, don't include Godeps, binaries, python tests, or git files # misspell target, don't include Godeps, binaries, python tests, or git files
misspell: misspell:
@echo "+ $@" @echo "+ $@"
@test -z "$$(find . -name '*' | grep -v Godeps/_workspace/src/ | grep -v bin/ | grep -v misc/ | grep -v .git/ | xargs misspell | tee /dev/stderr)" @test -z "$$(find . -name '*' | grep -v vendor/ | grep -v bin/ | grep -v misc/ | grep -v .git/ | xargs misspell | tee /dev/stderr)"
build: build:
@echo "+ $@" @echo "+ $@"
@go build -tags "${NOTARY_BUILDTAGS}" -v ${GO_LDFLAGS} ./... @go build -tags "${NOTARY_BUILDTAGS}" -v ${GO_LDFLAGS} $(PKGS)
# When running `go test ./...`, it runs all the suites in parallel, which causes # When running `go test ./...`, it runs all the suites in parallel, which causes
# problems when running with a yubikey # problems when running with a yubikey
@ -117,14 +117,14 @@ test:
@echo Note: when testing with a yubikey plugged in, make sure to include 'TESTOPTS="-p 1"' @echo Note: when testing with a yubikey plugged in, make sure to include 'TESTOPTS="-p 1"'
@echo "+ $@ $(TESTOPTS)" @echo "+ $@ $(TESTOPTS)"
@echo @echo
go test -tags "${NOTARY_BUILDTAGS}" $(TESTOPTS) ./... go test -tags "${NOTARY_BUILDTAGS}" $(TESTOPTS) $(PKGS)
test-full: TESTOPTS = test-full: TESTOPTS =
test-full: vet lint test-full: vet lint
@echo Note: when testing with a yubikey plugged in, make sure to include 'TESTOPTS="-p 1"' @echo Note: when testing with a yubikey plugged in, make sure to include 'TESTOPTS="-p 1"'
@echo "+ $@" @echo "+ $@"
@echo @echo
go test -tags "${NOTARY_BUILDTAGS}" $(TESTOPTS) -v ./... go test -tags "${NOTARY_BUILDTAGS}" $(TESTOPTS) -v $(PKGS)
protos: protos:
@protoc --go_out=plugins=grpc:. proto/*.proto @protoc --go_out=plugins=grpc:. proto/*.proto

View file

@ -39,14 +39,25 @@ func (err ErrRepoNotInitialized) Error() string {
} }
// ErrInvalidRemoteRole is returned when the server is requested to manage // ErrInvalidRemoteRole is returned when the server is requested to manage
// an unsupported key type // a key type that is not permitted
type ErrInvalidRemoteRole struct { type ErrInvalidRemoteRole struct {
Role string Role string
} }
func (err ErrInvalidRemoteRole) Error() string { func (err ErrInvalidRemoteRole) Error() string {
return fmt.Sprintf( return fmt.Sprintf(
"notary does not support the server managing the %s key", err.Role) "notary does not permit the server managing the %s key", err.Role)
}
// ErrInvalidLocalRole is returned when the client wants to manage
// a key type that is not permitted
type ErrInvalidLocalRole struct {
Role string
}
func (err ErrInvalidLocalRole) Error() string {
return fmt.Sprintf(
"notary does not permit the client managing the %s key", err.Role)
} }
// ErrRepositoryNotExist is returned when an action is taken on a remote // ErrRepositoryNotExist is returned when an action is taken on a remote
@ -93,7 +104,7 @@ func repositoryFromKeystores(baseDir, gun, baseURL string, rt http.RoundTripper,
return nil, err return nil, err
} }
cryptoService := cryptoservice.NewCryptoService(gun, keyStores...) cryptoService := cryptoservice.NewCryptoService(keyStores...)
nRepo := &NotaryRepository{ nRepo := &NotaryRepository{
gun: gun, gun: gun,
@ -140,7 +151,7 @@ func NewTarget(targetName string, targetPath string) (*Target, error) {
return nil, err return nil, err
} }
meta, err := data.NewFileMeta(bytes.NewBuffer(b)) meta, err := data.NewFileMeta(bytes.NewBuffer(b), data.NotaryDefaultHashes...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -223,7 +234,7 @@ func (r *NotaryRepository) Initialize(rootKeyID string, serverManagedRoles ...st
// make unnecessary network calls // make unnecessary network calls
for _, role := range locallyManagedKeys { for _, role := range locallyManagedKeys {
// This is currently hardcoding the keys to ECDSA. // This is currently hardcoding the keys to ECDSA.
key, err := r.CryptoService.Create(role, data.ECDSAKey) key, err := r.CryptoService.Create(role, r.gun, data.ECDSAKey)
if err != nil { if err != nil {
return err return err
} }
@ -520,6 +531,25 @@ func (r *NotaryRepository) ListRoles() ([]RoleWithSignatures, error) {
// Publish pushes the local changes in signed material to the remote notary-server // Publish pushes the local changes in signed material to the remote notary-server
// Conceptually it performs an operation similar to a `git rebase` // Conceptually it performs an operation similar to a `git rebase`
func (r *NotaryRepository) Publish() error { func (r *NotaryRepository) Publish() error {
cl, err := r.GetChangelist()
if err != nil {
return err
}
if err = r.publish(cl); err != nil {
return err
}
if err = cl.Clear(""); err != nil {
// This is not a critical problem when only a single host is pushing
// but will cause weird behaviour if changelist cleanup is failing
// and there are multiple hosts writing to the repo.
logrus.Warn("Unable to clear changelist. You may want to manually delete the folder ", filepath.Join(r.tufRepoPath, "changelist"))
}
return nil
}
// publish pushes the changes in the given changelist to the remote notary-server
// Conceptually it performs an operation similar to a `git rebase`
func (r *NotaryRepository) publish(cl changelist.Changelist) error {
var initialPublish bool var initialPublish bool
// update first before publishing // update first before publishing
_, err := r.Update(true) _, err := r.Update(true)
@ -543,15 +573,10 @@ func (r *NotaryRepository) Publish() error {
initialPublish = true initialPublish = true
} else { } else {
// We could not update, so we cannot publish. // We could not update, so we cannot publish.
logrus.Error("Could not publish Repository: ", err.Error()) logrus.Error("Could not publish Repository since we could not update: ", err.Error())
return err return err
} }
} }
cl, err := r.GetChangelist()
if err != nil {
return err
}
// apply the changelist to the repo // apply the changelist to the repo
err = applyChangelist(r.tufRepo, cl) err = applyChangelist(r.tufRepo, cl)
if err != nil { if err != nil {
@ -622,25 +647,14 @@ func (r *NotaryRepository) Publish() error {
return err return err
} }
err = remote.SetMultiMeta(updatedFiles) return remote.SetMultiMeta(updatedFiles)
if err != nil {
return err
}
err = cl.Clear("")
if err != nil {
// This is not a critical problem when only a single host is pushing
// but will cause weird behaviour if changelist cleanup is failing
// and there are multiple hosts writing to the repo.
logrus.Warn("Unable to clear changelist. You may want to manually delete the folder ", filepath.Join(r.tufRepoPath, "changelist"))
}
return nil
} }
// bootstrapRepo loads the repository from the local file system. This attempts // bootstrapRepo loads the repository from the local file system. This attempts
// to load metadata for all roles. Since server snapshots are supported, // to load metadata for all roles. Since server snapshots are supported,
// if the snapshot metadata fails to load, that's ok. // if the snapshot metadata fails to load, that's ok.
// This can also be unified with some cache reading tools from tuf/client. // This can also be unified with some cache reading tools from tuf/client.
// This assumes that bootstrapRepo is only used by Publish() // This assumes that bootstrapRepo is only used by Publish() or RotateKey()
func (r *NotaryRepository) bootstrapRepo() error { func (r *NotaryRepository) bootstrapRepo() error {
tufRepo := tuf.NewRepo(r.CryptoService) tufRepo := tuf.NewRepo(r.CryptoService)
@ -858,37 +872,53 @@ func (r *NotaryRepository) validateRoot(rootJSON []byte) (*data.SignedRoot, erro
// creates and adds one new key or delegates managing the key to the server. // creates and adds one new key or delegates managing the key to the server.
// These changes are staged in a changelist until publish is called. // These changes are staged in a changelist until publish is called.
func (r *NotaryRepository) RotateKey(role string, serverManagesKey bool) error { func (r *NotaryRepository) RotateKey(role string, serverManagesKey bool) error {
if role == data.CanonicalRootRole || role == data.CanonicalTimestampRole { switch {
return fmt.Errorf( // We currently support locally or remotely managing snapshot keys...
"notary does not currently support rotating the %s key", role) case role == data.CanonicalSnapshotRole:
} break
if serverManagesKey && role == data.CanonicalTargetsRole {
// locally managing targets keys only
case role == data.CanonicalTargetsRole && !serverManagesKey:
break
case role == data.CanonicalTargetsRole && serverManagesKey:
return ErrInvalidRemoteRole{Role: data.CanonicalTargetsRole} return ErrInvalidRemoteRole{Role: data.CanonicalTargetsRole}
// and remotely managing timestamp keys only
case role == data.CanonicalTimestampRole && serverManagesKey:
break
case role == data.CanonicalTimestampRole && !serverManagesKey:
return ErrInvalidLocalRole{Role: data.CanonicalTimestampRole}
default:
return fmt.Errorf("notary does not currently permit rotating the %s key", role)
} }
var ( var (
pubKey data.PublicKey pubKey data.PublicKey
err error err error
errFmtMsg string
) )
if serverManagesKey { switch serverManagesKey {
case true:
pubKey, err = getRemoteKey(r.baseURL, r.gun, role, r.roundTrip) pubKey, err = getRemoteKey(r.baseURL, r.gun, role, r.roundTrip)
} else { errFmtMsg = "unable to rotate remote key: %s"
pubKey, err = r.CryptoService.Create(role, data.ECDSAKey) default:
} pubKey, err = r.CryptoService.Create(role, r.gun, data.ECDSAKey)
if err != nil { errFmtMsg = "unable to generate key: %s"
return err
} }
return r.rootFileKeyChange(role, changelist.ActionCreate, pubKey) if err != nil {
return fmt.Errorf(errFmtMsg, err)
}
cl := changelist.NewMemChangelist()
if err := r.rootFileKeyChange(cl, role, changelist.ActionCreate, pubKey); err != nil {
return err
}
return r.publish(cl)
} }
func (r *NotaryRepository) rootFileKeyChange(role, action string, key data.PublicKey) error { func (r *NotaryRepository) rootFileKeyChange(cl changelist.Changelist, role, action string, key data.PublicKey) error {
cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
if err != nil {
return err
}
defer cl.Close()
kl := make(data.KeyList, 0, 1) kl := make(data.KeyList, 0, 1)
kl = append(kl, key) kl = append(kl, key)
meta := changelist.TufRootData{ meta := changelist.TufRootData{
@ -907,11 +937,7 @@ func (r *NotaryRepository) rootFileKeyChange(role, action string, key data.Publi
role, role,
metaJSON, metaJSON,
) )
err = cl.Add(c) return cl.Add(c)
if err != nil {
return err
}
return nil
} }
// DeleteTrustData removes the trust data stored for this repo in the TUF cache and certificate store on the client side // DeleteTrustData removes the trust data stored for this repo in the TUF cache and certificate store on the client side

View file

@ -4,7 +4,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"path"
"strings" "strings"
"time" "time"
@ -130,22 +129,6 @@ func changeTargetsDelegation(repo *tuf.Repo, c changelist.Change) error {
} }
// applies a function repeatedly, falling back on the parent role, until it no
// longer can
func doWithRoleFallback(role string, doFunc func(string) error) error {
for role == data.CanonicalTargetsRole || data.IsDelegation(role) {
err := doFunc(role)
if err == nil {
return nil
}
if _, ok := err.(data.ErrInvalidRole); !ok {
return err
}
role = path.Dir(role)
}
return data.ErrInvalidRole{Role: role}
}
func changeTargetMeta(repo *tuf.Repo, c changelist.Change) error { func changeTargetMeta(repo *tuf.Repo, c changelist.Change) error {
var err error var err error
switch c.Action() { switch c.Action() {
@ -158,21 +141,16 @@ func changeTargetMeta(repo *tuf.Repo, c changelist.Change) error {
} }
files := data.Files{c.Path(): *meta} files := data.Files{c.Path(): *meta}
err = doWithRoleFallback(c.Scope(), func(role string) error { // Attempt to add the target to this role
_, e := repo.AddTargets(role, files) if _, err = repo.AddTargets(c.Scope(), files); err != nil {
return e
})
if err != nil {
logrus.Errorf("couldn't add target to %s: %s", c.Scope(), err.Error()) logrus.Errorf("couldn't add target to %s: %s", c.Scope(), err.Error())
} }
case changelist.ActionDelete: case changelist.ActionDelete:
logrus.Debug("changelist remove: ", c.Path()) logrus.Debug("changelist remove: ", c.Path())
err = doWithRoleFallback(c.Scope(), func(role string) error { // Attempt to remove the target from this role
return repo.RemoveTargets(role, c.Path()) if err = repo.RemoveTargets(c.Scope(), c.Path()); err != nil {
})
if err != nil {
logrus.Errorf("couldn't remove target from %s: %s", c.Scope(), err.Error()) logrus.Errorf("couldn't remove target from %s: %s", c.Scope(), err.Error())
} }

View file

@ -20,6 +20,10 @@ const (
PubCertPerms = 0755 PubCertPerms = 0755
// Sha256HexSize is how big a Sha256 hex is in number of characters // Sha256HexSize is how big a Sha256 hex is in number of characters
Sha256HexSize = 64 Sha256HexSize = 64
// SHA256 is the name of SHA256 hash algorithm
SHA256 = "sha256"
// SHA512 is the name of SHA512 hash algorithm
SHA512 = "sha512"
// TrustedCertsDir is the directory, under the notary repo base directory, where trusted certs are stored // TrustedCertsDir is the directory, under the notary repo base directory, where trusted certs are stored
TrustedCertsDir = "trusted_certificates" TrustedCertsDir = "trusted_certificates"
// PrivDir is the directory, under the notary repo base directory, where private keys are stored // PrivDir is the directory, under the notary repo base directory, where private keys are stored
@ -38,6 +42,13 @@ const (
NotaryTargetsExpiry = 3 * Year NotaryTargetsExpiry = 3 * Year
NotarySnapshotExpiry = 3 * Year NotarySnapshotExpiry = 3 * Year
NotaryTimestampExpiry = 14 * Day NotaryTimestampExpiry = 14 * Day
ConsistentMetadataCacheMaxAge = 30 * Day
CurrentMetadataCacheMaxAge = 5 * time.Minute
// CacheMaxAgeLimit is the generally recommended maximum age for Cache-Control headers
// (one year, in seconds, since one year is forever in terms of internet
// content)
CacheMaxAgeLimit = 1 * Year
) )
// NotaryDefaultExpiries is the construct used to configure the default expiry times of // NotaryDefaultExpiries is the construct used to configure the default expiry times of

View file

@ -5,6 +5,6 @@
# subpackage's dependencies within the containing package, as well as the # subpackage's dependencies within the containing package, as well as the
# subpackage itself. # subpackage itself.
DEPENDENCIES="$(go list -f $'{{range $f := .Deps}}{{$f}}\n{{end}}' ${1} | grep ${2})" DEPENDENCIES="$(go list -f $'{{range $f := .Deps}}{{$f}}\n{{end}}' ${1} | grep ${2} | grep -v ${2}/vendor)"
echo "${1} ${DEPENDENCIES}" | xargs echo -n | tr ' ' ',' echo "${1} ${DEPENDENCIES}" | xargs echo -n | tr ' ' ','

View file

@ -3,7 +3,6 @@ package cryptoservice
import ( import (
"crypto/rand" "crypto/rand"
"fmt" "fmt"
"path/filepath"
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/docker/notary/trustmanager" "github.com/docker/notary/trustmanager"
@ -17,17 +16,16 @@ const (
// CryptoService implements Sign and Create, holding a specific GUN and keystore to // CryptoService implements Sign and Create, holding a specific GUN and keystore to
// operate on // operate on
type CryptoService struct { type CryptoService struct {
gun string
keyStores []trustmanager.KeyStore keyStores []trustmanager.KeyStore
} }
// NewCryptoService returns an instance of CryptoService // NewCryptoService returns an instance of CryptoService
func NewCryptoService(gun string, keyStores ...trustmanager.KeyStore) *CryptoService { func NewCryptoService(keyStores ...trustmanager.KeyStore) *CryptoService {
return &CryptoService{gun: gun, keyStores: keyStores} return &CryptoService{keyStores: keyStores}
} }
// Create is used to generate keys for targets, snapshots and timestamps // Create is used to generate keys for targets, snapshots and timestamps
func (cs *CryptoService) Create(role, algorithm string) (data.PublicKey, error) { func (cs *CryptoService) Create(role, gun, algorithm string) (data.PublicKey, error) {
var privKey data.PrivateKey var privKey data.PrivateKey
var err error var err error
@ -52,16 +50,9 @@ func (cs *CryptoService) Create(role, algorithm string) (data.PublicKey, error)
} }
logrus.Debugf("generated new %s key for role: %s and keyID: %s", algorithm, role, privKey.ID()) logrus.Debugf("generated new %s key for role: %s and keyID: %s", algorithm, role, privKey.ID())
// Store the private key into our keystore with the name being: /GUN/ID.key with an alias of role // Store the private key into our keystore
var keyPath string
if role == data.CanonicalRootRole {
keyPath = privKey.ID()
} else {
keyPath = filepath.Join(cs.gun, privKey.ID())
}
for _, ks := range cs.keyStores { for _, ks := range cs.keyStores {
err = ks.AddKey(keyPath, role, privKey) err = ks.AddKey(trustmanager.KeyInfo{Role: role, Gun: gun}, privKey)
if err == nil { if err == nil {
return data.PublicKeyFromPrivate(privKey), nil return data.PublicKeyFromPrivate(privKey), nil
} }
@ -74,15 +65,9 @@ func (cs *CryptoService) Create(role, algorithm string) (data.PublicKey, error)
} }
// GetPrivateKey returns a private key and role if present by ID. // GetPrivateKey returns a private key and role if present by ID.
// It tries to get the key first without a GUN (in which case it's a root key).
// If that fails, try to get the key with the GUN (non-root key).
// If that fails, then we don't have the key.
func (cs *CryptoService) GetPrivateKey(keyID string) (k data.PrivateKey, role string, err error) { func (cs *CryptoService) GetPrivateKey(keyID string) (k data.PrivateKey, role string, err error) {
keyPaths := []string{keyID, filepath.Join(cs.gun, keyID)}
for _, ks := range cs.keyStores { for _, ks := range cs.keyStores {
for _, keyPath := range keyPaths { if k, role, err = ks.GetKey(keyID); err == nil {
k, role, err = ks.GetKey(keyPath)
if err == nil {
return return
} }
switch err.(type) { switch err.(type) {
@ -92,7 +77,6 @@ func (cs *CryptoService) GetPrivateKey(keyID string) (k data.PrivateKey, role st
continue continue
} }
} }
}
return // returns whatever the final values were return // returns whatever the final values were
} }
@ -105,12 +89,42 @@ func (cs *CryptoService) GetKey(keyID string) data.PublicKey {
return data.PublicKeyFromPrivate(privKey) return data.PublicKeyFromPrivate(privKey)
} }
// GetKeyInfo returns role and GUN info of a key by ID
func (cs *CryptoService) GetKeyInfo(keyID string) (trustmanager.KeyInfo, error) {
for _, store := range cs.keyStores {
if info, err := store.GetKeyInfo(keyID); err == nil {
return info, nil
}
}
return trustmanager.KeyInfo{}, fmt.Errorf("Could not find info for keyID %s", keyID)
}
// RemoveKey deletes a key by ID // RemoveKey deletes a key by ID
func (cs *CryptoService) RemoveKey(keyID string) (err error) { func (cs *CryptoService) RemoveKey(keyID string) (err error) {
keyPaths := []string{keyID, filepath.Join(cs.gun, keyID)}
for _, ks := range cs.keyStores { for _, ks := range cs.keyStores {
for _, keyPath := range keyPaths { ks.RemoveKey(keyID)
ks.RemoveKey(keyPath) }
return // returns whatever the final values were
}
// AddKey adds a private key to a specified role.
// The GUN is inferred from the cryptoservice itself for non-root roles
func (cs *CryptoService) AddKey(role, gun string, key data.PrivateKey) (err error) {
// First check if this key already exists in any of our keystores
for _, ks := range cs.keyStores {
if keyInfo, err := ks.GetKeyInfo(key.ID()); err == nil {
if keyInfo.Role != role {
return fmt.Errorf("key with same ID already exists for role: %s", keyInfo.Role)
}
logrus.Debugf("key with same ID %s and role %s already exists", key.ID(), keyInfo.Role)
return nil
}
}
// If the key didn't exist in any of our keystores, add and return on the first successful keystore
for _, ks := range cs.keyStores {
// Try to add to this keystore, return if successful
if err = ks.AddKey(trustmanager.KeyInfo{Role: role, Gun: gun}, key); err == nil {
return nil
} }
} }
return // returns whatever the final values were return // returns whatever the final values were
@ -121,7 +135,7 @@ func (cs *CryptoService) ListKeys(role string) []string {
var res []string var res []string
for _, ks := range cs.keyStores { for _, ks := range cs.keyStores {
for k, r := range ks.ListKeys() { for k, r := range ks.ListKeys() {
if r == role { if r.Role == role {
res = append(res, k) res = append(res, k)
} }
} }
@ -134,7 +148,7 @@ func (cs *CryptoService) ListAllKeys() map[string]string {
res := make(map[string]string) res := make(map[string]string)
for _, ks := range cs.keyStores { for _, ks := range cs.keyStores {
for k, r := range ks.ListKeys() { for k, r := range ks.ListKeys() {
res[k] = r // keys are content addressed so don't care about overwrites res[k] = r.Role // keys are content addressed so don't care about overwrites
} }
} }
return res return res

View file

@ -11,10 +11,8 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/docker/notary"
"github.com/docker/notary/passphrase" "github.com/docker/notary/passphrase"
"github.com/docker/notary/trustmanager" "github.com/docker/notary/trustmanager"
"github.com/docker/notary/tuf/data"
) )
const zipMadeByUNIX = 3 << 8 const zipMadeByUNIX = 3 << 8
@ -41,9 +39,6 @@ func (cs *CryptoService) ExportKey(dest io.Writer, keyID, role string) error {
err error err error
) )
if role != data.CanonicalRootRole {
keyID = filepath.Join(cs.gun, keyID)
}
for _, ks := range cs.keyStores { for _, ks := range cs.keyStores {
pemBytes, err = ks.ExportKey(keyID) pemBytes, err = ks.ExportKey(keyID)
if err != nil { if err != nil {
@ -67,7 +62,12 @@ func (cs *CryptoService) ExportKey(dest io.Writer, keyID, role string) error {
// ExportKeyReencrypt exports the specified private key to an io.Writer in // ExportKeyReencrypt exports the specified private key to an io.Writer in
// PEM format. The key is reencrypted with a new passphrase. // PEM format. The key is reencrypted with a new passphrase.
func (cs *CryptoService) ExportKeyReencrypt(dest io.Writer, keyID string, newPassphraseRetriever passphrase.Retriever) error { func (cs *CryptoService) ExportKeyReencrypt(dest io.Writer, keyID string, newPassphraseRetriever passphrase.Retriever) error {
privateKey, role, err := cs.GetPrivateKey(keyID) privateKey, _, err := cs.GetPrivateKey(keyID)
if err != nil {
return err
}
keyInfo, err := cs.GetKeyInfo(keyID)
if err != nil { if err != nil {
return err return err
} }
@ -81,7 +81,7 @@ func (cs *CryptoService) ExportKeyReencrypt(dest io.Writer, keyID string, newPas
return err return err
} }
err = tempKeyStore.AddKey(keyID, role, privateKey) err = tempKeyStore.AddKey(keyInfo, privateKey)
if err != nil { if err != nil {
return err return err
} }
@ -100,56 +100,6 @@ func (cs *CryptoService) ExportKeyReencrypt(dest io.Writer, keyID string, newPas
return nil return nil
} }
// ImportRootKey imports a root in PEM format key from an io.Reader
// It prompts for the key's passphrase to verify the data and to determine
// the key ID.
func (cs *CryptoService) ImportRootKey(source io.Reader) error {
pemBytes, err := ioutil.ReadAll(source)
if err != nil {
return err
}
return cs.ImportRoleKey(pemBytes, data.CanonicalRootRole, nil)
}
// 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, alias); err == nil {
return nil //bail on the first keystore we import to
}
}
return err
}
// ExportAllKeys exports all keys to an io.Writer in zip format. // ExportAllKeys exports all keys to an io.Writer in zip format.
// newPassphraseRetriever will be used to obtain passphrases to use to encrypt the existing keys. // newPassphraseRetriever will be used to obtain passphrases to use to encrypt the existing keys.
func (cs *CryptoService) ExportAllKeys(dest io.Writer, newPassphraseRetriever passphrase.Retriever) error { func (cs *CryptoService) ExportAllKeys(dest io.Writer, newPassphraseRetriever passphrase.Retriever) error {
@ -182,7 +132,7 @@ func (cs *CryptoService) ExportAllKeys(dest io.Writer, newPassphraseRetriever pa
// ImportKeysZip imports keys from a zip file provided as an zip.Reader. The // ImportKeysZip imports keys from a zip file provided as an zip.Reader. The
// keys in the root_keys directory are left encrypted, but the other keys are // keys in the root_keys directory are left encrypted, but the other keys are
// decrypted with the specified passphrase. // decrypted with the specified passphrase.
func (cs *CryptoService) ImportKeysZip(zipReader zip.Reader) error { func (cs *CryptoService) ImportKeysZip(zipReader zip.Reader, retriever passphrase.Retriever) error {
// Temporarily store the keys in maps, so we can bail early if there's // Temporarily store the keys in maps, so we can bail early if there's
// an error (for example, wrong passphrase), without leaving the key // an error (for example, wrong passphrase), without leaving the key
// store in an inconsistent state // store in an inconsistent state
@ -191,7 +141,6 @@ func (cs *CryptoService) ImportKeysZip(zipReader zip.Reader) error {
// Iterate through the files in the archive. Don't add the keys // Iterate through the files in the archive. Don't add the keys
for _, f := range zipReader.File { for _, f := range zipReader.File {
fNameTrimmed := strings.TrimSuffix(f.Name, filepath.Ext(f.Name)) fNameTrimmed := strings.TrimSuffix(f.Name, filepath.Ext(f.Name))
rc, err := f.Open() rc, err := f.Open()
if err != nil { if err != nil {
return err return err
@ -206,7 +155,7 @@ func (cs *CryptoService) ImportKeysZip(zipReader zip.Reader) error {
// Note that using / as a separator is okay here - the zip // Note that using / as a separator is okay here - the zip
// package guarantees that the separator will be / // package guarantees that the separator will be /
if fNameTrimmed[len(fNameTrimmed)-5:] == "_root" { if fNameTrimmed[len(fNameTrimmed)-5:] == "_root" {
if err = checkRootKeyIsEncrypted(fileBytes); err != nil { if err = CheckRootKeyIsEncrypted(fileBytes); err != nil {
return err return err
} }
} }
@ -214,22 +163,21 @@ func (cs *CryptoService) ImportKeysZip(zipReader zip.Reader) error {
} }
for keyName, pemBytes := range newKeys { for keyName, pemBytes := range newKeys {
if keyName[len(keyName)-5:] == "_root" { // Get the key role information as well as its data.PrivateKey representation
keyName = "root" _, keyInfo, err := trustmanager.KeyInfoFromPEM(pemBytes, keyName)
if err != nil {
return err
} }
// try to import the key to all key stores. As long as one of them privKey, err := trustmanager.ParsePEMPrivateKey(pemBytes, "")
// succeeds, consider it a success if err != nil {
var tmpErr error privKey, _, err = trustmanager.GetPasswdDecryptBytes(retriever, pemBytes, "", "imported "+keyInfo.Role)
for _, ks := range cs.keyStores { if err != nil {
if err := ks.ImportKey(pemBytes, keyName); err != nil { return err
tmpErr = err
} else {
tmpErr = nil
break
} }
} }
if tmpErr != nil { // Add the key to our cryptoservice, will add to the first successful keystore
return tmpErr if err = cs.AddKey(keyInfo.Role, keyInfo.Gun, privKey); err != nil {
return err
} }
} }
@ -271,18 +219,18 @@ func (cs *CryptoService) ExportKeysByGUN(dest io.Writer, gun string, passphraseR
} }
func moveKeysByGUN(oldKeyStore, newKeyStore trustmanager.KeyStore, gun string) error { func moveKeysByGUN(oldKeyStore, newKeyStore trustmanager.KeyStore, gun string) error {
for relKeyPath := range oldKeyStore.ListKeys() { for keyID, keyInfo := range oldKeyStore.ListKeys() {
// Skip keys that aren't associated with this GUN // Skip keys that aren't associated with this GUN
if !strings.HasPrefix(relKeyPath, filepath.FromSlash(gun)) { if keyInfo.Gun != gun {
continue continue
} }
privKey, alias, err := oldKeyStore.GetKey(relKeyPath) privKey, _, err := oldKeyStore.GetKey(keyID)
if err != nil { if err != nil {
return err return err
} }
err = newKeyStore.AddKey(relKeyPath, alias, privKey) err = newKeyStore.AddKey(keyInfo, privKey)
if err != nil { if err != nil {
return err return err
} }
@ -292,13 +240,13 @@ func moveKeysByGUN(oldKeyStore, newKeyStore trustmanager.KeyStore, gun string) e
} }
func moveKeys(oldKeyStore, newKeyStore trustmanager.KeyStore) error { func moveKeys(oldKeyStore, newKeyStore trustmanager.KeyStore) error {
for f := range oldKeyStore.ListKeys() { for keyID, keyInfo := range oldKeyStore.ListKeys() {
privateKey, role, err := oldKeyStore.GetKey(f) privateKey, _, err := oldKeyStore.GetKey(keyID)
if err != nil { if err != nil {
return err return err
} }
err = newKeyStore.AddKey(f, role, privateKey) err = newKeyStore.AddKey(keyInfo, privateKey)
if err != nil { if err != nil {
return err return err
@ -349,9 +297,9 @@ func addKeysToArchive(zipWriter *zip.Writer, newKeyStore *trustmanager.KeyFileSt
return nil return nil
} }
// checkRootKeyIsEncrypted makes sure the root key is encrypted. We have // CheckRootKeyIsEncrypted makes sure the root key is encrypted. We have
// internal assumptions that depend on this. // internal assumptions that depend on this.
func checkRootKeyIsEncrypted(pemBytes []byte) error { func CheckRootKeyIsEncrypted(pemBytes []byte) error {
block, _ := pem.Decode(pemBytes) block, _ := pem.Decode(pemBytes)
if block == nil { if block == nil {
return ErrNoValidPrivateKey return ErrNoValidPrivateKey

View file

@ -1,4 +1,4 @@
FROM golang:1.5.3 FROM golang:1.6.0
MAINTAINER David Lawrence "david.lawrence@docker.com" MAINTAINER David Lawrence "david.lawrence@docker.com"
RUN apt-get update && apt-get install -y \ RUN apt-get update && apt-get install -y \
@ -12,9 +12,6 @@ EXPOSE 4443
RUN go get github.com/mattes/migrate RUN go get github.com/mattes/migrate
ENV NOTARYPKG github.com/docker/notary ENV NOTARYPKG github.com/docker/notary
ENV GOPATH /go/src/${NOTARYPKG}/Godeps/_workspace:$GOPATH
# Copy the local repo to the expected go path # Copy the local repo to the expected go path
COPY . /go/src/github.com/docker/notary COPY . /go/src/github.com/docker/notary

View file

@ -1,4 +1,4 @@
FROM golang:1.5.3 FROM golang:1.6.0
MAINTAINER David Lawrence "david.lawrence@docker.com" MAINTAINER David Lawrence "david.lawrence@docker.com"
RUN apt-get update && apt-get install -y \ RUN apt-get update && apt-get install -y \
@ -12,7 +12,6 @@ EXPOSE 4444
RUN go get github.com/mattes/migrate RUN go get github.com/mattes/migrate
ENV NOTARYPKG github.com/docker/notary ENV NOTARYPKG github.com/docker/notary
ENV GOPATH /go/src/${NOTARYPKG}/Godeps/_workspace:$GOPATH
ENV NOTARY_SIGNER_DEFAULT_ALIAS="timestamp_1" ENV NOTARY_SIGNER_DEFAULT_ALIAS="timestamp_1"
ENV NOTARY_SIGNER_TIMESTAMP_1="testpassword" ENV NOTARY_SIGNER_TIMESTAMP_1="testpassword"

View file

@ -13,12 +13,15 @@ import (
"github.com/docker/notary/tuf/data" "github.com/docker/notary/tuf/data"
) )
type keyInfoMap map[string]KeyInfo
// KeyFileStore persists and manages private keys on disk // KeyFileStore persists and manages private keys on disk
type KeyFileStore struct { type KeyFileStore struct {
sync.Mutex sync.Mutex
SimpleFileStore SimpleFileStore
passphrase.Retriever passphrase.Retriever
cachedKeys map[string]*cachedKey cachedKeys map[string]*cachedKey
keyInfoMap
} }
// KeyMemoryStore manages private keys in memory // KeyMemoryStore manages private keys in memory
@ -27,6 +30,14 @@ type KeyMemoryStore struct {
MemoryFileStore MemoryFileStore
passphrase.Retriever passphrase.Retriever
cachedKeys map[string]*cachedKey cachedKeys map[string]*cachedKey
keyInfoMap
}
// KeyInfo stores the role, path, and gun for a corresponding private key ID
// It is assumed that each private key ID is unique
type KeyInfo struct {
Gun string
Role string
} }
// NewKeyFileStore returns a new KeyFileStore creating a private directory to // NewKeyFileStore returns a new KeyFileStore creating a private directory to
@ -38,10 +49,93 @@ func NewKeyFileStore(baseDir string, passphraseRetriever passphrase.Retriever) (
return nil, err return nil, err
} }
cachedKeys := make(map[string]*cachedKey) cachedKeys := make(map[string]*cachedKey)
keyInfoMap := make(keyInfoMap)
return &KeyFileStore{SimpleFileStore: *fileStore, keyStore := &KeyFileStore{SimpleFileStore: *fileStore,
Retriever: passphraseRetriever, Retriever: passphraseRetriever,
cachedKeys: cachedKeys}, nil cachedKeys: cachedKeys,
keyInfoMap: keyInfoMap,
}
// Load this keystore's ID --> gun/role map
keyStore.loadKeyInfo()
return keyStore, nil
}
func generateKeyInfoMap(s LimitedFileStore) map[string]KeyInfo {
keyInfoMap := make(map[string]KeyInfo)
for _, keyPath := range s.ListFiles() {
d, err := s.Get(keyPath)
if err != nil {
logrus.Error(err)
continue
}
keyID, keyInfo, err := KeyInfoFromPEM(d, keyPath)
if err != nil {
logrus.Error(err)
continue
}
keyInfoMap[keyID] = keyInfo
}
return keyInfoMap
}
// Attempts to infer the keyID, role, and GUN from the specified key path.
// Note that non-root roles can only be inferred if this is a legacy style filename: KEYID_ROLE.key
func inferKeyInfoFromKeyPath(keyPath string) (string, string, string) {
var keyID, role, gun string
keyID = filepath.Base(keyPath)
underscoreIndex := strings.LastIndex(keyID, "_")
// This is the legacy KEYID_ROLE filename
// The keyID is the first part of the keyname
// The keyRole is the second part of the keyname
// in a key named abcde_root, abcde is the keyID and root is the KeyAlias
if underscoreIndex != -1 {
role = keyID[underscoreIndex+1:]
keyID = keyID[:underscoreIndex]
}
if filepath.HasPrefix(keyPath, notary.RootKeysSubdir+"/") {
return keyID, data.CanonicalRootRole, ""
}
keyPath = strings.TrimPrefix(keyPath, notary.NonRootKeysSubdir+"/")
gun = getGunFromFullID(keyPath)
return keyID, role, gun
}
func getGunFromFullID(fullKeyID string) string {
keyGun := filepath.Dir(fullKeyID)
// If the gun is empty, Dir will return .
if keyGun == "." {
keyGun = ""
}
return keyGun
}
func (s *KeyFileStore) loadKeyInfo() {
s.keyInfoMap = generateKeyInfoMap(s)
}
func (s *KeyMemoryStore) loadKeyInfo() {
s.keyInfoMap = generateKeyInfoMap(s)
}
// GetKeyInfo returns the corresponding gun and role key info for a keyID
func (s *KeyFileStore) GetKeyInfo(keyID string) (KeyInfo, error) {
if info, ok := s.keyInfoMap[keyID]; ok {
return info, nil
}
return KeyInfo{}, fmt.Errorf("Could not find info for keyID %s", keyID)
}
// GetKeyInfo returns the corresponding gun and role key info for a keyID
func (s *KeyMemoryStore) GetKeyInfo(keyID string) (KeyInfo, error) {
if info, ok := s.keyInfoMap[keyID]; ok {
return info, nil
}
return KeyInfo{}, fmt.Errorf("Could not find info for keyID %s", keyID)
} }
// Name returns a user friendly name for the location this store // Name returns a user friendly name for the location this store
@ -51,55 +145,81 @@ func (s *KeyFileStore) Name() string {
} }
// AddKey stores the contents of a PEM-encoded private key as a PEM block // AddKey stores the contents of a PEM-encoded private key as a PEM block
func (s *KeyFileStore) AddKey(name, role string, privKey data.PrivateKey) error { func (s *KeyFileStore) AddKey(keyInfo KeyInfo, privKey data.PrivateKey) error {
s.Lock() s.Lock()
defer s.Unlock() defer s.Unlock()
return addKey(s, s.Retriever, s.cachedKeys, name, role, privKey) if keyInfo.Role == data.CanonicalRootRole || data.IsDelegation(keyInfo.Role) || !data.ValidRole(keyInfo.Role) {
keyInfo.Gun = ""
}
err := addKey(s, s.Retriever, s.cachedKeys, filepath.Join(keyInfo.Gun, privKey.ID()), keyInfo.Role, privKey)
if err != nil {
return err
}
s.keyInfoMap[privKey.ID()] = keyInfo
return nil
} }
// GetKey returns the PrivateKey given a KeyID // GetKey returns the PrivateKey given a KeyID
func (s *KeyFileStore) GetKey(name string) (data.PrivateKey, string, error) { func (s *KeyFileStore) GetKey(name string) (data.PrivateKey, string, error) {
s.Lock() s.Lock()
defer s.Unlock() defer s.Unlock()
// If this is a bare key ID without the gun, prepend the gun so the filestore lookup succeeds
if keyInfo, ok := s.keyInfoMap[name]; ok {
name = filepath.Join(keyInfo.Gun, name)
}
return getKey(s, s.Retriever, s.cachedKeys, name) return getKey(s, s.Retriever, s.cachedKeys, name)
} }
// ListKeys returns a list of unique PublicKeys present on the KeyFileStore. // ListKeys returns a list of unique PublicKeys present on the KeyFileStore, by returning a copy of the keyInfoMap
func (s *KeyFileStore) ListKeys() map[string]string { func (s *KeyFileStore) ListKeys() map[string]KeyInfo {
return listKeys(s) return copyKeyInfoMap(s.keyInfoMap)
} }
// RemoveKey removes the key from the keyfilestore // RemoveKey removes the key from the keyfilestore
func (s *KeyFileStore) RemoveKey(name string) error { func (s *KeyFileStore) RemoveKey(keyID string) error {
s.Lock() s.Lock()
defer s.Unlock() defer s.Unlock()
return removeKey(s, s.cachedKeys, name) // If this is a bare key ID without the gun, prepend the gun so the filestore lookup succeeds
if keyInfo, ok := s.keyInfoMap[keyID]; ok {
keyID = filepath.Join(keyInfo.Gun, keyID)
}
err := removeKey(s, s.cachedKeys, keyID)
if err != nil {
return err
}
// Remove this key from our keyInfo map if we removed from our filesystem
delete(s.keyInfoMap, filepath.Base(keyID))
return nil
} }
// ExportKey exportes the encrypted bytes from the keystore and writes it to // ExportKey exports the encrypted bytes from the keystore
// dest. func (s *KeyFileStore) ExportKey(keyID string) ([]byte, error) {
func (s *KeyFileStore) ExportKey(name string) ([]byte, error) { if keyInfo, ok := s.keyInfoMap[keyID]; ok {
keyBytes, _, err := getRawKey(s, name) keyID = filepath.Join(keyInfo.Gun, keyID)
}
keyBytes, _, err := getRawKey(s, keyID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return keyBytes, nil return keyBytes, nil
} }
// ImportKey imports the private key in the encrypted bytes into the keystore
// with the given key ID and alias.
func (s *KeyFileStore) ImportKey(pemBytes []byte, alias string) error {
return importKey(s, s.Retriever, s.cachedKeys, alias, pemBytes)
}
// NewKeyMemoryStore returns a new KeyMemoryStore which holds keys in memory // NewKeyMemoryStore returns a new KeyMemoryStore which holds keys in memory
func NewKeyMemoryStore(passphraseRetriever passphrase.Retriever) *KeyMemoryStore { func NewKeyMemoryStore(passphraseRetriever passphrase.Retriever) *KeyMemoryStore {
memStore := NewMemoryFileStore() memStore := NewMemoryFileStore()
cachedKeys := make(map[string]*cachedKey) cachedKeys := make(map[string]*cachedKey)
return &KeyMemoryStore{MemoryFileStore: *memStore, keyInfoMap := make(keyInfoMap)
keyStore := &KeyMemoryStore{MemoryFileStore: *memStore,
Retriever: passphraseRetriever, Retriever: passphraseRetriever,
cachedKeys: cachedKeys} cachedKeys: cachedKeys,
keyInfoMap: keyInfoMap,
}
// Load this keystore's ID --> gun/role map
keyStore.loadKeyInfo()
return keyStore
} }
// Name returns a user friendly name for the location this store // Name returns a user friendly name for the location this store
@ -109,45 +229,84 @@ func (s *KeyMemoryStore) Name() string {
} }
// AddKey stores the contents of a PEM-encoded private key as a PEM block // AddKey stores the contents of a PEM-encoded private key as a PEM block
func (s *KeyMemoryStore) AddKey(name, alias string, privKey data.PrivateKey) error { func (s *KeyMemoryStore) AddKey(keyInfo KeyInfo, privKey data.PrivateKey) error {
s.Lock() s.Lock()
defer s.Unlock() defer s.Unlock()
return addKey(s, s.Retriever, s.cachedKeys, name, alias, privKey) if keyInfo.Role == data.CanonicalRootRole || data.IsDelegation(keyInfo.Role) || !data.ValidRole(keyInfo.Role) {
keyInfo.Gun = ""
}
err := addKey(s, s.Retriever, s.cachedKeys, filepath.Join(keyInfo.Gun, privKey.ID()), keyInfo.Role, privKey)
if err != nil {
return err
}
s.keyInfoMap[privKey.ID()] = keyInfo
return nil
} }
// GetKey returns the PrivateKey given a KeyID // GetKey returns the PrivateKey given a KeyID
func (s *KeyMemoryStore) GetKey(name string) (data.PrivateKey, string, error) { func (s *KeyMemoryStore) GetKey(name string) (data.PrivateKey, string, error) {
s.Lock() s.Lock()
defer s.Unlock() defer s.Unlock()
// If this is a bare key ID without the gun, prepend the gun so the filestore lookup succeeds
if keyInfo, ok := s.keyInfoMap[name]; ok {
name = filepath.Join(keyInfo.Gun, name)
}
return getKey(s, s.Retriever, s.cachedKeys, name) return getKey(s, s.Retriever, s.cachedKeys, name)
} }
// ListKeys returns a list of unique PublicKeys present on the KeyFileStore. // ListKeys returns a list of unique PublicKeys present on the KeyFileStore, by returning a copy of the keyInfoMap
func (s *KeyMemoryStore) ListKeys() map[string]string { func (s *KeyMemoryStore) ListKeys() map[string]KeyInfo {
return listKeys(s) return copyKeyInfoMap(s.keyInfoMap)
}
// copyKeyInfoMap returns a deep copy of the passed-in keyInfoMap
func copyKeyInfoMap(keyInfoMap map[string]KeyInfo) map[string]KeyInfo {
copyMap := make(map[string]KeyInfo)
for keyID, keyInfo := range keyInfoMap {
copyMap[keyID] = KeyInfo{Role: keyInfo.Role, Gun: keyInfo.Gun}
}
return copyMap
} }
// RemoveKey removes the key from the keystore // RemoveKey removes the key from the keystore
func (s *KeyMemoryStore) RemoveKey(name string) error { func (s *KeyMemoryStore) RemoveKey(keyID string) error {
s.Lock() s.Lock()
defer s.Unlock() defer s.Unlock()
return removeKey(s, s.cachedKeys, name) // If this is a bare key ID without the gun, prepend the gun so the filestore lookup succeeds
if keyInfo, ok := s.keyInfoMap[keyID]; ok {
keyID = filepath.Join(keyInfo.Gun, keyID)
}
err := removeKey(s, s.cachedKeys, keyID)
if err != nil {
return err
}
// Remove this key from our keyInfo map if we removed from our filesystem
delete(s.keyInfoMap, filepath.Base(keyID))
return nil
} }
// ExportKey exportes the encrypted bytes from the keystore and writes it to // ExportKey exports the encrypted bytes from the keystore
// dest. func (s *KeyMemoryStore) ExportKey(keyID string) ([]byte, error) {
func (s *KeyMemoryStore) ExportKey(name string) ([]byte, error) { keyBytes, _, err := getRawKey(s, keyID)
keyBytes, _, err := getRawKey(s, name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return keyBytes, nil return keyBytes, nil
} }
// ImportKey imports the private key in the encrypted bytes into the keystore // KeyInfoFromPEM attempts to get a keyID and KeyInfo from the filename and PEM bytes of a key
// with the given key ID and alias. func KeyInfoFromPEM(pemBytes []byte, filename string) (string, KeyInfo, error) {
func (s *KeyMemoryStore) ImportKey(pemBytes []byte, alias string) error { keyID, role, gun := inferKeyInfoFromKeyPath(filename)
return importKey(s, s.Retriever, s.cachedKeys, alias, pemBytes) if role == "" {
block, _ := pem.Decode(pemBytes)
if block == nil {
return "", KeyInfo{}, fmt.Errorf("could not decode PEM block for key %s", filename)
}
if keyRole, ok := block.Headers["role"]; ok {
role = keyRole
}
}
return keyID, KeyInfo{Gun: gun, Role: role}, nil
} }
func addKey(s LimitedFileStore, passphraseRetriever passphrase.Retriever, cachedKeys map[string]*cachedKey, name, role string, privKey data.PrivateKey) error { func addKey(s LimitedFileStore, passphraseRetriever passphrase.Retriever, cachedKeys map[string]*cachedKey, name, role string, privKey data.PrivateKey) error {
@ -229,50 +388,6 @@ func getKey(s LimitedFileStore, passphraseRetriever passphrase.Retriever, cached
return privKey, keyAlias, nil return privKey, keyAlias, nil
} }
// ListKeys returns a map of unique PublicKeys present on the KeyFileStore and
// their corresponding aliases.
func listKeys(s LimitedFileStore) map[string]string {
keyIDMap := make(map[string]string)
for _, f := range s.ListFiles() {
// Remove the prefix of the directory from the filename
var keyIDFull string
if strings.HasPrefix(f, notary.RootKeysSubdir+"/") {
keyIDFull = strings.TrimPrefix(f, notary.RootKeysSubdir+"/")
} else {
keyIDFull = strings.TrimPrefix(f, notary.NonRootKeysSubdir+"/")
}
keyIDFull = strings.TrimSpace(keyIDFull)
// If the key does not have a _, we'll attempt to
// read it as a PEM
underscoreIndex := strings.LastIndex(keyIDFull, "_")
if underscoreIndex == -1 {
d, err := s.Get(f)
if err != nil {
logrus.Error(err)
continue
}
block, _ := pem.Decode(d)
if block == nil {
continue
}
if role, ok := block.Headers["role"]; ok {
keyIDMap[keyIDFull] = role
}
} else {
// The keyID is the first part of the keyname
// The KeyAlias is the second part of the keyname
// in a key named abcde_root, abcde is the keyID and root is the KeyAlias
keyID := keyIDFull[:underscoreIndex]
keyAlias := keyIDFull[underscoreIndex+1:]
keyIDMap[keyID] = keyAlias
}
}
return keyIDMap
}
// RemoveKey removes the key from the keyfilestore // RemoveKey removes the key from the keyfilestore
func removeKey(s LimitedFileStore, cachedKeys map[string]*cachedKey, name string) error { func removeKey(s LimitedFileStore, cachedKeys map[string]*cachedKey, name string) error {
role, legacy, err := getKeyRole(s, name) role, legacy, err := getKeyRole(s, name)
@ -296,7 +411,7 @@ func removeKey(s LimitedFileStore, cachedKeys map[string]*cachedKey, name string
// Assumes 2 subdirectories, 1 containing root keys and 1 containing tuf keys // Assumes 2 subdirectories, 1 containing root keys and 1 containing tuf keys
func getSubdir(alias string) string { func getSubdir(alias string) string {
if alias == "root" { if alias == data.CanonicalRootRole {
return notary.RootKeysSubdir return notary.RootKeysSubdir
} }
return notary.NonRootKeysSubdir return notary.NonRootKeysSubdir
@ -380,21 +495,3 @@ func encryptAndAddKey(s LimitedFileStore, passwd string, cachedKeys map[string]*
cachedKeys[name] = &cachedKey{alias: role, key: privKey} cachedKeys[name] = &cachedKey{alias: role, key: privKey}
return s.Add(filepath.Join(getSubdir(role), name), pemPrivKey) return s.Add(filepath.Join(getSubdir(role), name), pemPrivKey)
} }
func importKey(s LimitedFileStore, passphraseRetriever passphrase.Retriever, cachedKeys map[string]*cachedKey, alias string, pemBytes []byte) error {
if alias != data.CanonicalRootRole {
return s.Add(alias, pemBytes)
}
privKey, passphrase, err := GetPasswdDecryptBytes(
passphraseRetriever, pemBytes, "", "imported "+alias)
if err != nil {
return err
}
var name string
name = privKey.ID()
return encryptAndAddKey(s, passphrase, cachedKeys, name, alias, privKey)
}

View file

@ -40,14 +40,14 @@ const (
// KeyStore is a generic interface for private key storage // KeyStore is a generic interface for private key storage
type KeyStore interface { type KeyStore interface {
// Add Key adds a key to the KeyStore, and if the key already exists, // AddKey adds a key to the KeyStore, and if the key already exists,
// succeeds. Otherwise, returns an error if it cannot add. // succeeds. Otherwise, returns an error if it cannot add.
AddKey(name, alias string, privKey data.PrivateKey) error AddKey(keyInfo KeyInfo, privKey data.PrivateKey) error
GetKey(name string) (data.PrivateKey, string, error) GetKey(keyID string) (data.PrivateKey, string, error)
ListKeys() map[string]string GetKeyInfo(keyID string) (KeyInfo, error)
RemoveKey(name string) error ListKeys() map[string]KeyInfo
ExportKey(name string) ([]byte, error) RemoveKey(keyID string) error
ImportKey(pemBytes []byte, alias string) error ExportKey(keyID string) ([]byte, error)
Name() string Name() string
} }

View file

@ -517,7 +517,7 @@ func EncryptPrivateKey(key data.PrivateKey, role, passphrase string) ([]byte, er
// ReadRoleFromPEM returns the value from the role PEM header, if it exists // ReadRoleFromPEM returns the value from the role PEM header, if it exists
func ReadRoleFromPEM(pemBytes []byte) string { func ReadRoleFromPEM(pemBytes []byte) string {
pemBlock, _ := pem.Decode(pemBytes) pemBlock, _ := pem.Decode(pemBytes)
if pemBlock.Headers == nil { if pemBlock == nil || pemBlock.Headers == nil {
return "" return ""
} }
role, ok := pemBlock.Headers["role"] role, ok := pemBlock.Headers["role"]

View file

@ -617,7 +617,7 @@ func (s *YubiKeyStore) setLibLoader(loader pkcs11LibLoader) {
s.libLoader = loader s.libLoader = loader
} }
func (s *YubiKeyStore) ListKeys() map[string]string { func (s *YubiKeyStore) ListKeys() map[string]trustmanager.KeyInfo {
if len(s.keys) > 0 { if len(s.keys) > 0 {
return buildKeyMap(s.keys) return buildKeyMap(s.keys)
} }
@ -639,15 +639,15 @@ func (s *YubiKeyStore) ListKeys() map[string]string {
} }
// AddKey puts a key inside the Yubikey, as well as writing it to the backup store // AddKey puts a key inside the Yubikey, as well as writing it to the backup store
func (s *YubiKeyStore) AddKey(keyID, role string, privKey data.PrivateKey) error { func (s *YubiKeyStore) AddKey(keyInfo trustmanager.KeyInfo, privKey data.PrivateKey) error {
added, err := s.addKey(keyID, role, privKey) added, err := s.addKey(privKey.ID(), keyInfo.Role, privKey)
if err != nil { if err != nil {
return err return err
} }
if added { if added && s.backupStore != nil {
err = s.backupStore.AddKey(privKey.ID(), role, privKey) err = s.backupStore.AddKey(keyInfo, privKey)
if err != nil { if err != nil {
defer s.RemoveKey(keyID) defer s.RemoveKey(privKey.ID())
return ErrBackupFailed{err: err.Error()} return ErrBackupFailed{err: err.Error()}
} }
} }
@ -762,20 +762,9 @@ func (s *YubiKeyStore) ExportKey(keyID string) ([]byte, error) {
return nil, errors.New("Keys cannot be exported from a Yubikey.") return nil, errors.New("Keys cannot be exported from a Yubikey.")
} }
// ImportKey imports a root key into a Yubikey // Not yet implemented
func (s *YubiKeyStore) ImportKey(pemBytes []byte, keyPath string) error { func (s *YubiKeyStore) GetKeyInfo(keyID string) (trustmanager.KeyInfo, error) {
logrus.Debugf("Attempting to import: %s key inside of YubiKeyStore", keyPath) return trustmanager.KeyInfo{}, fmt.Errorf("Not yet implemented")
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
}
_, err = s.addKey(privKey.ID(), "root", privKey)
return err
} }
func cleanup(ctx IPKCS11Ctx, session pkcs11.SessionHandle) { func cleanup(ctx IPKCS11Ctx, session pkcs11.SessionHandle) {
@ -890,10 +879,10 @@ func login(ctx IPKCS11Ctx, session pkcs11.SessionHandle, passRetriever passphras
return nil return nil
} }
func buildKeyMap(keys map[string]yubiSlot) map[string]string { func buildKeyMap(keys map[string]yubiSlot) map[string]trustmanager.KeyInfo {
res := make(map[string]string) res := make(map[string]trustmanager.KeyInfo)
for k, v := range keys { for k, v := range keys {
res[k] = v.role res[k] = trustmanager.KeyInfo{Role: v.role, Gun: ""}
} }
return res return res
} }

View file

@ -1,8 +1,6 @@
package client package client
import ( import (
"bytes"
"crypto/sha256"
"encoding/json" "encoding/json"
"fmt" "fmt"
"path" "path"
@ -92,16 +90,16 @@ func (c *Client) update() error {
func (c Client) checkRoot() error { func (c Client) checkRoot() error {
role := data.CanonicalRootRole role := data.CanonicalRootRole
size := c.local.Snapshot.Signed.Meta[role].Length size := c.local.Snapshot.Signed.Meta[role].Length
hashSha256 := c.local.Snapshot.Signed.Meta[role].Hashes["sha256"]
expectedHashes := c.local.Snapshot.Signed.Meta[role].Hashes
raw, err := c.cache.GetMeta("root", size) raw, err := c.cache.GetMeta("root", size)
if err != nil { if err != nil {
return err return err
} }
hash := sha256.Sum256(raw) if err := data.CheckHashes(raw, expectedHashes); err != nil {
if !bytes.Equal(hash[:], hashSha256) { return fmt.Errorf("Cached root hashes did not match snapshot root hashes")
return fmt.Errorf("Cached root sha256 did not match snapshot root sha256")
} }
if int64(len(raw)) != size { if int64(len(raw)) != size {
@ -127,11 +125,19 @@ func (c *Client) downloadRoot() error {
// We can't read an exact size for the root metadata without risking getting stuck in the TUF update cycle // 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 // since it's possible that downloading timestamp/snapshot metadata may fail due to a signature mismatch
var size int64 = -1 var size int64 = -1
var expectedSha256 []byte
// We could not expect what the "snapshot" meta has specified.
//
// In some old clients, there is only the "sha256",
// but both "sha256" and "sha512" in the newer ones.
//
// And possibly more in the future.
var expectedHashes data.Hashes
if c.local.Snapshot != nil { if c.local.Snapshot != nil {
if prevRootMeta, ok := c.local.Snapshot.Signed.Meta[role]; ok { if prevRootMeta, ok := c.local.Snapshot.Signed.Meta[role]; ok {
size = prevRootMeta.Length size = prevRootMeta.Length
expectedSha256 = prevRootMeta.Hashes["sha256"] expectedHashes = prevRootMeta.Hashes
} }
} }
@ -144,8 +150,9 @@ func (c *Client) downloadRoot() error {
old := &data.Signed{} old := &data.Signed{}
version := 0 version := 0
if expectedSha256 != nil { // Due to the same reason, we don't really know how many hashes are there.
// can only trust cache if we have an expected sha256 to trust if len(expectedHashes) != 0 {
// can only trust cache if we have an expected sha256(for example) to trust
cachedRoot, err = c.cache.GetMeta(role, size) cachedRoot, err = c.cache.GetMeta(role, size)
} }
@ -153,11 +160,11 @@ func (c *Client) downloadRoot() error {
logrus.Debug("didn't find a cached root, must download") logrus.Debug("didn't find a cached root, must download")
download = true download = true
} else { } else {
hash := sha256.Sum256(cachedRoot) if err := data.CheckHashes(cachedRoot, expectedHashes); err != nil {
if !bytes.Equal(hash[:], expectedSha256) {
logrus.Debug("cached root's hash didn't match expected, must download") logrus.Debug("cached root's hash didn't match expected, must download")
download = true download = true
} }
err := json.Unmarshal(cachedRoot, old) err := json.Unmarshal(cachedRoot, old)
if err == nil { if err == nil {
root, err := data.RootFromSigned(old) root, err := data.RootFromSigned(old)
@ -176,7 +183,7 @@ func (c *Client) downloadRoot() error {
var raw []byte var raw []byte
if download { if download {
// use consistent download if we have the checksum. // use consistent download if we have the checksum.
raw, s, err = c.downloadSigned(role, size, expectedSha256) raw, s, err = c.downloadSigned(role, size, expectedHashes)
if err != nil { if err != nil {
return err return err
} }
@ -322,8 +329,8 @@ func (c *Client) downloadSnapshot() error {
return tuf.ErrNotLoaded{Role: data.CanonicalTimestampRole} return tuf.ErrNotLoaded{Role: data.CanonicalTimestampRole}
} }
size := c.local.Timestamp.Signed.Meta[role].Length size := c.local.Timestamp.Signed.Meta[role].Length
expectedSha256, ok := c.local.Timestamp.Signed.Meta[role].Hashes["sha256"] expectedHashes := c.local.Timestamp.Signed.Meta[role].Hashes
if !ok { if len(expectedHashes) == 0 {
return data.ErrMissingMeta{Role: "snapshot"} return data.ErrMissingMeta{Role: "snapshot"}
} }
@ -336,11 +343,11 @@ func (c *Client) downloadSnapshot() error {
download = true download = true
} else { } else {
// file may have been tampered with on disk. Always check the hash! // file may have been tampered with on disk. Always check the hash!
genHash := sha256.Sum256(raw) if err := data.CheckHashes(raw, expectedHashes); err != nil {
if !bytes.Equal(genHash[:], expectedSha256) {
logrus.Debug("hash of snapshot in cache did not match expected hash, must download") logrus.Debug("hash of snapshot in cache did not match expected hash, must download")
download = true download = true
} }
err := json.Unmarshal(raw, old) err := json.Unmarshal(raw, old)
if err == nil { if err == nil {
snap, err := data.SnapshotFromSigned(old) snap, err := data.SnapshotFromSigned(old)
@ -357,7 +364,7 @@ func (c *Client) downloadSnapshot() error {
} }
var s *data.Signed var s *data.Signed
if download { if download {
raw, s, err = c.downloadSigned(role, size, expectedSha256) raw, s, err = c.downloadSigned(role, size, expectedHashes)
if err != nil { if err != nil {
return err return err
} }
@ -439,18 +446,19 @@ func (c *Client) downloadTargets(role string) error {
return nil return nil
} }
func (c *Client) downloadSigned(role string, size int64, expectedSha256 []byte) ([]byte, *data.Signed, error) { func (c *Client) downloadSigned(role string, size int64, expectedHashes data.Hashes) ([]byte, *data.Signed, error) {
rolePath := utils.ConsistentName(role, expectedSha256) rolePath := utils.ConsistentName(role, expectedHashes["sha256"])
raw, err := c.remote.GetMeta(rolePath, size) raw, err := c.remote.GetMeta(rolePath, size)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
if expectedSha256 != nil {
genHash := sha256.Sum256(raw) if expectedHashes != nil {
if !bytes.Equal(genHash[:], expectedSha256) { if err := data.CheckHashes(raw, expectedHashes); err != nil {
return nil, nil, ErrChecksumMismatch{role: role} return nil, nil, ErrChecksumMismatch{role: role}
} }
} }
s := &data.Signed{} s := &data.Signed{}
err = json.Unmarshal(raw, s) err = json.Unmarshal(raw, s)
if err != nil { if err != nil {
@ -465,8 +473,8 @@ func (c Client) getTargetsFile(role string, snapshotMeta data.Files, consistent
if !ok { if !ok {
return nil, data.ErrMissingMeta{Role: role} return nil, data.ErrMissingMeta{Role: role}
} }
expectedSha256, ok := snapshotMeta[role].Hashes["sha256"] expectedHashes := snapshotMeta[role].Hashes
if !ok { if len(expectedHashes) == 0 {
return nil, data.ErrMissingMeta{Role: role} return nil, data.ErrMissingMeta{Role: role}
} }
@ -480,10 +488,10 @@ func (c Client) getTargetsFile(role string, snapshotMeta data.Files, consistent
download = true download = true
} else { } else {
// file may have been tampered with on disk. Always check the hash! // file may have been tampered with on disk. Always check the hash!
genHash := sha256.Sum256(raw) if err := data.CheckHashes(raw, expectedHashes); err != nil {
if !bytes.Equal(genHash[:], expectedSha256) {
download = true download = true
} }
err := json.Unmarshal(raw, old) err := json.Unmarshal(raw, old)
if err == nil { if err == nil {
targ, err := data.TargetsFromSigned(old, role) targ, err := data.TargetsFromSigned(old, role)
@ -500,7 +508,7 @@ func (c Client) getTargetsFile(role string, snapshotMeta data.Files, consistent
size := snapshotMeta[role].Length size := snapshotMeta[role].Length
var s *data.Signed var s *data.Signed
if download { if download {
raw, s, err = c.downloadSigned(role, size, expectedSha256) raw, s, err = c.downloadSigned(role, size, expectedHashes)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -129,7 +129,7 @@ func (r SignedRoot) ToSigned() (*Signed, error) {
copy(sigs, r.Signatures) copy(sigs, r.Signatures)
return &Signed{ return &Signed{
Signatures: sigs, Signatures: sigs,
Signed: signed, Signed: &signed,
}, nil }, nil
} }
@ -146,7 +146,7 @@ func (r SignedRoot) MarshalJSON() ([]byte, error) {
// that it is a valid SignedRoot // that it is a valid SignedRoot
func RootFromSigned(s *Signed) (*SignedRoot, error) { func RootFromSigned(s *Signed) (*SignedRoot, error) {
r := Root{} r := Root{}
if err := defaultSerializer.Unmarshal(s.Signed, &r); err != nil { if err := defaultSerializer.Unmarshal(*s.Signed, &r); err != nil {
return nil, err return nil, err
} }
if err := isValidRootStructure(r); err != nil { if err := isValidRootStructure(r); err != nil {

View file

@ -2,12 +2,12 @@ package data
import ( import (
"bytes" "bytes"
"crypto/sha256"
"fmt" "fmt"
"time" "time"
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/docker/go/canonical/json" "github.com/docker/go/canonical/json"
"github.com/docker/notary"
) )
// SignedSnapshot is a fully unpacked snapshot.json // SignedSnapshot is a fully unpacked snapshot.json
@ -39,10 +39,18 @@ func isValidSnapshotStructure(s Snapshot) error {
// Meta is a map of FileMeta, so if the role isn't in the map it returns // 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 // an empty FileMeta, which has an empty map, and you can check on keys
// from an empty map. // from an empty map.
if checksum, ok := s.Meta[role].Hashes["sha256"]; !ok || len(checksum) != sha256.Size { //
// For now sha256 is required and sha512 is not.
if _, ok := s.Meta[role].Hashes[notary.SHA256]; !ok {
return ErrInvalidMetadata{ return ErrInvalidMetadata{
role: CanonicalSnapshotRole, role: CanonicalSnapshotRole,
msg: fmt.Sprintf("missing or invalid %s sha256 checksum information", role), msg: fmt.Sprintf("missing %s sha256 checksum information", role),
}
}
if err := CheckValidHashStructures(s.Meta[role].Hashes); err != nil {
return ErrInvalidMetadata{
role: CanonicalSnapshotRole,
msg: fmt.Sprintf("invalid %s checksum information, %v", role, err),
} }
} }
} }
@ -63,11 +71,11 @@ func NewSnapshot(root *Signed, targets *Signed) (*SignedSnapshot, error) {
logrus.Debug("Error Marshalling Root") logrus.Debug("Error Marshalling Root")
return nil, err return nil, err
} }
rootMeta, err := NewFileMeta(bytes.NewReader(rootJSON), "sha256") rootMeta, err := NewFileMeta(bytes.NewReader(rootJSON), NotaryDefaultHashes...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
targetsMeta, err := NewFileMeta(bytes.NewReader(targetsJSON), "sha256") targetsMeta, err := NewFileMeta(bytes.NewReader(targetsJSON), NotaryDefaultHashes...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -85,10 +93,6 @@ func NewSnapshot(root *Signed, targets *Signed) (*SignedSnapshot, error) {
}, nil }, nil
} }
func (sp *SignedSnapshot) hashForRole(role string) []byte {
return sp.Signed.Meta[role].Hashes["sha256"]
}
// ToSigned partially serializes a SignedSnapshot for further signing // ToSigned partially serializes a SignedSnapshot for further signing
func (sp *SignedSnapshot) ToSigned() (*Signed, error) { func (sp *SignedSnapshot) ToSigned() (*Signed, error) {
s, err := defaultSerializer.MarshalCanonical(sp.Signed) s, err := defaultSerializer.MarshalCanonical(sp.Signed)
@ -104,7 +108,7 @@ func (sp *SignedSnapshot) ToSigned() (*Signed, error) {
copy(sigs, sp.Signatures) copy(sigs, sp.Signatures)
return &Signed{ return &Signed{
Signatures: sigs, Signatures: sigs,
Signed: signed, Signed: &signed,
}, nil }, nil
} }
@ -144,7 +148,7 @@ func (sp *SignedSnapshot) MarshalJSON() ([]byte, error) {
// SnapshotFromSigned fully unpacks a Signed object into a SignedSnapshot // SnapshotFromSigned fully unpacks a Signed object into a SignedSnapshot
func SnapshotFromSigned(s *Signed) (*SignedSnapshot, error) { func SnapshotFromSigned(s *Signed) (*SignedSnapshot, error) {
sp := Snapshot{} sp := Snapshot{}
if err := defaultSerializer.Unmarshal(s.Signed, &sp); err != nil { if err := defaultSerializer.Unmarshal(*s.Signed, &sp); err != nil {
return nil, err return nil, err
} }
if err := isValidSnapshotStructure(sp); err != nil { if err := isValidSnapshotStructure(sp); err != nil {

View file

@ -162,7 +162,7 @@ func (t *SignedTargets) ToSigned() (*Signed, error) {
copy(sigs, t.Signatures) copy(sigs, t.Signatures)
return &Signed{ return &Signed{
Signatures: sigs, Signatures: sigs,
Signed: signed, Signed: &signed,
}, nil }, nil
} }
@ -179,7 +179,7 @@ func (t *SignedTargets) MarshalJSON() ([]byte, error) {
// a role name (so it can validate the SignedTargets object) // a role name (so it can validate the SignedTargets object)
func TargetsFromSigned(s *Signed, roleName string) (*SignedTargets, error) { func TargetsFromSigned(s *Signed, roleName string) (*SignedTargets, error) {
t := Targets{} t := Targets{}
if err := defaultSerializer.Unmarshal(s.Signed, &t); err != nil { if err := defaultSerializer.Unmarshal(*s.Signed, &t); err != nil {
return nil, err return nil, err
} }
if err := isValidTargetsStructure(t, roleName); err != nil { if err := isValidTargetsStructure(t, roleName); err != nil {

View file

@ -2,11 +2,11 @@ package data
import ( import (
"bytes" "bytes"
"crypto/sha256"
"fmt" "fmt"
"time" "time"
"github.com/docker/go/canonical/json" "github.com/docker/go/canonical/json"
"github.com/docker/notary"
) )
// SignedTimestamp is a fully unpacked timestamp.json // SignedTimestamp is a fully unpacked timestamp.json
@ -37,10 +37,17 @@ func isValidTimestampStructure(t Timestamp) error {
// Meta is a map of FileMeta, so if the role isn't in the map it returns // 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 // an empty FileMeta, which has an empty map, and you can check on keys
// from an empty map. // from an empty map.
if cs, ok := t.Meta[CanonicalSnapshotRole].Hashes["sha256"]; !ok || len(cs) != sha256.Size { //
// For now sha256 is required and sha512 is not.
if _, ok := t.Meta[CanonicalSnapshotRole].Hashes[notary.SHA256]; !ok {
return ErrInvalidMetadata{ return ErrInvalidMetadata{
role: CanonicalTimestampRole, msg: "missing or invalid snapshot sha256 checksum information"} role: CanonicalTimestampRole, msg: "missing snapshot sha256 checksum information"}
} }
if err := CheckValidHashStructures(t.Meta[CanonicalSnapshotRole].Hashes); err != nil {
return ErrInvalidMetadata{
role: CanonicalTimestampRole, msg: fmt.Sprintf("invalid snapshot checksum information, %v", err)}
}
return nil return nil
} }
@ -50,7 +57,7 @@ func NewTimestamp(snapshot *Signed) (*SignedTimestamp, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
snapshotMeta, err := NewFileMeta(bytes.NewReader(snapshotJSON), "sha256") snapshotMeta, err := NewFileMeta(bytes.NewReader(snapshotJSON), NotaryDefaultHashes...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -83,7 +90,7 @@ func (ts *SignedTimestamp) ToSigned() (*Signed, error) {
copy(sigs, ts.Signatures) copy(sigs, ts.Signatures)
return &Signed{ return &Signed{
Signatures: sigs, Signatures: sigs,
Signed: signed, Signed: &signed,
}, nil }, nil
} }
@ -110,7 +117,7 @@ func (ts *SignedTimestamp) MarshalJSON() ([]byte, error) {
// SignedTimestamp // SignedTimestamp
func TimestampFromSigned(s *Signed) (*SignedTimestamp, error) { func TimestampFromSigned(s *Signed) (*SignedTimestamp, error) {
ts := Timestamp{} ts := Timestamp{}
if err := defaultSerializer.Unmarshal(s.Signed, &ts); err != nil { if err := defaultSerializer.Unmarshal(*s.Signed, &ts); err != nil {
return nil, err return nil, err
} }
if err := isValidTimestampStructure(ts); err != nil { if err := isValidTimestampStructure(ts); err != nil {

View file

@ -3,6 +3,7 @@ package data
import ( import (
"crypto/sha256" "crypto/sha256"
"crypto/sha512" "crypto/sha512"
"crypto/subtle"
"fmt" "fmt"
"hash" "hash"
"io" "io"
@ -85,7 +86,7 @@ func ValidTUFType(typ, role string) bool {
// used to verify signatures before fully unpacking, or to add signatures // used to verify signatures before fully unpacking, or to add signatures
// before fully packing // before fully packing
type Signed struct { type Signed struct {
Signed json.RawMessage `json:"signed"` Signed *json.RawMessage `json:"signed"`
Signatures []Signature `json:"signatures"` Signatures []Signature `json:"signatures"`
} }
@ -119,12 +120,71 @@ type Files map[string]FileMeta
// and target file // and target file
type Hashes map[string][]byte type Hashes map[string][]byte
// NotaryDefaultHashes contains the default supported hash algorithms.
var NotaryDefaultHashes = []string{notary.SHA256, notary.SHA512}
// FileMeta contains the size and hashes for a metadata or target file. Custom // FileMeta contains the size and hashes for a metadata or target file. Custom
// data can be optionally added. // data can be optionally added.
type FileMeta struct { type FileMeta struct {
Length int64 `json:"length"` Length int64 `json:"length"`
Hashes Hashes `json:"hashes"` Hashes Hashes `json:"hashes"`
Custom json.RawMessage `json:"custom,omitempty"` Custom *json.RawMessage `json:"custom,omitempty"`
}
// CheckHashes verifies all the checksums specified by the "hashes" of the payload.
func CheckHashes(payload []byte, hashes Hashes) error {
cnt := 0
// k, v indicate the hash algorithm and the corresponding value
for k, v := range hashes {
switch k {
case notary.SHA256:
checksum := sha256.Sum256(payload)
if subtle.ConstantTimeCompare(checksum[:], v) == 0 {
return fmt.Errorf("%s checksum mismatched", k)
}
cnt++
case notary.SHA512:
checksum := sha512.Sum512(payload)
if subtle.ConstantTimeCompare(checksum[:], v) == 0 {
return fmt.Errorf("%s checksum mismatched", k)
}
cnt++
}
}
if cnt == 0 {
return fmt.Errorf("at least one supported hash needed")
}
return nil
}
// CheckValidHashStructures returns an error, or nil, depending on whether
// the content of the hashes is valid or not.
func CheckValidHashStructures(hashes Hashes) error {
cnt := 0
for k, v := range hashes {
switch k {
case notary.SHA256:
if len(v) != sha256.Size {
return fmt.Errorf("invalid %s checksum", notary.SHA256)
}
cnt++
case notary.SHA512:
if len(v) != sha512.Size {
return fmt.Errorf("invalid %s checksum", notary.SHA512)
}
cnt++
}
}
if cnt == 0 {
return fmt.Errorf("at least one supported hash needed")
}
return nil
} }
// NewFileMeta generates a FileMeta object from the reader, using the // NewFileMeta generates a FileMeta object from the reader, using the
@ -137,12 +197,12 @@ func NewFileMeta(r io.Reader, hashAlgorithms ...string) (FileMeta, error) {
for _, hashAlgorithm := range hashAlgorithms { for _, hashAlgorithm := range hashAlgorithms {
var h hash.Hash var h hash.Hash
switch hashAlgorithm { switch hashAlgorithm {
case "sha256": case notary.SHA256:
h = sha256.New() h = sha256.New()
case "sha512": case notary.SHA512:
h = sha512.New() h = sha512.New()
default: default:
return FileMeta{}, fmt.Errorf("Unknown Hash Algorithm: %s", hashAlgorithm) return FileMeta{}, fmt.Errorf("Unknown hash algorithm: %s", hashAlgorithm)
} }
hashes[hashAlgorithm] = h hashes[hashAlgorithm] = h
r = io.TeeReader(r, h) r = io.TeeReader(r, h)

View file

@ -3,10 +3,7 @@ package signed
import ( import (
"crypto/rand" "crypto/rand"
"errors" "errors"
"io"
"io/ioutil"
"github.com/agl/ed25519"
"github.com/docker/notary/trustmanager" "github.com/docker/notary/trustmanager"
"github.com/docker/notary/tuf/data" "github.com/docker/notary/tuf/data"
) )
@ -29,6 +26,12 @@ func NewEd25519() *Ed25519 {
} }
} }
// AddKey allows you to add a private key
func (e *Ed25519) AddKey(role, gun string, k data.PrivateKey) error {
e.addKey(role, k)
return nil
}
// addKey allows you to add a private key // addKey allows you to add a private key
func (e *Ed25519) addKey(role string, k data.PrivateKey) { func (e *Ed25519) addKey(role string, k data.PrivateKey) {
e.keys[k.ID()] = edCryptoKey{ e.keys[k.ID()] = edCryptoKey{
@ -64,7 +67,7 @@ func (e *Ed25519) ListAllKeys() map[string]string {
} }
// Create generates a new key and returns the public part // Create generates a new key and returns the public part
func (e *Ed25519) Create(role, algorithm string) (data.PublicKey, error) { func (e *Ed25519) Create(role, gun, algorithm string) (data.PublicKey, error) {
if algorithm != data.ED25519Key { if algorithm != data.ED25519Key {
return nil, errors.New("only ED25519 supported by this cryptoservice") return nil, errors.New("only ED25519 supported by this cryptoservice")
} }
@ -102,22 +105,3 @@ func (e *Ed25519) GetPrivateKey(keyID string) (data.PrivateKey, string, error) {
} }
return nil, "", trustmanager.ErrKeyNotFound{KeyID: keyID} return nil, "", trustmanager.ErrKeyNotFound{KeyID: keyID}
} }
// ImportRootKey adds an Ed25519 key to the store as a root key
func (e *Ed25519) ImportRootKey(r io.Reader) error {
raw, err := ioutil.ReadAll(r)
if err != nil {
return err
}
dataSize := ed25519.PublicKeySize + ed25519.PrivateKeySize
if len(raw) < dataSize || len(raw) > dataSize {
return errors.New("Wrong length of data for Ed25519 Key Import")
}
public := data.NewED25519PublicKey(raw[:ed25519.PublicKeySize])
private, err := data.NewED25519PrivateKey(*public, raw[ed25519.PublicKeySize:])
e.keys[private.ID()] = edCryptoKey{
role: "root",
privKey: private,
}
return nil
}

View file

@ -2,7 +2,6 @@ package signed
import ( import (
"github.com/docker/notary/tuf/data" "github.com/docker/notary/tuf/data"
"io"
) )
// KeyService provides management of keys locally. It will never // KeyService provides management of keys locally. It will never
@ -11,9 +10,10 @@ import (
type KeyService interface { type KeyService interface {
// Create issues a new key pair and is responsible for loading // Create issues a new key pair and is responsible for loading
// the private key into the appropriate signing service. // the private key into the appropriate signing service.
// The role isn't currently used for anything, but it's here to support Create(role, gun, algorithm string) (data.PublicKey, error)
// future features
Create(role, algorithm string) (data.PublicKey, error) // AddKey adds a private key to the specified role and gun
AddKey(role, gun string, key data.PrivateKey) error
// GetKey retrieves the public key if present, otherwise it returns nil // GetKey retrieves the public key if present, otherwise it returns nil
GetKey(keyID string) data.PublicKey GetKey(keyID string) data.PublicKey
@ -30,10 +30,6 @@ type KeyService interface {
// ListAllKeys returns a map of all available signing key IDs to role // ListAllKeys returns a map of all available signing key IDs to role
ListAllKeys() map[string]string ListAllKeys() map[string]string
// ImportRootKey imports a root key to the highest priority keystore associated with
// the cryptoservice
ImportRootKey(source io.Reader) error
} }
// CryptoService is deprecated and all instances of its use should be // CryptoService is deprecated and all instances of its use should be

View file

@ -22,10 +22,13 @@ import (
// Sign takes a data.Signed and a key, calculated and adds the signature // Sign takes a data.Signed and a key, calculated and adds the signature
// to the data.Signed // to the data.Signed
// N.B. All public keys for a role should be passed so that this function
// can correctly clean up signatures that are no longer valid.
func Sign(service CryptoService, s *data.Signed, keys ...data.PublicKey) error { func Sign(service CryptoService, s *data.Signed, keys ...data.PublicKey) error {
logrus.Debugf("sign called with %d keys", len(keys)) logrus.Debugf("sign called with %d keys", len(keys))
signatures := make([]data.Signature, 0, len(s.Signatures)+1) signatures := make([]data.Signature, 0, len(s.Signatures)+1)
signingKeyIDs := make(map[string]struct{}) signingKeyIDs := make(map[string]struct{})
tufIDs := make(map[string]data.PublicKey)
ids := make([]string, 0, len(keys)) ids := make([]string, 0, len(keys))
privKeys := make(map[string]data.PrivateKey) privKeys := make(map[string]data.PrivateKey)
@ -34,6 +37,7 @@ func Sign(service CryptoService, s *data.Signed, keys ...data.PublicKey) error {
for _, key := range keys { for _, key := range keys {
canonicalID, err := utils.CanonicalKeyID(key) canonicalID, err := utils.CanonicalKeyID(key)
ids = append(ids, canonicalID) ids = append(ids, canonicalID)
tufIDs[key.ID()] = key
if err != nil { if err != nil {
continue continue
} }
@ -51,7 +55,7 @@ func Sign(service CryptoService, s *data.Signed, keys ...data.PublicKey) error {
// Do signing and generate list of signatures // Do signing and generate list of signatures
for keyID, pk := range privKeys { for keyID, pk := range privKeys {
sig, err := pk.Sign(rand.Reader, s.Signed, nil) sig, err := pk.Sign(rand.Reader, *s.Signed, nil)
if err != nil { if err != nil {
logrus.Debugf("Failed to sign with key: %s. Reason: %v", keyID, err) logrus.Debugf("Failed to sign with key: %s. Reason: %v", keyID, err)
continue continue
@ -78,6 +82,20 @@ func Sign(service CryptoService, s *data.Signed, keys ...data.PublicKey) error {
// key is in the set of key IDs for which a signature has been created // key is in the set of key IDs for which a signature has been created
continue continue
} }
var (
k data.PublicKey
ok bool
)
if k, ok = tufIDs[sig.KeyID]; !ok {
// key is no longer a valid signing key
continue
}
if err := VerifySignature(*s.Signed, sig, k); err != nil {
// signature is no longer valid
continue
}
// keep any signatures that still represent valid keys and are
// themselves valid
signatures = append(signatures, sig) signatures = append(signatures, sig)
} }
s.Signatures = signatures s.Signatures = signatures

View file

@ -2,6 +2,7 @@ package signed
import ( import (
"errors" "errors"
"fmt"
"strings" "strings"
"time" "time"
@ -28,7 +29,7 @@ func VerifyRoot(s *data.Signed, minVersion int, keys map[string]data.PublicKey)
} }
var decoded map[string]interface{} var decoded map[string]interface{}
if err := json.Unmarshal(s.Signed, &decoded); err != nil { if err := json.Unmarshal(*s.Signed, &decoded); err != nil {
return err return err
} }
msg, err := json.MarshalCanonical(decoded) msg, err := json.MarshalCanonical(decoded)
@ -72,7 +73,7 @@ func Verify(s *data.Signed, role data.BaseRole, minVersion int) error {
func verifyMeta(s *data.Signed, role string, minVersion int) error { func verifyMeta(s *data.Signed, role string, minVersion int) error {
sm := &data.SignedCommon{} sm := &data.SignedCommon{}
if err := json.Unmarshal(s.Signed, sm); err != nil { if err := json.Unmarshal(*s.Signed, sm); err != nil {
return err return err
} }
if !data.ValidTUFType(sm.Type, role) { if !data.ValidTUFType(sm.Type, role) {
@ -108,7 +109,7 @@ func VerifySignatures(s *data.Signed, roleData data.BaseRole) error {
// remarshal the signed part so we can verify the signature, since the signature has // remarshal the signed part so we can verify the signature, since the signature has
// to be of a canonically marshalled signed object // to be of a canonically marshalled signed object
var decoded map[string]interface{} var decoded map[string]interface{}
if err := json.Unmarshal(s.Signed, &decoded); err != nil { if err := json.Unmarshal(*s.Signed, &decoded); err != nil {
return err return err
} }
msg, err := json.MarshalCanonical(decoded) msg, err := json.MarshalCanonical(decoded)
@ -124,16 +125,8 @@ func VerifySignatures(s *data.Signed, roleData data.BaseRole) error {
logrus.Debugf("continuing b/c keyid lookup was nil: %s\n", sig.KeyID) logrus.Debugf("continuing b/c keyid lookup was nil: %s\n", sig.KeyID)
continue continue
} }
// method lookup is consistent due to Unmarshal JSON doing lower case for us. if err := VerifySignature(msg, sig, key); err != nil {
method := sig.Method logrus.Debugf("continuing b/c %s", err.Error())
verifier, ok := Verifiers[method]
if !ok {
logrus.Debugf("continuing b/c signing method is not supported: %s\n", sig.Method)
continue
}
if err := verifier.Verify(key, sig.Signature, msg); err != nil {
logrus.Debugf("continuing b/c signature was invalid\n")
continue continue
} }
valid[sig.KeyID] = struct{}{} valid[sig.KeyID] = struct{}{}
@ -145,3 +138,18 @@ func VerifySignatures(s *data.Signed, roleData data.BaseRole) error {
return nil return nil
} }
// VerifySignature checks a single signature and public key against a payload
func VerifySignature(msg []byte, sig data.Signature, pk data.PublicKey) error {
// method lookup is consistent due to Unmarshal JSON doing lower case for us.
method := sig.Method
verifier, ok := Verifiers[method]
if !ok {
return fmt.Errorf("signing method is not supported: %s\n", sig.Method)
}
if err := verifier.Verify(pk, sig.Signature, msg); err != nil {
return fmt.Errorf("signature was invalid\n")
}
return nil
}

View file

@ -577,6 +577,7 @@ func (tr *Repo) VerifyCanSign(roleName string) error {
var ( var (
role data.BaseRole role data.BaseRole
err error err error
canonicalKeyIDs []string
) )
// we only need the BaseRole part of a delegation because we're just // we only need the BaseRole part of a delegation because we're just
// checking KeyIDs // checking KeyIDs
@ -597,6 +598,7 @@ func (tr *Repo) VerifyCanSign(roleName string) error {
check := []string{keyID} check := []string{keyID}
if canonicalID, err := utils.CanonicalKeyID(k); err == nil { if canonicalID, err := utils.CanonicalKeyID(k); err == nil {
check = append(check, canonicalID) check = append(check, canonicalID)
canonicalKeyIDs = append(canonicalKeyIDs, canonicalID)
} }
for _, id := range check { for _, id := range check {
p, _, err := tr.cryptoService.GetPrivateKey(id) p, _, err := tr.cryptoService.GetPrivateKey(id)
@ -605,7 +607,7 @@ func (tr *Repo) VerifyCanSign(roleName string) error {
} }
} }
} }
return signed.ErrNoKeys{KeyIDs: role.ListKeyIDs()} return signed.ErrNoKeys{KeyIDs: canonicalKeyIDs}
} }
// used for walking the targets/delegations tree, potentially modifying the underlying SignedTargets for the repo // used for walking the targets/delegations tree, potentially modifying the underlying SignedTargets for the repo
@ -760,7 +762,7 @@ func (tr *Repo) UpdateSnapshot(role string, s *data.Signed) error {
if err != nil { if err != nil {
return err return err
} }
meta, err := data.NewFileMeta(bytes.NewReader(jsonData), "sha256") meta, err := data.NewFileMeta(bytes.NewReader(jsonData), data.NotaryDefaultHashes...)
if err != nil { if err != nil {
return err return err
} }
@ -775,7 +777,7 @@ func (tr *Repo) UpdateTimestamp(s *data.Signed) error {
if err != nil { if err != nil {
return err return err
} }
meta, err := data.NewFileMeta(bytes.NewReader(jsonData), "sha256") meta, err := data.NewFileMeta(bytes.NewReader(jsonData), data.NotaryDefaultHashes...)
if err != nil { if err != nil {
return err return err
} }
@ -917,12 +919,7 @@ func (tr *Repo) SignTimestamp(expires time.Time) (*data.Signed, error) {
} }
func (tr Repo) sign(signedData *data.Signed, role data.BaseRole) (*data.Signed, error) { func (tr Repo) sign(signedData *data.Signed, role data.BaseRole) (*data.Signed, error) {
ks := role.ListKeys() if err := signed.Sign(tr.cryptoService, signedData, role.ListKeys()...); err != nil {
if len(ks) < 1 {
return nil, signed.ErrNoKeys{}
}
err := signed.Sign(tr.cryptoService, signedData, ks...)
if err != nil {
return nil, err return nil, err
} }
return signedData, nil return signedData, nil

View file

@ -1,7 +1,6 @@
package utils package utils
import ( import (
"bytes"
"crypto/sha256" "crypto/sha256"
"crypto/sha512" "crypto/sha512"
"crypto/tls" "crypto/tls"
@ -34,24 +33,6 @@ func Upload(url string, body io.Reader) (*http.Response, error) {
return client.Post(url, "application/json", body) return client.Post(url, "application/json", body)
} }
// ValidateTarget ensures that the data read from reader matches
// the known metadata
func ValidateTarget(r io.Reader, m *data.FileMeta) error {
h := sha256.New()
length, err := io.Copy(h, r)
if err != nil {
return err
}
if length != m.Length {
return fmt.Errorf("Size of downloaded target did not match targets entry.\nExpected: %d\nReceived: %d\n", m.Length, length)
}
hashDigest := h.Sum(nil)
if bytes.Compare(m.Hashes["sha256"], hashDigest[:]) != 0 {
return fmt.Errorf("Hash of downloaded target did not match targets entry.\nExpected: %x\nReceived: %x\n", m.Hashes["sha256"], hashDigest)
}
return nil
}
// StrSliceContains checks if the given string appears in the slice // StrSliceContains checks if the given string appears in the slice
func StrSliceContains(ss []string, s string) bool { func StrSliceContains(ss []string, s string) bool {
for _, v := range ss { for _, v := range ss {