Merge pull request #22317 from cyli/bump-notary-version
Bump notary version to v0.3.0-RC1
This commit is contained in:
commit
a603fa33d5
|
@ -186,7 +186,7 @@ RUN set -x \
|
|||
&& rm -rf "$GOPATH"
|
||||
|
||||
# Install notary and notary-server
|
||||
ENV NOTARY_VERSION docker-v1.11-3
|
||||
ENV NOTARY_VERSION v0.3.0-RC1
|
||||
RUN set -x \
|
||||
&& export GO15VENDOREXPERIMENT=1 \
|
||||
&& export GOPATH="$(mktemp -d)" \
|
||||
|
|
|
@ -117,7 +117,7 @@ RUN set -x \
|
|||
&& rm -rf "$GOPATH"
|
||||
|
||||
# Install notary and notary-server
|
||||
ENV NOTARY_VERSION docker-v1.11-3
|
||||
ENV NOTARY_VERSION v0.3.0-RC1
|
||||
RUN set -x \
|
||||
&& export GO15VENDOREXPERIMENT=1 \
|
||||
&& export GOPATH="$(mktemp -d)" \
|
||||
|
|
|
@ -128,7 +128,7 @@ RUN set -x \
|
|||
&& rm -rf "$GOPATH"
|
||||
|
||||
# Install notary and notary-server
|
||||
ENV NOTARY_VERSION docker-v1.11-3
|
||||
ENV NOTARY_VERSION v0.3.0-RC1
|
||||
RUN set -x \
|
||||
&& export GO15VENDOREXPERIMENT=1 \
|
||||
&& export GOPATH="$(mktemp -d)" \
|
||||
|
|
|
@ -141,7 +141,7 @@ RUN set -x \
|
|||
&& rm -rf "$GOPATH"
|
||||
|
||||
# Install notary and notary-server
|
||||
ENV NOTARY_VERSION docker-v1.11-3
|
||||
ENV NOTARY_VERSION v0.3.0-RC1
|
||||
RUN set -x \
|
||||
&& export GOPATH="$(mktemp -d)" \
|
||||
&& git clone https://github.com/docker/notary.git "$GOPATH/src/github.com/docker/notary" \
|
||||
|
|
|
@ -130,7 +130,7 @@ RUN set -x \
|
|||
&& rm -rf "$GOPATH"
|
||||
|
||||
# Install notary and notary-server
|
||||
ENV NOTARY_VERSION docker-v1.11-3
|
||||
ENV NOTARY_VERSION v0.3.0-RC1
|
||||
RUN set -x \
|
||||
&& export GO15VENDOREXPERIMENT=1 \
|
||||
&& export GOPATH="$(mktemp -d)" \
|
||||
|
@ -158,7 +158,7 @@ RUN useradd --create-home --gid docker unprivilegeduser
|
|||
|
||||
VOLUME /var/lib/docker
|
||||
WORKDIR /go/src/github.com/docker/docker
|
||||
ENV DOCKER_BUILDTAGS apparmor pkcs11 selinux
|
||||
ENV DOCKER_BUILDTAGS apparmor selinux
|
||||
|
||||
# Let us use a .bashrc file
|
||||
RUN ln -sfv $PWD/.bashrc ~/.bashrc
|
||||
|
|
|
@ -33,6 +33,7 @@ import (
|
|||
"github.com/docker/notary/client"
|
||||
"github.com/docker/notary/passphrase"
|
||||
"github.com/docker/notary/trustmanager"
|
||||
"github.com/docker/notary/trustpinning"
|
||||
"github.com/docker/notary/tuf/data"
|
||||
"github.com/docker/notary/tuf/signed"
|
||||
"github.com/docker/notary/tuf/store"
|
||||
|
@ -184,7 +185,9 @@ func (cli *DockerCli) getNotaryRepository(repoInfo *registry.RepositoryInfo, aut
|
|||
modifiers = append(modifiers, transport.RequestModifier(auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)))
|
||||
tr := transport.NewTransport(base, modifiers...)
|
||||
|
||||
return client.NewNotaryRepository(cli.trustDirectory(), repoInfo.FullName(), server, tr, cli.getPassphraseRetriever())
|
||||
return client.NewNotaryRepository(
|
||||
cli.trustDirectory(), repoInfo.FullName(), server, tr, cli.getPassphraseRetriever(),
|
||||
trustpinning.TrustPinConfig{})
|
||||
}
|
||||
|
||||
func convertTarget(t client.Target) (target, error) {
|
||||
|
@ -474,7 +477,7 @@ func (cli *DockerCli) trustedPush(repoInfo *registry.RepositoryInfo, ref referen
|
|||
}
|
||||
|
||||
// get the latest repository metadata so we can figure out which roles to sign
|
||||
_, err = repo.Update(false)
|
||||
err = repo.Update(false)
|
||||
|
||||
switch err.(type) {
|
||||
case client.ErrRepoNotInitialized, client.ErrRepositoryNotExist:
|
||||
|
|
|
@ -53,7 +53,7 @@ clone git github.com/docker/distribution 9ec0d742d69f77caa4dd5f49ceb70c3067d39f3
|
|||
clone git github.com/vbatts/tar-split v0.9.11
|
||||
|
||||
# get desired notary commit, might also need to be updated in Dockerfile
|
||||
clone git github.com/docker/notary docker-v1.11-3
|
||||
clone git github.com/docker/notary v0.3.0-RC1
|
||||
|
||||
clone git google.golang.org/grpc a22b6611561e9f0a3e0919690dd2caf48f14c517 https://github.com/grpc/grpc-go.git
|
||||
clone git github.com/miekg/pkcs11 df8ae6ca730422dba20c768ff38ef7d79077a59f
|
||||
|
|
|
@ -1,15 +1,35 @@
|
|||
FROM golang:1.6.0
|
||||
FROM golang:1.6.1
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
curl \
|
||||
clang \
|
||||
libltdl-dev \
|
||||
libsqlite3-dev \
|
||||
patch \
|
||||
tar \
|
||||
xz-utils \
|
||||
--no-install-recommends \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN go get golang.org/x/tools/cmd/vet \
|
||||
&& go get golang.org/x/tools/cmd/cover \
|
||||
&& go get github.com/tools/godep
|
||||
RUN go get golang.org/x/tools/cmd/cover
|
||||
|
||||
COPY . /go/src/github.com/docker/notary
|
||||
# Configure the container for OSX cross compilation
|
||||
ENV OSX_SDK MacOSX10.11.sdk
|
||||
ENV OSX_CROSS_COMMIT 8aa9b71a394905e6c5f4b59e2b97b87a004658a4
|
||||
RUN set -x \
|
||||
&& export OSXCROSS_PATH="/osxcross" \
|
||||
&& git clone https://github.com/tpoechtrager/osxcross.git $OSXCROSS_PATH \
|
||||
&& ( cd $OSXCROSS_PATH && git checkout -q $OSX_CROSS_COMMIT) \
|
||||
&& curl -sSL https://s3.dockerproject.org/darwin/v2/${OSX_SDK}.tar.xz -o "${OSXCROSS_PATH}/tarballs/${OSX_SDK}.tar.xz" \
|
||||
&& UNATTENDED=yes OSX_VERSION_MIN=10.6 ${OSXCROSS_PATH}/build.sh > /dev/null
|
||||
ENV PATH /osxcross/target/bin:$PATH
|
||||
|
||||
WORKDIR /go/src/github.com/docker/notary
|
||||
ENV NOTARYDIR /go/src/github.com/docker/notary
|
||||
|
||||
COPY . ${NOTARYDIR}
|
||||
|
||||
ENV GOPATH ${NOTARYDIR}/Godeps/_workspace:$GOPATH
|
||||
|
||||
WORKDIR ${NOTARYDIR}
|
||||
|
||||
# Note this cannot use alpine because of the MacOSX Cross SDK: the cctools there uses sys/cdefs.h and that cannot be used in alpine: http://wiki.musl-libc.org/wiki/FAQ#Q:_I.27m_trying_to_compile_something_against_musl_and_I_get_error_messages_about_sys.2Fcdefs.h
|
||||
|
|
|
@ -13,14 +13,18 @@ endif
|
|||
CTIMEVAR=-X $(NOTARY_PKG)/version.GitCommit=$(GITCOMMIT) -X $(NOTARY_PKG)/version.NotaryVersion=$(NOTARY_VERSION)
|
||||
GO_LDFLAGS=-ldflags "-w $(CTIMEVAR)"
|
||||
GO_LDFLAGS_STATIC=-ldflags "-w $(CTIMEVAR) -extldflags -static"
|
||||
GOOSES = darwin freebsd linux
|
||||
GOARCHS = amd64
|
||||
GOOSES = darwin linux
|
||||
NOTARY_BUILDTAGS ?= pkcs11
|
||||
GO_EXC = go
|
||||
NOTARYDIR := /go/src/github.com/docker/notary
|
||||
|
||||
GO_VERSION := $(shell go version | grep "1\.[6-9]\(\.[0-9]+\)*")
|
||||
# check to make sure we have the right version
|
||||
ifeq ($(strip $(GO_VERSION)),)
|
||||
$(error Bad Go version - please install Go >= 1.6)
|
||||
endif
|
||||
|
||||
# check to be sure pkcs11 lib is always imported with a build tag
|
||||
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)
|
||||
GO_LIST_PKCS11 := $(shell go list -tags "${NOTARY_BUILDTAGS}" -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),)
|
||||
$(info pkcs11 import was not found anywhere without a build tag, yay)
|
||||
else
|
||||
|
@ -34,7 +38,7 @@ _space := $(empty) $(empty)
|
|||
COVERDIR=.cover
|
||||
COVERPROFILE?=$(COVERDIR)/cover.out
|
||||
COVERMODE=count
|
||||
PKGS ?= $(shell go list ./... | grep -v /vendor/ | tr '\n' ' ')
|
||||
PKGS ?= $(shell go list -tags "${NOTARY_BUILDTAGS}" ./... | grep -v /vendor/ | tr '\n' ' ')
|
||||
|
||||
GO_VERSION = $(shell go version | awk '{print $$3}')
|
||||
|
||||
|
@ -53,15 +57,15 @@ version/version.go:
|
|||
|
||||
${PREFIX}/bin/notary-server: NOTARY_VERSION $(shell find . -type f -name '*.go')
|
||||
@echo "+ $@"
|
||||
@godep go build -tags ${NOTARY_BUILDTAGS} -o $@ ${GO_LDFLAGS} ./cmd/notary-server
|
||||
@go build -tags ${NOTARY_BUILDTAGS} -o $@ ${GO_LDFLAGS} ./cmd/notary-server
|
||||
|
||||
${PREFIX}/bin/notary: NOTARY_VERSION $(shell find . -type f -name '*.go')
|
||||
@echo "+ $@"
|
||||
@godep go build -tags ${NOTARY_BUILDTAGS} -o $@ ${GO_LDFLAGS} ./cmd/notary
|
||||
@go build -tags ${NOTARY_BUILDTAGS} -o $@ ${GO_LDFLAGS} ./cmd/notary
|
||||
|
||||
${PREFIX}/bin/notary-signer: NOTARY_VERSION $(shell find . -type f -name '*.go')
|
||||
@echo "+ $@"
|
||||
@godep go build -tags ${NOTARY_BUILDTAGS} -o $@ ${GO_LDFLAGS} ./cmd/notary-signer
|
||||
@go build -tags ${NOTARY_BUILDTAGS} -o $@ ${GO_LDFLAGS} ./cmd/notary-signer
|
||||
|
||||
ifeq ($(shell uname -s),Darwin)
|
||||
${PREFIX}/bin/static/notary-server:
|
||||
|
@ -72,11 +76,11 @@ ${PREFIX}/bin/static/notary-signer:
|
|||
else
|
||||
${PREFIX}/bin/static/notary-server: NOTARY_VERSION $(shell find . -type f -name '*.go')
|
||||
@echo "+ $@"
|
||||
@godep go build -tags ${NOTARY_BUILDTAGS} -o $@ ${GO_LDFLAGS_STATIC} ./cmd/notary-server
|
||||
@go build -tags ${NOTARY_BUILDTAGS} -o $@ ${GO_LDFLAGS_STATIC} ./cmd/notary-server
|
||||
|
||||
${PREFIX}/bin/static/notary-signer: NOTARY_VERSION $(shell find . -type f -name '*.go')
|
||||
@echo "+ $@"
|
||||
@godep go build -tags ${NOTARY_BUILDTAGS} -o $@ ${GO_LDFLAGS_STATIC} ./cmd/notary-signer
|
||||
@go build -tags ${NOTARY_BUILDTAGS} -o $@ ${GO_LDFLAGS_STATIC} ./cmd/notary-signer
|
||||
endif
|
||||
|
||||
vet:
|
||||
|
@ -94,7 +98,7 @@ fmt:
|
|||
|
||||
lint:
|
||||
@echo "+ $@"
|
||||
@test -z "$$(golint ./... | grep -v .pb. | grep -v vendor/ | tee /dev/stderr)"
|
||||
@test -z "$(shell find . -type f -name "*.go" -not -path "./vendor/*" -not -name "*.pb.*" -exec golint {} \; | tee /dev/stderr)"
|
||||
|
||||
# Requires that the following:
|
||||
# go get -u github.com/client9/misspell/cmd/misspell
|
||||
|
@ -126,6 +130,9 @@ test-full: vet lint
|
|||
@echo
|
||||
go test -tags "${NOTARY_BUILDTAGS}" $(TESTOPTS) -v $(PKGS)
|
||||
|
||||
integration:
|
||||
buildscripts/integrationtest.sh development.yml
|
||||
|
||||
protos:
|
||||
@protoc --go_out=plugins=grpc:. proto/*.proto
|
||||
|
||||
|
@ -136,7 +143,7 @@ protos:
|
|||
# be run first
|
||||
|
||||
define gocover
|
||||
$(GO_EXC) test $(OPTS) $(TESTOPTS) -covermode="$(COVERMODE)" -coverprofile="$(COVERDIR)/$(subst /,-,$(1)).$(subst $(_space),.,$(NOTARY_BUILDTAGS)).coverage.txt" "$(1)" || exit 1;
|
||||
go test $(OPTS) $(TESTOPTS) -covermode="$(COVERMODE)" -coverprofile="$(COVERDIR)/$(subst /,-,$(1)).$(subst $(_space),.,$(NOTARY_BUILDTAGS)).coverage.txt" "$(1)" || exit 1;
|
||||
endef
|
||||
|
||||
gen-cover:
|
||||
|
@ -146,15 +153,13 @@ gen-cover:
|
|||
|
||||
# Generates the cover binaries and runs them all in serial, so this can be used
|
||||
# run all tests with a yubikey without any problems
|
||||
cover: GO_EXC := go
|
||||
OPTS = -tags "${NOTARY_BUILDTAGS}" -coverpkg "$(shell ./coverpkg.sh $(1) $(NOTARY_PKG))"
|
||||
cover: OPTS = -tags "${NOTARY_BUILDTAGS}" -coverpkg "$(shell ./coverpkg.sh $(1) $(NOTARY_PKG))"
|
||||
cover: gen-cover covmerge
|
||||
@go tool cover -html="$(COVERPROFILE)"
|
||||
|
||||
# Generates the cover binaries and runs them all in serial, so this can be used
|
||||
# run all tests with a yubikey without any problems
|
||||
ci: OPTS = -tags "${NOTARY_BUILDTAGS}" -race -coverpkg "$(shell ./coverpkg.sh $(1) $(NOTARY_PKG))"
|
||||
GO_EXC := godep go
|
||||
# Codecov knows how to merge multiple coverage files, so covmerge is not needed
|
||||
ci: gen-cover
|
||||
|
||||
|
@ -168,21 +173,15 @@ covmerge:
|
|||
clean-protos:
|
||||
@rm proto/*.pb.go
|
||||
|
||||
client: ${PREFIX}/bin/notary
|
||||
@echo "+ $@"
|
||||
|
||||
binaries: ${PREFIX}/bin/notary-server ${PREFIX}/bin/notary ${PREFIX}/bin/notary-signer
|
||||
@echo "+ $@"
|
||||
|
||||
static: ${PREFIX}/bin/static/notary-server ${PREFIX}/bin/static/notary-signer
|
||||
@echo "+ $@"
|
||||
|
||||
define template
|
||||
mkdir -p ${PREFIX}/cross/$(1)/$(2);
|
||||
GOOS=$(1) GOARCH=$(2) CGO_ENABLED=0 go build -o ${PREFIX}/cross/$(1)/$(2)/notary -a -tags "static_build netgo" -installsuffix netgo ${GO_LDFLAGS_STATIC} ./cmd/notary;
|
||||
endef
|
||||
|
||||
cross:
|
||||
$(foreach GOARCH,$(GOARCHS),$(foreach GOOS,$(GOOSES),$(call template,$(GOOS),$(GOARCH))))
|
||||
|
||||
|
||||
notary-dockerfile:
|
||||
@docker build --rm --force-rm -t notary .
|
||||
|
||||
|
@ -197,6 +196,10 @@ docker-images: notary-dockerfile server-dockerfile signer-dockerfile
|
|||
shell: notary-dockerfile
|
||||
docker run --rm -it -v $(CURDIR)/cross:$(NOTARYDIR)/cross -v $(CURDIR)/bin:$(NOTARYDIR)/bin notary bash
|
||||
|
||||
cross: notary-dockerfile
|
||||
@rm -rf $(CURDIR)/cross
|
||||
docker run --rm -v $(CURDIR)/cross:$(NOTARYDIR)/cross -e NOTARY_BUILDTAGS=$(NOTARY_BUILDTAGS) notary buildscripts/cross.sh $(GOOSES)
|
||||
|
||||
|
||||
clean:
|
||||
@echo "+ $@"
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
# Notary
|
||||
# Notary
|
||||
[![Circle CI](https://circleci.com/gh/docker/notary/tree/master.svg?style=shield)](https://circleci.com/gh/docker/notary/tree/master) [![CodeCov](https://codecov.io/github/docker/notary/coverage.svg?branch=master)](https://codecov.io/github/docker/notary)
|
||||
|
||||
The Notary project comprises a [server](cmd/notary-server) and a [client](cmd/notary) for running and interacting
|
||||
with trusted collections.
|
||||
with trusted collections. Please see the [service architecture](docs/service_architecture.md) documentation
|
||||
for more information.
|
||||
|
||||
|
||||
Notary aims to make the internet more secure by making it easy for people to
|
||||
|
@ -21,7 +22,7 @@ received content.
|
|||
|
||||
## Goals
|
||||
|
||||
Notary is based on [The Update Framework](http://theupdateframework.com/), a secure general design for the problem of software distribution and updates. By using TUF, notary achieves a number of key advantages:
|
||||
Notary is based on [The Update Framework](https://www.theupdateframework.com/), a secure general design for the problem of software distribution and updates. By using TUF, notary achieves a number of key advantages:
|
||||
|
||||
* **Survivable Key Compromise**: Content publishers must manage keys in order to sign their content. Signing keys may be compromised or lost so systems must be designed in order to be flexible and recoverable in the case of key compromise. TUF's notion of key roles is utilized to separate responsibilities across a hierarchy of keys such that loss of any particular key (except the root role) by itself is not fatal to the security of the system.
|
||||
* **Freshness Guarantees**: Replay attacks are a common problem in designing secure systems, where previously valid payloads are replayed to trick another system. The same problem exists in the software update systems, where old signed can be presented as the most recent. notary makes use of timestamping on publishing so that consumers can know that they are receiving the most up to date content. This is particularly important when dealing with software update where old vulnerable versions could be used to attack users.
|
||||
|
@ -30,165 +31,60 @@ Notary is based on [The Update Framework](http://theupdateframework.com/), a sec
|
|||
* **Use of Existing Distribution**: Notary's trust guarantees are not tied at all to particular distribution channels from which content is delivered. Therefore, trust can be added to any existing content delivery mechanism.
|
||||
* **Untrusted Mirrors and Transport**: All of the notary metadata can be mirrored and distributed via arbitrary channels.
|
||||
|
||||
# Notary CLI
|
||||
## Security
|
||||
|
||||
Notary is a tool for publishing and managing trusted collections of content. Publishers can digitally sign collections and consumers can verify integrity and origin of content. This ability is built on a straightforward key management and signing interface to create signed collections and configure trusted publishers.
|
||||
Please see our [service architecture docs](docs/service_architecture.md#threat-model) for more information about our threat model, which details the varying survivability and severities for key compromise as well as mitigations.
|
||||
|
||||
## Using Notary
|
||||
Lets try using notary.
|
||||
Our last security audit was on July 31, 2015 by NCC ([results](docs/resources/ncc_docker_notary_audit_2015_07_31.pdf)).
|
||||
|
||||
Any security vulnerabilities can be reported to security@docker.com.
|
||||
|
||||
# Getting started with the Notary CLI
|
||||
|
||||
Please get the Notary Client CLI binary from [the official releases page](https://github.com/docker/notary/releases) or you can [build one yourself](#building-notary).
|
||||
The version of Notary server and signer should be greater than or equal to Notary CLI's version to ensure feature compatibility (ex: CLI version 0.2, server/signer version >= 0.2), and all official releases are associated with GitHub tags.
|
||||
|
||||
To use the Notary CLI with Docker hub images, please have a look at our
|
||||
[getting started docs](docs/getting_started.md).
|
||||
|
||||
For more advanced usage, please see the
|
||||
[advanced usage docs](docs/advanced_usage.md).
|
||||
|
||||
To use the CLI against a local Notary server rather than against Docker Hub:
|
||||
|
||||
1. Please ensure that you have [docker and docker-compose](http://docs.docker.com/compose/install/) installed.
|
||||
1. `git clone https://github.com/docker/notary.git` and from the cloned repository path,
|
||||
start up a local Notary server and signer and copy the config file and testing certs to your
|
||||
local notary config directory:
|
||||
|
||||
```sh
|
||||
$ docker-compose build
|
||||
$ docker-compose up -d
|
||||
$ mkdir -p ~/.notary && cp cmd/notary/config.json cmd/notary/root-ca.crt ~/.notary
|
||||
```
|
||||
|
||||
1. Add `127.0.0.1 notary-server` to your `/etc/hosts`, or if using docker-machine,
|
||||
add `$(docker-machine ip) notary-server`).
|
||||
|
||||
You can run through the examples in the
|
||||
[getting started docs](docs/getting_started.md) and
|
||||
[advanced usage docs](docs/advanced_usage.md), but
|
||||
without the `-s` (server URL) argument to the `notary` command since the server
|
||||
URL is specified already in the configuration, file you copied.
|
||||
|
||||
You can also leave off the `-d ~/.docker/trust` argument if you do not care
|
||||
to use `notary` with Docker images.
|
||||
|
||||
|
||||
## Building Notary
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Requirements from the [Compiling Notary Server](#compiling-notary-server) section (such as go 1.5.1)
|
||||
- [docker and docker-compose](http://docs.docker.com/compose/install/)
|
||||
- [Notary server configuration](#configuring-notary-server)
|
||||
|
||||
As setup, let's build notary and then start up a local notary-server (don't forget to add `127.0.0.1 notary-server` to your `/etc/hosts`, or if using docker-machine, add `$(docker-machine ip) notary-server`).
|
||||
|
||||
```sh
|
||||
make binaries
|
||||
docker-compose build
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
Note: In order to have notary use the local notary server and development root CA we can load the local development configuration by appending `-c cmd/notary/config.json` to every command. If you would rather not have to use `-c` on every command, copy `cmd/notary/config.json and cmd/notary/root-ca.crt` to `~/.notary`.
|
||||
|
||||
|
||||
First, let's initiate a notary collection called `example.com/scripts`
|
||||
|
||||
```sh
|
||||
notary init example.com/scripts
|
||||
```
|
||||
|
||||
Now, look at the keys you created as a result of initialization
|
||||
```sh
|
||||
notary key list
|
||||
```
|
||||
|
||||
Cool, now add a local file `install.sh` and call it `v1`
|
||||
```sh
|
||||
notary add example.com/scripts v1 install.sh
|
||||
```
|
||||
|
||||
Wouldn't it be nice if others could know that you've signed this content? Use `publish` to publish your collection to your default notary-server
|
||||
```sh
|
||||
notary publish example.com/scripts
|
||||
```
|
||||
|
||||
Now, others can pull your trusted collection
|
||||
```sh
|
||||
notary list example.com/scripts
|
||||
```
|
||||
|
||||
More importantly, they can verify the content of your script by using `notary verify`:
|
||||
```sh
|
||||
curl example.com/install.sh | notary verify example.com/scripts v1 | sh
|
||||
```
|
||||
|
||||
# Notary Server
|
||||
|
||||
Notary Server manages TUF data over an HTTP API compatible with the
|
||||
[notary client](cmd/notary).
|
||||
|
||||
It may be configured to use either JWT or HTTP Basic Auth for authentication.
|
||||
Currently it only supports MySQL for storage of the TUF data, we intend to
|
||||
expand this to other storage options.
|
||||
|
||||
## Setup for Development
|
||||
|
||||
The notary repository comes with Dockerfiles and a docker-compose file
|
||||
to facilitate development. Simply run the following commands to start
|
||||
a notary server with a temporary MySQL database in containers:
|
||||
|
||||
```
|
||||
$ docker-compose build
|
||||
$ docker-compose up
|
||||
```
|
||||
|
||||
If you are on Mac OSX with boot2docker or kitematic, you'll need to
|
||||
update your hosts file such that the name `notary` is associated with
|
||||
the IP address of your VM (for boot2docker, this can be determined
|
||||
by running `boot2docker ip`, with kitematic, `echo $DOCKER_HOST` should
|
||||
show the IP of the VM). If you are using the default Linux setup,
|
||||
you need to add `127.0.0.1 notary` to your hosts file.
|
||||
|
||||
## Successfully connecting over TLS
|
||||
|
||||
By default notary-server runs with TLS with certificates signed by a local
|
||||
CA. In order to be able to successfully connect to it using
|
||||
either `curl` or `openssl`, you will have to use the root CA file in `fixtures/root-ca.crt`.
|
||||
|
||||
OpenSSL example:
|
||||
|
||||
`openssl s_client -connect notary-server:4443 -CAfile fixtures/root-ca.crt`
|
||||
|
||||
## Compiling Notary Server
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Go = 1.5.1
|
||||
- Go >= 1.6.1
|
||||
- [godep](https://github.com/tools/godep) installed
|
||||
- libtool development headers installed
|
||||
- Ubuntu: `apt-get install libtool-dev`
|
||||
- CentOS/RedHat: `yum install libtool-ltdl-devel`
|
||||
- Mac OS ([Homebrew](http://brew.sh/)): `brew install libtool`
|
||||
|
||||
Install dependencies by running `godep restore`.
|
||||
|
||||
From the root of this git repository, run `make binaries`. This will
|
||||
compile the `notary`, `notary-server`, and `notary-signer` applications and
|
||||
place them in a `bin` directory at the root of the git repository (the `bin`
|
||||
directory is ignored by the .gitignore file).
|
||||
|
||||
`notary-signer` depends upon `pkcs11`, which requires that libtool headers be installed (`libtool-dev` on Ubuntu, `libtool-ltdl-devel` on CentOS/RedHat). If you are using Mac OS, you can `brew install libtool`, and run `make binaries` with the following environment variables (assuming a standard installation of Homebrew):
|
||||
|
||||
```sh
|
||||
export CPATH=/usr/local/include:${CPATH}
|
||||
export LIBRARY_PATH=/usr/local/lib:${LIBRARY_PATH}
|
||||
```
|
||||
|
||||
## Running Notary Server
|
||||
|
||||
The `notary-server` application has the following usage:
|
||||
|
||||
```
|
||||
$ bin/notary-server --help
|
||||
usage: bin/notary-serve
|
||||
-config="": Path to configuration file
|
||||
-debug=false: Enable the debugging server on localhost:8080
|
||||
```
|
||||
|
||||
## Configuring Notary Server
|
||||
|
||||
The configuration file must be a json file with the following format:
|
||||
|
||||
```json
|
||||
{
|
||||
"server": {
|
||||
"addr": ":4443",
|
||||
"tls_cert_file": "./fixtures/notary-server.crt",
|
||||
"tls_key_file": "./fixtures/notary-server.key"
|
||||
},
|
||||
"logging": {
|
||||
"level": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The pem and key provided in fixtures are purely for local development and
|
||||
testing. For production, you must create your own keypair and certificate,
|
||||
either via the CA of your choice, or a self signed certificate.
|
||||
|
||||
If using the pem and key provided in fixtures, either:
|
||||
- Add `fixtures/root-ca.crt` to your trusted root certificates
|
||||
- Use the default configuration for notary client that loads the CA root for you by using the flag `-c ./cmd/notary/config.json`
|
||||
- Disable TLS verification by adding the following option notary configuration file in `~/.notary/config.json`:
|
||||
|
||||
"skipTLSVerify": true
|
||||
|
||||
Otherwise, you will see TLS errors or X509 errors upon initializing the
|
||||
notary collection:
|
||||
|
||||
```
|
||||
$ notary list diogomonica.com/openvpn
|
||||
* fatal: Get https://notary-server:4443/v2/: x509: certificate signed by unknown authority
|
||||
$ notary list diogomonica.com/openvpn -c cmd/notary/config.json
|
||||
latest b1df2ad7cbc19f06f08b69b4bcd817649b509f3e5420cdd2245a85144288e26d 4056
|
||||
```
|
||||
Run `make binaries`, which creates the Notary Client CLI binary at `bin/notary`.
|
||||
|
|
|
@ -3,10 +3,18 @@ machine:
|
|||
pre:
|
||||
# Install gvm
|
||||
- bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/1.0.22/binscripts/gvm-installer)
|
||||
# Upgrade docker
|
||||
- sudo curl -L -o /usr/bin/docker 'https://s3-external-1.amazonaws.com/circle-downloads/docker-1.9.1-circleci'
|
||||
- sudo chmod 0755 /usr/bin/docker
|
||||
|
||||
post:
|
||||
# Install many go versions
|
||||
- gvm install go1.6 -B --name=stable
|
||||
- gvm install go1.6.1 -B --name=stable
|
||||
# upgrade compose
|
||||
- sudo pip install --upgrade docker-compose
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
environment:
|
||||
# Convenient shortcuts to "common" locations
|
||||
|
@ -32,16 +40,13 @@ dependencies:
|
|||
cp -R "$CHECKOUT" "$BASE_STABLE"
|
||||
|
||||
override:
|
||||
# Install dependencies for every copied clone/go version
|
||||
- gvm use stable && go get github.com/tools/godep:
|
||||
pwd: $BASE_STABLE
|
||||
|
||||
post:
|
||||
# For the stable go version, additionally install linting and misspell tools
|
||||
# don't use circleci's default dependency installation step of `go get -d -u ./...`
|
||||
# since we already vendor everything; additionally install linting and misspell tools
|
||||
- >
|
||||
gvm use stable &&
|
||||
go get github.com/golang/lint/golint &&
|
||||
go get -u github.com/client9/misspell/cmd/misspell
|
||||
|
||||
test:
|
||||
pre:
|
||||
# Output the go versions we are going to test
|
||||
|
@ -70,13 +75,13 @@ test:
|
|||
override:
|
||||
# Test stable, and report
|
||||
# hacking this to be parallel
|
||||
- case $CIRCLE_NODE_INDEX in 0) gvm use stable && NOTARY_BUILDTAGS=pkcs11 make ci ;; 1) gvm use stable && NOTARY_BUILDTAGS=none make ci ;; esac:
|
||||
- case $CIRCLE_NODE_INDEX in 0) gvm use stable && NOTARY_BUILDTAGS=pkcs11 make ci ;; 1) gvm use stable && NOTARY_BUILDTAGS=none make ci ;; 2) gvm use stable && make integration ;; esac:
|
||||
parallel: true
|
||||
timeout: 600
|
||||
pwd: $BASE_STABLE
|
||||
|
||||
post:
|
||||
# Report to codecov.io
|
||||
- bash <(curl -s https://codecov.io/bash):
|
||||
- case $CIRCLE_NODE_INDEX in 0) bash <(curl -s https://codecov.io/bash) ;; 1) bash <(curl -s https://codecov.io/bash) ;; esac:
|
||||
parallel: true
|
||||
pwd: $BASE_STABLE
|
||||
|
|
|
@ -2,6 +2,7 @@ package client
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
@ -14,10 +15,10 @@ import (
|
|||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/notary"
|
||||
"github.com/docker/notary/certs"
|
||||
"github.com/docker/notary/client/changelist"
|
||||
"github.com/docker/notary/cryptoservice"
|
||||
"github.com/docker/notary/trustmanager"
|
||||
"github.com/docker/notary/trustpinning"
|
||||
"github.com/docker/notary/tuf"
|
||||
tufclient "github.com/docker/notary/tuf/client"
|
||||
"github.com/docker/notary/tuf/data"
|
||||
|
@ -87,13 +88,14 @@ type NotaryRepository struct {
|
|||
tufRepo *tuf.Repo
|
||||
roundTrip http.RoundTripper
|
||||
CertStore trustmanager.X509Store
|
||||
trustPinning trustpinning.TrustPinConfig
|
||||
}
|
||||
|
||||
// repositoryFromKeystores is a helper function for NewNotaryRepository that
|
||||
// takes some basic NotaryRepository parameters as well as keystores (in order
|
||||
// of usage preference), and returns a NotaryRepository.
|
||||
func repositoryFromKeystores(baseDir, gun, baseURL string, rt http.RoundTripper,
|
||||
keyStores []trustmanager.KeyStore) (*NotaryRepository, error) {
|
||||
keyStores []trustmanager.KeyStore, trustPin trustpinning.TrustPinConfig) (*NotaryRepository, error) {
|
||||
|
||||
certPath := filepath.Join(baseDir, notary.TrustedCertsDir)
|
||||
certStore, err := trustmanager.NewX509FilteredFileStore(
|
||||
|
@ -114,6 +116,7 @@ func repositoryFromKeystores(baseDir, gun, baseURL string, rt http.RoundTripper,
|
|||
CryptoService: cryptoService,
|
||||
roundTrip: rt,
|
||||
CertStore: certStore,
|
||||
trustPinning: trustPin,
|
||||
}
|
||||
|
||||
fileStore, err := store.NewFilesystemStore(
|
||||
|
@ -159,8 +162,29 @@ func NewTarget(targetName string, targetPath string) (*Target, error) {
|
|||
return &Target{Name: targetName, Hashes: meta.Hashes, Length: meta.Length}, nil
|
||||
}
|
||||
|
||||
func rootCertKey(gun string, privKey data.PrivateKey) (*x509.Certificate, data.PublicKey, error) {
|
||||
// Hard-coded policy: the generated certificate expires in 10 years.
|
||||
startTime := time.Now()
|
||||
cert, err := cryptoservice.GenerateCertificate(
|
||||
privKey, gun, startTime, startTime.Add(notary.Year*10))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
x509PublicKey := trustmanager.CertToKey(cert)
|
||||
if x509PublicKey == nil {
|
||||
return nil, nil, fmt.Errorf(
|
||||
"cannot use regenerated certificate: format %s", cert.PublicKeyAlgorithm)
|
||||
}
|
||||
|
||||
return cert, x509PublicKey, nil
|
||||
}
|
||||
|
||||
// Initialize creates a new repository by using rootKey as the root Key for the
|
||||
// TUF repository.
|
||||
// TUF repository. The server must be reachable (and is asked to generate a
|
||||
// timestamp key and possibly other serverManagedRoles), but the created repository
|
||||
// result is only stored on local disk, not published to the server. To do that,
|
||||
// use r.Publish() eventually.
|
||||
func (r *NotaryRepository) Initialize(rootKeyID string, serverManagedRoles ...string) error {
|
||||
privKey, _, err := r.CryptoService.GetPrivateKey(rootKeyID)
|
||||
if err != nil {
|
||||
|
@ -194,31 +218,12 @@ func (r *NotaryRepository) Initialize(rootKeyID string, serverManagedRoles ...st
|
|||
}
|
||||
}
|
||||
|
||||
// Hard-coded policy: the generated certificate expires in 10 years.
|
||||
startTime := time.Now()
|
||||
rootCert, err := cryptoservice.GenerateCertificate(
|
||||
privKey, r.gun, startTime, startTime.AddDate(10, 0, 0))
|
||||
|
||||
rootCert, rootKey, err := rootCertKey(r.gun, privKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.CertStore.AddCert(rootCert)
|
||||
|
||||
// The root key gets stored in the TUF metadata X509 encoded, linking
|
||||
// the tuf root.json to our X509 PKI.
|
||||
// If the key is RSA, we store it as type RSAx509, if it is ECDSA we store it
|
||||
// as ECDSAx509 to allow the gotuf verifiers to correctly decode the
|
||||
// key on verification of signatures.
|
||||
var rootKey data.PublicKey
|
||||
switch privKey.Algorithm() {
|
||||
case data.RSAKey:
|
||||
rootKey = data.NewRSAx509PublicKey(trustmanager.CertToPEM(rootCert))
|
||||
case data.ECDSAKey:
|
||||
rootKey = data.NewECDSAx509PublicKey(trustmanager.CertToPEM(rootCert))
|
||||
default:
|
||||
return fmt.Errorf("invalid format for root key: %s", privKey.Algorithm())
|
||||
}
|
||||
|
||||
var (
|
||||
rootRole = data.NewBaseRole(
|
||||
data.CanonicalRootRole,
|
||||
|
@ -341,9 +346,12 @@ func addChange(cl *changelist.FileChangelist, c changelist.Change, roles ...stri
|
|||
|
||||
// AddTarget creates new changelist entries to add a target to the given roles
|
||||
// in the repository when the changelist gets applied at publish time.
|
||||
// If roles are unspecified, the default role is "targets".
|
||||
// If roles are unspecified, the default role is "targets"
|
||||
func (r *NotaryRepository) AddTarget(target *Target, roles ...string) error {
|
||||
|
||||
if len(target.Hashes) == 0 {
|
||||
return fmt.Errorf("no hashes specified for target \"%s\"", target.Name)
|
||||
}
|
||||
cl, err := changelist.NewFileChangelist(filepath.Join(r.tufRepoPath, "changelist"))
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -386,7 +394,7 @@ func (r *NotaryRepository) RemoveTarget(targetName string, roles ...string) erro
|
|||
// subtree and also the "targets/x" subtree, as we will defer parsing it until
|
||||
// we explicitly reach it in our iteration of the provided list of roles.
|
||||
func (r *NotaryRepository) ListTargets(roles ...string) ([]*TargetWithRole, error) {
|
||||
_, err := r.Update(false)
|
||||
err := r.Update(false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -432,8 +440,7 @@ func (r *NotaryRepository) ListTargets(roles ...string) ([]*TargetWithRole, erro
|
|||
// will be returned
|
||||
// See the IMPORTANT section on ListTargets above. Those roles also apply here.
|
||||
func (r *NotaryRepository) GetTargetByName(name string, roles ...string) (*TargetWithRole, error) {
|
||||
_, err := r.Update(false)
|
||||
if err != nil {
|
||||
if err := r.Update(false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -460,9 +467,8 @@ func (r *NotaryRepository) GetTargetByName(name string, roles ...string) (*Targe
|
|||
}
|
||||
return nil
|
||||
}
|
||||
err = r.tufRepo.WalkTargets(name, role, getTargetVisitorFunc, skipRoles...)
|
||||
// Check that we didn't error, and that we assigned to our target
|
||||
if err == nil && foundTarget {
|
||||
if err := r.tufRepo.WalkTargets(name, role, getTargetVisitorFunc, skipRoles...); err == nil && foundTarget {
|
||||
return &TargetWithRole{Target: Target{Name: name, Hashes: resultMeta.Hashes, Length: resultMeta.Length}, Role: resultRoleName}, nil
|
||||
}
|
||||
}
|
||||
|
@ -491,8 +497,7 @@ type RoleWithSignatures struct {
|
|||
// This represents the latest metadata for each role in this repo
|
||||
func (r *NotaryRepository) ListRoles() ([]RoleWithSignatures, error) {
|
||||
// Update to latest repo state
|
||||
_, err := r.Update(false)
|
||||
if err != nil {
|
||||
if err := r.Update(false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -514,9 +519,8 @@ func (r *NotaryRepository) ListRoles() ([]RoleWithSignatures, error) {
|
|||
case data.CanonicalTimestampRole:
|
||||
roleWithSig.Signatures = r.tufRepo.Timestamp.Signatures
|
||||
default:
|
||||
// If the role isn't a delegation, we should error -- this is only possible if we have invalid state
|
||||
if !data.IsDelegation(role.Name) {
|
||||
return nil, data.ErrInvalidRole{Role: role.Name, Reason: "invalid role name"}
|
||||
continue
|
||||
}
|
||||
if _, ok := r.tufRepo.Targets[role.Name]; ok {
|
||||
// We'll only find a signature if we've published any targets with this delegation
|
||||
|
@ -552,8 +556,7 @@ func (r *NotaryRepository) Publish() error {
|
|||
func (r *NotaryRepository) publish(cl changelist.Changelist) error {
|
||||
var initialPublish bool
|
||||
// update first before publishing
|
||||
_, err := r.Update(true)
|
||||
if err != nil {
|
||||
if err := r.Update(true); err != nil {
|
||||
// If the remote is not aware of the repo, then this is being published
|
||||
// for the first time. Try to load from disk instead for publishing.
|
||||
if _, ok := err.(ErrRepositoryNotExist); ok {
|
||||
|
@ -578,8 +581,7 @@ func (r *NotaryRepository) publish(cl changelist.Changelist) error {
|
|||
}
|
||||
}
|
||||
// apply the changelist to the repo
|
||||
err = applyChangelist(r.tufRepo, cl)
|
||||
if err != nil {
|
||||
if err := applyChangelist(r.tufRepo, cl); err != nil {
|
||||
logrus.Debug("Error applying changelist")
|
||||
return err
|
||||
}
|
||||
|
@ -631,7 +633,7 @@ func (r *NotaryRepository) publish(cl changelist.Changelist) error {
|
|||
if err == nil {
|
||||
// Only update the snapshot if we've successfully signed it.
|
||||
updatedFiles[data.CanonicalSnapshotRole] = snapshotJSON
|
||||
} else if _, ok := err.(signed.ErrNoKeys); ok {
|
||||
} else if signErr, ok := err.(signed.ErrInsufficientSignatures); ok && signErr.FoundKeys == 0 {
|
||||
// If signing fails due to us not having the snapshot key, then
|
||||
// assume the server is going to sign, and do not include any snapshot
|
||||
// data.
|
||||
|
@ -650,16 +652,17 @@ func (r *NotaryRepository) publish(cl changelist.Changelist) error {
|
|||
return remote.SetMultiMeta(updatedFiles)
|
||||
}
|
||||
|
||||
// bootstrapRepo loads the repository from the local file system. This attempts
|
||||
// to load metadata for all roles. Since server snapshots are supported,
|
||||
// if the snapshot metadata fails to load, that's ok.
|
||||
// bootstrapRepo loads the repository from the local file system (i.e.
|
||||
// a not yet published repo or a possibly obsolete local copy) into
|
||||
// r.tufRepo. This attempts to load metadata for all roles. Since server
|
||||
// snapshots are supported, if the snapshot metadata fails to load, that's ok.
|
||||
// This can also be unified with some cache reading tools from tuf/client.
|
||||
// This assumes that bootstrapRepo is only used by Publish() or RotateKey()
|
||||
func (r *NotaryRepository) bootstrapRepo() error {
|
||||
tufRepo := tuf.NewRepo(r.CryptoService)
|
||||
|
||||
logrus.Debugf("Loading trusted collection.")
|
||||
rootJSON, err := r.fileStore.GetMeta("root", -1)
|
||||
rootJSON, err := r.fileStore.GetMeta(data.CanonicalRootRole, -1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -672,7 +675,7 @@ func (r *NotaryRepository) bootstrapRepo() error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targetsJSON, err := r.fileStore.GetMeta("targets", -1)
|
||||
targetsJSON, err := r.fileStore.GetMeta(data.CanonicalTargetsRole, -1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -681,9 +684,9 @@ func (r *NotaryRepository) bootstrapRepo() error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tufRepo.SetTargets("targets", targets)
|
||||
tufRepo.SetTargets(data.CanonicalTargetsRole, targets)
|
||||
|
||||
snapshotJSON, err := r.fileStore.GetMeta("snapshot", -1)
|
||||
snapshotJSON, err := r.fileStore.GetMeta(data.CanonicalSnapshotRole, -1)
|
||||
if err == nil {
|
||||
snapshot := &data.SignedSnapshot{}
|
||||
err = json.Unmarshal(snapshotJSON, snapshot)
|
||||
|
@ -700,6 +703,8 @@ func (r *NotaryRepository) bootstrapRepo() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// saveMetadata saves contents of r.tufRepo onto the local disk, creating
|
||||
// signatures as necessary, possibly prompting for passphrases.
|
||||
func (r *NotaryRepository) saveMetadata(ignoreSnapshot bool) error {
|
||||
logrus.Debugf("Saving changes to Trusted Collection.")
|
||||
|
||||
|
@ -714,7 +719,7 @@ func (r *NotaryRepository) saveMetadata(ignoreSnapshot bool) error {
|
|||
|
||||
targetsToSave := make(map[string][]byte)
|
||||
for t := range r.tufRepo.Targets {
|
||||
signedTargets, err := r.tufRepo.SignTargets(t, data.DefaultExpires("targets"))
|
||||
signedTargets, err := r.tufRepo.SignTargets(t, data.DefaultExpires(data.CanonicalTargetsRole))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -756,25 +761,24 @@ func (r *NotaryRepository) errRepositoryNotExist() error {
|
|||
|
||||
// Update bootstraps a trust anchor (root.json) before updating all the
|
||||
// metadata from the repo.
|
||||
func (r *NotaryRepository) Update(forWrite bool) (*tufclient.Client, error) {
|
||||
func (r *NotaryRepository) Update(forWrite bool) error {
|
||||
c, err := r.bootstrapClient(forWrite)
|
||||
if err != nil {
|
||||
if _, ok := err.(store.ErrMetaNotFound); ok {
|
||||
return nil, r.errRepositoryNotExist()
|
||||
return r.errRepositoryNotExist()
|
||||
}
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
err = c.Update()
|
||||
if err != nil {
|
||||
if err := c.Update(); err != nil {
|
||||
// notFound.Resource may include a checksum so when the role is root,
|
||||
// it will be root.json or root.<checksum>.json. Therefore best we can
|
||||
// do it match a "root." prefix
|
||||
if notFound, ok := err.(store.ErrMetaNotFound); ok && strings.HasPrefix(notFound.Resource, data.CanonicalRootRole+".") {
|
||||
return nil, r.errRepositoryNotExist()
|
||||
return r.errRepositoryNotExist()
|
||||
}
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
return c, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// bootstrapClient attempts to bootstrap a root.json to be used as the trust
|
||||
|
@ -782,6 +786,20 @@ func (r *NotaryRepository) Update(forWrite bool) (*tufclient.Client, error) {
|
|||
// we should always attempt to contact the server to determine if the repository
|
||||
// is initialized or not. If set to true, we will always attempt to download
|
||||
// and return an error if the remote repository errors.
|
||||
//
|
||||
// Partially populates r.tufRepo with this root metadata (only; use
|
||||
// tufclient.Client.Update to load the rest).
|
||||
//
|
||||
// As another side effect, r.CertManager's list of trusted certificates
|
||||
// is updated with data from the loaded root.json.
|
||||
//
|
||||
// Fails if the remote server is reachable and does not know the repo
|
||||
// (i.e. before the first r.Publish()), in which case the error is
|
||||
// store.ErrMetaNotFound, or if the root metadata (from whichever source is used)
|
||||
// is not trusted.
|
||||
//
|
||||
// Returns a tufclient.Client for the remote server, which may not be actually
|
||||
// operational (if the URL is invalid but a root.json is cached).
|
||||
func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Client, error) {
|
||||
var (
|
||||
rootJSON []byte
|
||||
|
@ -791,7 +809,7 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl
|
|||
// try to read root from cache first. We will trust this root
|
||||
// until we detect a problem during update which will cause
|
||||
// us to download a new root and perform a rotation.
|
||||
rootJSON, cachedRootErr := r.fileStore.GetMeta("root", -1)
|
||||
rootJSON, cachedRootErr := r.fileStore.GetMeta(data.CanonicalRootRole, -1)
|
||||
|
||||
if cachedRootErr == nil {
|
||||
signedRoot, cachedRootErr = r.validateRoot(rootJSON)
|
||||
|
@ -806,7 +824,7 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl
|
|||
|
||||
// if remote store successfully set up, try and get root from remote
|
||||
// We don't have any local data to determine the size of root, so try the maximum (though it is restricted at 100MB)
|
||||
tmpJSON, err := remote.GetMeta("root", -1)
|
||||
tmpJSON, err := remote.GetMeta(data.CanonicalRootRole, -1)
|
||||
if err != nil {
|
||||
// we didn't have a root in cache and were unable to load one from
|
||||
// the server. Nothing we can do but error.
|
||||
|
@ -820,7 +838,7 @@ func (r *NotaryRepository) bootstrapClient(checkInitialized bool) (*tufclient.Cl
|
|||
return nil, err
|
||||
}
|
||||
|
||||
err = r.fileStore.SetMeta("root", tmpJSON)
|
||||
err = r.fileStore.SetMeta(data.CanonicalRootRole, tmpJSON)
|
||||
if err != nil {
|
||||
// if we can't write cache we should still continue, just log error
|
||||
logrus.Errorf("could not save root to cache: %s", err.Error())
|
||||
|
@ -860,7 +878,7 @@ func (r *NotaryRepository) validateRoot(rootJSON []byte) (*data.SignedRoot, erro
|
|||
return nil, err
|
||||
}
|
||||
|
||||
err = certs.ValidateRoot(r.CertStore, root, r.gun)
|
||||
err = trustpinning.ValidateRoot(r.CertStore, root, r.gun, r.trustPinning)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -872,25 +890,19 @@ func (r *NotaryRepository) validateRoot(rootJSON []byte) (*data.SignedRoot, erro
|
|||
// 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.
|
||||
func (r *NotaryRepository) RotateKey(role string, serverManagesKey bool) error {
|
||||
// We currently support remotely managing timestamp and snapshot keys
|
||||
canBeRemoteKey := role == data.CanonicalTimestampRole || role == data.CanonicalSnapshotRole
|
||||
// And locally managing root, targets, and snapshot keys
|
||||
canBeLocalKey := (role == data.CanonicalSnapshotRole || role == data.CanonicalTargetsRole ||
|
||||
role == data.CanonicalRootRole)
|
||||
|
||||
switch {
|
||||
// We currently support locally or remotely managing snapshot keys...
|
||||
case role == data.CanonicalSnapshotRole:
|
||||
break
|
||||
|
||||
// locally managing targets keys only
|
||||
case role == data.CanonicalTargetsRole && !serverManagesKey:
|
||||
break
|
||||
case role == data.CanonicalTargetsRole && serverManagesKey:
|
||||
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:
|
||||
case !data.ValidRole(role) || data.IsDelegation(role):
|
||||
return fmt.Errorf("notary does not currently permit rotating the %s key", role)
|
||||
case serverManagesKey && !canBeRemoteKey:
|
||||
return ErrInvalidRemoteRole{Role: role}
|
||||
case !serverManagesKey && !canBeLocalKey:
|
||||
return ErrInvalidLocalRole{Role: role}
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -911,6 +923,18 @@ func (r *NotaryRepository) RotateKey(role string, serverManagesKey bool) error {
|
|||
return fmt.Errorf(errFmtMsg, err)
|
||||
}
|
||||
|
||||
// if this is a root role, generate a root cert for the public key
|
||||
if role == data.CanonicalRootRole {
|
||||
privKey, _, err := r.CryptoService.GetPrivateKey(pubKey.ID())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, pubKey, err = rootCertKey(r.gun, privKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
cl := changelist.NewMemChangelist()
|
||||
if err := r.rootFileKeyChange(cl, role, changelist.ActionCreate, pubKey); err != nil {
|
||||
return err
|
||||
|
|
|
@ -240,7 +240,7 @@ func newDeleteDelegationChange(name string, content []byte) *changelist.TufChang
|
|||
// Also converts key IDs to canonical key IDs to keep consistent with signing prompts
|
||||
func (r *NotaryRepository) GetDelegationRoles() ([]*data.Role, error) {
|
||||
// Update state of the repo to latest
|
||||
if _, err := r.Update(false); err != nil {
|
||||
if err := r.Update(false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
|
@ -8,13 +8,14 @@ import (
|
|||
|
||||
"github.com/docker/notary/passphrase"
|
||||
"github.com/docker/notary/trustmanager"
|
||||
"github.com/docker/notary/trustpinning"
|
||||
)
|
||||
|
||||
// NewNotaryRepository is a helper method that returns a new notary repository.
|
||||
// It takes the base directory under where all the trust files will be stored
|
||||
// (usually ~/.docker/trust/).
|
||||
func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper,
|
||||
retriever passphrase.Retriever) (
|
||||
retriever passphrase.Retriever, trustPinning trustpinning.TrustPinConfig) (
|
||||
*NotaryRepository, error) {
|
||||
|
||||
fileKeyStore, err := trustmanager.NewKeyFileStore(baseDir, retriever)
|
||||
|
@ -23,5 +24,5 @@ func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper,
|
|||
}
|
||||
|
||||
return repositoryFromKeystores(baseDir, gun, baseURL, rt,
|
||||
[]trustmanager.KeyStore{fileKeyStore})
|
||||
[]trustmanager.KeyStore{fileKeyStore}, trustPinning)
|
||||
}
|
||||
|
|
|
@ -9,13 +9,14 @@ import (
|
|||
"github.com/docker/notary/passphrase"
|
||||
"github.com/docker/notary/trustmanager"
|
||||
"github.com/docker/notary/trustmanager/yubikey"
|
||||
"github.com/docker/notary/trustpinning"
|
||||
)
|
||||
|
||||
// NewNotaryRepository is a helper method that returns a new notary repository.
|
||||
// It takes the base directory under where all the trust files will be stored
|
||||
// (usually ~/.docker/trust/).
|
||||
func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper,
|
||||
retriever passphrase.Retriever) (
|
||||
retriever passphrase.Retriever, trustPinning trustpinning.TrustPinConfig) (
|
||||
*NotaryRepository, error) {
|
||||
|
||||
fileKeyStore, err := trustmanager.NewKeyFileStore(baseDir, retriever)
|
||||
|
@ -24,10 +25,10 @@ func NewNotaryRepository(baseDir, gun, baseURL string, rt http.RoundTripper,
|
|||
}
|
||||
|
||||
keyStores := []trustmanager.KeyStore{fileKeyStore}
|
||||
yubiKeyStore, _ := yubikey.NewYubiKeyStore(fileKeyStore, retriever)
|
||||
yubiKeyStore, _ := yubikey.NewYubiStore(fileKeyStore, retriever)
|
||||
if yubiKeyStore != nil {
|
||||
keyStores = []trustmanager.KeyStore{yubiKeyStore, fileKeyStore}
|
||||
}
|
||||
|
||||
return repositoryFromKeystores(baseDir, gun, baseURL, rt, keyStores)
|
||||
return repositoryFromKeystores(baseDir, gun, baseURL, rt, keyStores, trustPinning)
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@ const (
|
|||
PubCertPerms = 0755
|
||||
// Sha256HexSize is how big a Sha256 hex is in number of characters
|
||||
Sha256HexSize = 64
|
||||
// Sha512HexSize is how big a Sha512 hex is in number of characters
|
||||
Sha512HexSize = 128
|
||||
// SHA256 is the name of SHA256 hash algorithm
|
||||
SHA256 = "sha256"
|
||||
// SHA512 is the name of SHA512 hash algorithm
|
||||
|
@ -49,6 +51,11 @@ const (
|
|||
// (one year, in seconds, since one year is forever in terms of internet
|
||||
// content)
|
||||
CacheMaxAgeLimit = 1 * Year
|
||||
|
||||
MySQLBackend = "mysql"
|
||||
MemoryBackend = "memory"
|
||||
SQLiteBackend = "sqlite3"
|
||||
RethinkDBBackend = "rethinkdb"
|
||||
)
|
||||
|
||||
// NotaryDefaultExpiries is the construct used to configure the default expiry times of
|
||||
|
|
|
@ -21,13 +21,6 @@ func GenerateCertificate(rootKey data.PrivateKey, gun string, startTime, endTime
|
|||
return generateCertificate(signer, gun, startTime, endTime)
|
||||
}
|
||||
|
||||
// GenerateTestingCertificate generates a non-expired X509 Certificate from a template, given a GUN.
|
||||
// Good enough for tests where expiration does not really matter; do not use if you care about the policy.
|
||||
func GenerateTestingCertificate(signer crypto.Signer, gun string) (*x509.Certificate, error) {
|
||||
startTime := time.Now()
|
||||
return generateCertificate(signer, gun, startTime, startTime.AddDate(10, 0, 0))
|
||||
}
|
||||
|
||||
func generateCertificate(signer crypto.Signer, gun string, startTime, endTime time.Time) (*x509.Certificate, error) {
|
||||
template, err := trustmanager.NewCertificate(gun, startTime, endTime)
|
||||
if err != nil {
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
version: "2"
|
||||
services:
|
||||
server:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: server.Dockerfile
|
||||
volumes:
|
||||
- ./fixtures/rethinkdb:/tls
|
||||
networks:
|
||||
- rdb
|
||||
links:
|
||||
- rdb-proxy:rdb-proxy.rdb
|
||||
- signer
|
||||
environment:
|
||||
- SERVICE_NAME=notary_server
|
||||
ports:
|
||||
- "8080"
|
||||
- "4443:4443"
|
||||
entrypoint: /usr/bin/env sh
|
||||
command: -c "sh migrations/rethink_migrate.sh && notary-server -config=fixtures/server-config.rethink.json"
|
||||
depends_on:
|
||||
- rdb-proxy
|
||||
signer:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: signer.Dockerfile
|
||||
volumes:
|
||||
- ./fixtures/rethinkdb:/tls
|
||||
networks:
|
||||
rdb:
|
||||
aliases:
|
||||
- notarysigner
|
||||
links:
|
||||
- rdb-proxy:rdb-proxy.rdb
|
||||
environment:
|
||||
- SERVICE_NAME=notary_signer
|
||||
entrypoint: /usr/bin/env sh
|
||||
command: -c "sh migrations/rethink_migrate.sh && notary-signer -config=fixtures/signer-config.rethink.json"
|
||||
depends_on:
|
||||
- rdb-proxy
|
||||
rdb-01:
|
||||
image: jlhawn/rethinkdb-tls
|
||||
volumes:
|
||||
- ./fixtures/rethinkdb:/tls
|
||||
- rdb-01-data:/var/data
|
||||
networks:
|
||||
rdb:
|
||||
aliases:
|
||||
- rdb
|
||||
- rdb.rdb
|
||||
- rdb-01.rdb
|
||||
command: "--bind all --no-http-admin --server-name rdb_01 --canonical-address rdb-01.rdb --directory /var/data/rethinkdb --join rdb.rdb --driver-tls --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
|
||||
rdb-02:
|
||||
image: jlhawn/rethinkdb-tls
|
||||
volumes:
|
||||
- ./fixtures/rethinkdb:/tls
|
||||
- rdb-02-data:/var/data
|
||||
networks:
|
||||
rdb:
|
||||
aliases:
|
||||
- rdb
|
||||
- rdb.rdb
|
||||
- rdb-02.rdb
|
||||
command: "--bind all --no-http-admin --server-name rdb_02 --canonical-address rdb-02.rdb --directory /var/data/rethinkdb --join rdb.rdb --driver-tls --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
|
||||
rdb-03:
|
||||
image: jlhawn/rethinkdb-tls
|
||||
volumes:
|
||||
- ./fixtures/rethinkdb:/tls
|
||||
- rdb-03-data:/var/data
|
||||
networks:
|
||||
rdb:
|
||||
aliases:
|
||||
- rdb
|
||||
- rdb.rdb
|
||||
- rdb-03.rdb
|
||||
command: "--bind all --no-http-admin --server-name rdb_03 --canonical-address rdb-03.rdb --directory /var/data/rethinkdb --join rdb.rdb --driver-tls --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
|
||||
rdb-proxy:
|
||||
image: jlhawn/rethinkdb-tls
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- ./fixtures/rethinkdb:/tls
|
||||
networks:
|
||||
rdb:
|
||||
aliases:
|
||||
- rdb-proxy
|
||||
- rdb-proxy.rdp
|
||||
command: "proxy --bind all --join rdb.rdb --web-tls --web-tls-key /tls/key.pem --web-tls-cert /tls/cert.pem --driver-tls --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
|
||||
depends_on:
|
||||
- rdb-01
|
||||
- rdb-02
|
||||
- rdb-03
|
||||
client:
|
||||
volumes:
|
||||
- ./test_output:/test_output
|
||||
networks:
|
||||
- rdb
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
links:
|
||||
- server:notary-server
|
||||
command: buildscripts/testclient.sh
|
||||
volumes:
|
||||
rdb-01-data:
|
||||
external: false
|
||||
rdb-02-data:
|
||||
external: false
|
||||
rdb-03-data:
|
||||
external: false
|
||||
networks:
|
||||
rdb:
|
||||
external: false
|
|
@ -0,0 +1,36 @@
|
|||
server:
|
||||
build: .
|
||||
dockerfile: server.Dockerfile
|
||||
links:
|
||||
- mysql
|
||||
- signer
|
||||
- signer:notarysigner
|
||||
environment:
|
||||
- SERVICE_NAME=notary_server
|
||||
entrypoint: /usr/bin/env sh
|
||||
command: -c "./migrations/migrate.sh && notary-server -config=fixtures/server-config.json"
|
||||
signer:
|
||||
build: .
|
||||
dockerfile: signer.Dockerfile
|
||||
links:
|
||||
- mysql
|
||||
environment:
|
||||
- SERVICE_NAME=notary_signer
|
||||
entrypoint: /usr/bin/env sh
|
||||
command: -c "./migrations/migrate.sh && notary-signer -config=fixtures/signer-config.json"
|
||||
mysql:
|
||||
volumes:
|
||||
- ./notarymysql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
|
||||
image: mariadb:10.1.10
|
||||
environment:
|
||||
- TERM=dumb
|
||||
- MYSQL_ALLOW_EMPTY_PASSWORD="true"
|
||||
command: mysqld --innodb_file_per_table
|
||||
client:
|
||||
volumes:
|
||||
- ./test_output:/test_output
|
||||
build: .
|
||||
dockerfile: Dockerfile
|
||||
links:
|
||||
- server:notary-server
|
||||
command: buildscripts/testclient.sh
|
|
@ -0,0 +1,102 @@
|
|||
version: "2"
|
||||
services:
|
||||
server:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: server.Dockerfile
|
||||
volumes:
|
||||
- ./fixtures/rethinkdb:/tls
|
||||
networks:
|
||||
- rdb
|
||||
links:
|
||||
- rdb-proxy:rdb-proxy.rdb
|
||||
- signer
|
||||
environment:
|
||||
- SERVICE_NAME=notary_server
|
||||
ports:
|
||||
- "8080"
|
||||
- "4443:4443"
|
||||
entrypoint: /usr/bin/env sh
|
||||
command: -c "sh migrations/rethink_migrate.sh && notary-server -config=fixtures/server-config.rethink.json"
|
||||
depends_on:
|
||||
- rdb-proxy
|
||||
signer:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: signer.Dockerfile
|
||||
volumes:
|
||||
- ./fixtures/rethinkdb:/tls
|
||||
networks:
|
||||
rdb:
|
||||
aliases:
|
||||
- notarysigner
|
||||
links:
|
||||
- rdb-proxy:rdb-proxy.rdb
|
||||
environment:
|
||||
- SERVICE_NAME=notary_signer
|
||||
entrypoint: /usr/bin/env sh
|
||||
command: -c "sh migrations/rethink_migrate.sh && notary-signer -config=fixtures/signer-config.rethink.json"
|
||||
depends_on:
|
||||
- rdb-proxy
|
||||
rdb-01:
|
||||
image: jlhawn/rethinkdb-tls
|
||||
volumes:
|
||||
- ./fixtures/rethinkdb:/tls
|
||||
- rdb-01-data:/var/data
|
||||
networks:
|
||||
rdb:
|
||||
aliases:
|
||||
- rdb
|
||||
- rdb.rdb
|
||||
- rdb-01.rdb
|
||||
command: "--bind all --no-http-admin --server-name rdb_01 --canonical-address rdb-01.rdb --directory /var/data/rethinkdb --join rdb.rdb --driver-tls --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
|
||||
rdb-02:
|
||||
image: jlhawn/rethinkdb-tls
|
||||
volumes:
|
||||
- ./fixtures/rethinkdb:/tls
|
||||
- rdb-02-data:/var/data
|
||||
networks:
|
||||
rdb:
|
||||
aliases:
|
||||
- rdb
|
||||
- rdb.rdb
|
||||
- rdb-02.rdb
|
||||
command: "--bind all --no-http-admin --server-name rdb_02 --canonical-address rdb-02.rdb --directory /var/data/rethinkdb --join rdb.rdb --driver-tls --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
|
||||
rdb-03:
|
||||
image: jlhawn/rethinkdb-tls
|
||||
volumes:
|
||||
- ./fixtures/rethinkdb:/tls
|
||||
- rdb-03-data:/var/data
|
||||
networks:
|
||||
rdb:
|
||||
aliases:
|
||||
- rdb
|
||||
- rdb.rdb
|
||||
- rdb-03.rdb
|
||||
command: "--bind all --no-http-admin --server-name rdb_03 --canonical-address rdb-03.rdb --directory /var/data/rethinkdb --join rdb.rdb --driver-tls --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
|
||||
rdb-proxy:
|
||||
image: jlhawn/rethinkdb-tls
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- ./fixtures/rethinkdb:/tls
|
||||
networks:
|
||||
rdb:
|
||||
aliases:
|
||||
- rdb-proxy
|
||||
- rdb-proxy.rdp
|
||||
command: "proxy --bind all --join rdb.rdb --web-tls --web-tls-key /tls/key.pem --web-tls-cert /tls/cert.pem --driver-tls --driver-tls-key /tls/key.pem --driver-tls-cert /tls/cert.pem --cluster-tls --cluster-tls-key /tls/key.pem --cluster-tls-cert /tls/cert.pem --cluster-tls-ca /tls/ca.pem"
|
||||
depends_on:
|
||||
- rdb-01
|
||||
- rdb-02
|
||||
- rdb-03
|
||||
volumes:
|
||||
rdb-01-data:
|
||||
external: false
|
||||
rdb-02-data:
|
||||
external: false
|
||||
rdb-03-data:
|
||||
external: false
|
||||
networks:
|
||||
rdb:
|
||||
external: false
|
|
@ -10,7 +10,7 @@ server:
|
|||
ports:
|
||||
- "8080"
|
||||
- "4443:4443"
|
||||
entrypoint: /bin/bash
|
||||
entrypoint: /usr/bin/env sh
|
||||
command: -c "./migrations/migrate.sh && notary-server -config=fixtures/server-config.json"
|
||||
signer:
|
||||
build: .
|
||||
|
@ -19,7 +19,7 @@ signer:
|
|||
- mysql
|
||||
environment:
|
||||
- SERVICE_NAME=notary_signer
|
||||
entrypoint: /bin/bash
|
||||
entrypoint: /usr/bin/env sh
|
||||
command: -c "./migrations/migrate.sh && notary-signer -config=fixtures/signer-config.json"
|
||||
mysql:
|
||||
volumes:
|
||||
|
|
|
@ -1,28 +1,25 @@
|
|||
FROM golang:1.6.0
|
||||
FROM golang:1.6.1-alpine
|
||||
MAINTAINER David Lawrence "david.lawrence@docker.com"
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
libltdl-dev \
|
||||
--no-install-recommends \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN apk add --update git gcc libc-dev && rm -rf /var/cache/apk/*
|
||||
|
||||
EXPOSE 4443
|
||||
|
||||
# Install DB migration tool
|
||||
# Install SQL DB migration tool
|
||||
RUN go get github.com/mattes/migrate
|
||||
|
||||
ENV NOTARYPKG github.com/docker/notary
|
||||
|
||||
# Copy the local repo to the expected go path
|
||||
COPY . /go/src/github.com/docker/notary
|
||||
COPY . /go/src/${NOTARYPKG}
|
||||
|
||||
WORKDIR /go/src/${NOTARYPKG}
|
||||
|
||||
EXPOSE 4443
|
||||
|
||||
# Install notary-server
|
||||
RUN go install \
|
||||
-tags pkcs11 \
|
||||
-ldflags "-w -X ${NOTARYPKG}/version.GitCommit=`git rev-parse --short HEAD` -X ${NOTARYPKG}/version.NotaryVersion=`cat NOTARY_VERSION`" \
|
||||
${NOTARYPKG}/cmd/notary-server
|
||||
${NOTARYPKG}/cmd/notary-server && apk del git gcc libc-dev
|
||||
|
||||
ENTRYPOINT [ "notary-server" ]
|
||||
CMD [ "-config=fixtures/server-config-local.json" ]
|
||||
|
|
|
@ -1,30 +1,28 @@
|
|||
FROM golang:1.6.0
|
||||
FROM golang:1.6.1-alpine
|
||||
MAINTAINER David Lawrence "david.lawrence@docker.com"
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
libltdl-dev \
|
||||
--no-install-recommends \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN apk add --update git gcc libc-dev && rm -rf /var/cache/apk/*
|
||||
|
||||
EXPOSE 4444
|
||||
|
||||
# Install DB migration tool
|
||||
# Install SQL DB migration tool
|
||||
RUN go get github.com/mattes/migrate
|
||||
|
||||
ENV NOTARYPKG github.com/docker/notary
|
||||
|
||||
# Copy the local repo to the expected go path
|
||||
COPY . /go/src/${NOTARYPKG}
|
||||
|
||||
WORKDIR /go/src/${NOTARYPKG}
|
||||
|
||||
ENV NOTARY_SIGNER_DEFAULT_ALIAS="timestamp_1"
|
||||
ENV NOTARY_SIGNER_TIMESTAMP_1="testpassword"
|
||||
|
||||
# Copy the local repo to the expected go path
|
||||
COPY . /go/src/github.com/docker/notary
|
||||
|
||||
WORKDIR /go/src/${NOTARYPKG}
|
||||
EXPOSE 4444
|
||||
|
||||
# Install notary-signer
|
||||
RUN go install \
|
||||
-tags pkcs11 \
|
||||
-ldflags "-w -X ${NOTARYPKG}/version.GitCommit=`git rev-parse --short HEAD` -X ${NOTARYPKG}/version.NotaryVersion=`cat NOTARY_VERSION`" \
|
||||
${NOTARYPKG}/cmd/notary-signer
|
||||
${NOTARYPKG}/cmd/notary-signer && apk del git gcc libc-dev
|
||||
|
||||
ENTRYPOINT [ "notary-signer" ]
|
||||
CMD [ "-config=fixtures/signer-config-local.json" ]
|
||||
|
|
|
@ -15,14 +15,12 @@ type SimpleFileStore struct {
|
|||
perms os.FileMode
|
||||
}
|
||||
|
||||
// NewSimpleFileStore creates a directory with 755 permissions
|
||||
func NewSimpleFileStore(baseDir string, fileExt string) (*SimpleFileStore, error) {
|
||||
// NewFileStore creates a fully configurable file store
|
||||
func NewFileStore(baseDir, fileExt string, perms os.FileMode) (*SimpleFileStore, error) {
|
||||
baseDir = filepath.Clean(baseDir)
|
||||
|
||||
if err := CreateDirectory(baseDir); err != nil {
|
||||
if err := createDirectory(baseDir, perms); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(fileExt, ".") {
|
||||
fileExt = "." + fileExt
|
||||
}
|
||||
|
@ -30,25 +28,20 @@ func NewSimpleFileStore(baseDir string, fileExt string) (*SimpleFileStore, error
|
|||
return &SimpleFileStore{
|
||||
baseDir: baseDir,
|
||||
fileExt: fileExt,
|
||||
perms: visible,
|
||||
perms: perms,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewPrivateSimpleFileStore creates a directory with 700 permissions
|
||||
func NewPrivateSimpleFileStore(baseDir string, fileExt string) (*SimpleFileStore, error) {
|
||||
if err := CreatePrivateDirectory(baseDir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// NewSimpleFileStore is a convenience wrapper to create a world readable,
|
||||
// owner writeable filestore
|
||||
func NewSimpleFileStore(baseDir, fileExt string) (*SimpleFileStore, error) {
|
||||
return NewFileStore(baseDir, fileExt, visible)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(fileExt, ".") {
|
||||
fileExt = "." + fileExt
|
||||
}
|
||||
|
||||
return &SimpleFileStore{
|
||||
baseDir: baseDir,
|
||||
fileExt: fileExt,
|
||||
perms: private,
|
||||
}, nil
|
||||
// NewPrivateSimpleFileStore is a wrapper to create an owner readable/writeable
|
||||
// _only_ filestore
|
||||
func NewPrivateSimpleFileStore(baseDir, fileExt string) (*SimpleFileStore, error) {
|
||||
return NewFileStore(baseDir, fileExt, private)
|
||||
}
|
||||
|
||||
// Add writes data to a file with a given name
|
||||
|
@ -71,24 +64,6 @@ func (f *SimpleFileStore) Remove(name string) error {
|
|||
return os.Remove(filePath)
|
||||
}
|
||||
|
||||
// RemoveDir removes the directory identified by name
|
||||
func (f *SimpleFileStore) RemoveDir(name string) error {
|
||||
dirPath := filepath.Join(f.baseDir, name)
|
||||
|
||||
// Check to see if directory exists
|
||||
fi, err := os.Stat(dirPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check to see if it is a directory
|
||||
if !fi.IsDir() {
|
||||
return fmt.Errorf("directory not found: %s", name)
|
||||
}
|
||||
|
||||
return os.RemoveAll(dirPath)
|
||||
}
|
||||
|
||||
// Get returns the data given a file name
|
||||
func (f *SimpleFileStore) Get(name string) ([]byte, error) {
|
||||
filePath, err := f.GetPath(name)
|
||||
|
@ -119,12 +94,6 @@ func (f *SimpleFileStore) ListFiles() []string {
|
|||
return f.list(f.baseDir)
|
||||
}
|
||||
|
||||
// ListDir lists all the files inside of a directory identified by a name
|
||||
func (f *SimpleFileStore) ListDir(name string) []string {
|
||||
fullPath := filepath.Join(f.baseDir, name)
|
||||
return f.list(fullPath)
|
||||
}
|
||||
|
||||
// list lists all the files in a directory given a full path. Ignores symlinks.
|
||||
func (f *SimpleFileStore) list(path string) []string {
|
||||
files := make([]string, 0, 0)
|
||||
|
@ -170,16 +139,6 @@ func (f *SimpleFileStore) BaseDir() string {
|
|||
return f.baseDir
|
||||
}
|
||||
|
||||
// CreateDirectory uses createDirectory to create a chmod 755 Directory
|
||||
func CreateDirectory(dir string) error {
|
||||
return createDirectory(dir, visible)
|
||||
}
|
||||
|
||||
// CreatePrivateDirectory uses createDirectory to create a chmod 700 Directory
|
||||
func CreatePrivateDirectory(dir string) error {
|
||||
return createDirectory(dir, private)
|
||||
}
|
||||
|
||||
// createDirectory receives a string of the path to a directory.
|
||||
// It does not support passing files, so the caller has to remove
|
||||
// the filename by doing filepath.Dir(full_path_to_file)
|
||||
|
|
|
@ -62,7 +62,7 @@ func NewKeyFileStore(baseDir string, passphraseRetriever passphrase.Retriever) (
|
|||
return keyStore, nil
|
||||
}
|
||||
|
||||
func generateKeyInfoMap(s LimitedFileStore) map[string]KeyInfo {
|
||||
func generateKeyInfoMap(s Storage) map[string]KeyInfo {
|
||||
keyInfoMap := make(map[string]KeyInfo)
|
||||
for _, keyPath := range s.ListFiles() {
|
||||
d, err := s.Get(keyPath)
|
||||
|
@ -309,7 +309,7 @@ func KeyInfoFromPEM(pemBytes []byte, filename string) (string, KeyInfo, error) {
|
|||
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 Storage, passphraseRetriever passphrase.Retriever, cachedKeys map[string]*cachedKey, name, role string, privKey data.PrivateKey) error {
|
||||
|
||||
var (
|
||||
chosenPassphrase string
|
||||
|
@ -338,7 +338,7 @@ func addKey(s LimitedFileStore, passphraseRetriever passphrase.Retriever, cached
|
|||
// both in the newer format PEM headers, and also in the legacy filename
|
||||
// format. It returns: the role, whether it was found in the legacy format
|
||||
// (true == legacy), and an error
|
||||
func getKeyRole(s LimitedFileStore, keyID string) (string, bool, error) {
|
||||
func getKeyRole(s Storage, keyID string) (string, bool, error) {
|
||||
name := strings.TrimSpace(strings.TrimSuffix(filepath.Base(keyID), filepath.Ext(keyID)))
|
||||
|
||||
for _, file := range s.ListFiles() {
|
||||
|
@ -361,11 +361,11 @@ func getKeyRole(s LimitedFileStore, keyID string) (string, bool, error) {
|
|||
}
|
||||
}
|
||||
|
||||
return "", false, &ErrKeyNotFound{KeyID: keyID}
|
||||
return "", false, ErrKeyNotFound{KeyID: keyID}
|
||||
}
|
||||
|
||||
// GetKey returns the PrivateKey given a KeyID
|
||||
func getKey(s LimitedFileStore, passphraseRetriever passphrase.Retriever, cachedKeys map[string]*cachedKey, name string) (data.PrivateKey, string, error) {
|
||||
func getKey(s Storage, passphraseRetriever passphrase.Retriever, cachedKeys map[string]*cachedKey, name string) (data.PrivateKey, string, error) {
|
||||
cachedKeyEntry, ok := cachedKeys[name]
|
||||
if ok {
|
||||
return cachedKeyEntry.key, cachedKeyEntry.alias, nil
|
||||
|
@ -389,7 +389,7 @@ func getKey(s LimitedFileStore, passphraseRetriever passphrase.Retriever, cached
|
|||
}
|
||||
|
||||
// RemoveKey removes the key from the keyfilestore
|
||||
func removeKey(s LimitedFileStore, cachedKeys map[string]*cachedKey, name string) error {
|
||||
func removeKey(s Storage, cachedKeys map[string]*cachedKey, name string) error {
|
||||
role, legacy, err := getKeyRole(s, name)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -419,7 +419,7 @@ func getSubdir(alias string) string {
|
|||
|
||||
// Given a key ID, gets the bytes and alias belonging to that key if the key
|
||||
// exists
|
||||
func getRawKey(s LimitedFileStore, name string) ([]byte, string, error) {
|
||||
func getRawKey(s Storage, name string) ([]byte, string, error) {
|
||||
role, legacy, err := getKeyRole(s, name)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
|
@ -475,7 +475,7 @@ func GetPasswdDecryptBytes(passphraseRetriever passphrase.Retriever, pemBytes []
|
|||
return privKey, passwd, nil
|
||||
}
|
||||
|
||||
func encryptAndAddKey(s LimitedFileStore, passwd string, cachedKeys map[string]*cachedKey, name, role string, privKey data.PrivateKey) error {
|
||||
func encryptAndAddKey(s Storage, passwd string, cachedKeys map[string]*cachedKey, name, role string, privKey data.PrivateKey) error {
|
||||
|
||||
var (
|
||||
pemPrivKey []byte
|
||||
|
|
|
@ -43,6 +43,8 @@ type KeyStore interface {
|
|||
// AddKey adds a key to the KeyStore, and if the key already exists,
|
||||
// succeeds. Otherwise, returns an error if it cannot add.
|
||||
AddKey(keyInfo KeyInfo, privKey data.PrivateKey) error
|
||||
// Should fail with ErrKeyNotFound if the keystore is operating normally
|
||||
// and knows that it does not store the requested key.
|
||||
GetKey(keyID string) (data.PrivateKey, string, error)
|
||||
GetKeyInfo(keyID string) (KeyInfo, error)
|
||||
ListKeys() map[string]KeyInfo
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"sync"
|
||||
)
|
||||
|
||||
// MemoryFileStore is an implementation of LimitedFileStore that keeps
|
||||
// MemoryFileStore is an implementation of Storage that keeps
|
||||
// the contents in memory.
|
||||
type MemoryFileStore struct {
|
||||
sync.Mutex
|
||||
|
|
|
@ -17,8 +17,8 @@ var (
|
|||
ErrPathOutsideStore = errors.New("path outside file store")
|
||||
)
|
||||
|
||||
// LimitedFileStore implements the bare bones primitives (no hierarchy)
|
||||
type LimitedFileStore interface {
|
||||
// Storage implements the bare bones primitives (no hierarchy)
|
||||
type Storage interface {
|
||||
// Add writes a file to the specified location, returning an error if this
|
||||
// is not possible (reasons may include permissions errors). The path is cleaned
|
||||
// before being made absolute against the store's base dir.
|
||||
|
@ -37,16 +37,6 @@ type LimitedFileStore interface {
|
|||
|
||||
// ListFiles returns a list of paths relative to the base directory of the
|
||||
// filestore. Any of these paths must be retrievable via the
|
||||
// LimitedFileStore.Get method.
|
||||
// Storage.Get method.
|
||||
ListFiles() []string
|
||||
}
|
||||
|
||||
// FileStore is the interface for full-featured FileStores
|
||||
type FileStore interface {
|
||||
LimitedFileStore
|
||||
|
||||
RemoveDir(directoryName string) error
|
||||
GetPath(fileName string) (string, error)
|
||||
ListDir(directoryName string) []string
|
||||
BaseDir() string
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ type X509FileStore struct {
|
|||
fileMap map[CertID]string
|
||||
fingerprintMap map[CertID]*x509.Certificate
|
||||
nameMap map[string][]CertID
|
||||
fileStore FileStore
|
||||
fileStore Storage
|
||||
}
|
||||
|
||||
// NewX509FileStore returns a new X509FileStore.
|
||||
|
@ -88,11 +88,7 @@ func (s *X509FileStore) addNamedCert(cert *x509.Certificate) error {
|
|||
certBytes := CertToPEM(cert)
|
||||
|
||||
// Save the file to disk if not already there.
|
||||
filePath, err := s.fileStore.GetPath(fileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||
if _, err = s.fileStore.Get(fileName); os.IsNotExist(err) {
|
||||
if err := s.fileStore.Add(fileName, certBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -246,7 +242,7 @@ func (s *X509FileStore) GetCertificatesByCN(cn string) ([]*x509.Certificate, err
|
|||
// as part of the roots list. This never allows the use of system roots, returning
|
||||
// an error if there are no root CAs.
|
||||
func (s *X509FileStore) GetVerifyOptions(dnsName string) (x509.VerifyOptions, error) {
|
||||
// If we have no Certificates loaded return error (we don't want to rever to using
|
||||
// If we have no Certificates loaded return error (we don't want to revert to using
|
||||
// system CAs).
|
||||
if len(s.fingerprintMap) == 0 {
|
||||
return x509.VerifyOptions{}, errors.New("no root CAs available")
|
||||
|
|
|
@ -188,7 +188,7 @@ func (s *X509MemStore) GetCertificatesByCN(cn string) ([]*x509.Certificate, erro
|
|||
// as part of the roots list. This never allows the use of system roots, returning
|
||||
// an error if there are no root CAs.
|
||||
func (s *X509MemStore) GetVerifyOptions(dnsName string) (x509.VerifyOptions, error) {
|
||||
// If we have no Certificates loaded return error (we don't want to rever to using
|
||||
// If we have no Certificates loaded return error (we don't want to revert to using
|
||||
// system CAs).
|
||||
if len(s.fingerprintMap) == 0 {
|
||||
return x509.VerifyOptions{}, errors.New("no root CAs available")
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package trustmanager
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
|
@ -57,13 +58,24 @@ func GetCertFromURL(urlStr string) (*x509.Certificate, error) {
|
|||
return cert, nil
|
||||
}
|
||||
|
||||
// CertToPEM is an utility function returns a PEM encoded x509 Certificate
|
||||
// CertToPEM is a utility function returns a PEM encoded x509 Certificate
|
||||
func CertToPEM(cert *x509.Certificate) []byte {
|
||||
pemCert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw})
|
||||
|
||||
return pemCert
|
||||
}
|
||||
|
||||
// CertChainToPEM is a utility function returns a PEM encoded chain of x509 Certificates, in the order they are passed
|
||||
func CertChainToPEM(certChain []*x509.Certificate) ([]byte, error) {
|
||||
var pemBytes bytes.Buffer
|
||||
for _, cert := range certChain {
|
||||
if err := pem.Encode(&pemBytes, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return pemBytes.Bytes(), nil
|
||||
}
|
||||
|
||||
// LoadCertFromPEM returns the first certificate found in a bunch of bytes or error
|
||||
// if nothing is found. Taken from https://golang.org/src/crypto/x509/cert_pool.go#L85.
|
||||
func LoadCertFromPEM(pemBytes []byte) (*x509.Certificate, error) {
|
||||
|
@ -224,20 +236,19 @@ func ParsePEMPrivateKey(pemBytes []byte, passphrase string) (data.PrivateKey, er
|
|||
return nil, errors.New("no valid private key found")
|
||||
}
|
||||
|
||||
var privKeyBytes []byte
|
||||
var err error
|
||||
if x509.IsEncryptedPEMBlock(block) {
|
||||
privKeyBytes, err = x509.DecryptPEMBlock(block, []byte(passphrase))
|
||||
if err != nil {
|
||||
return nil, errors.New("could not decrypt private key")
|
||||
}
|
||||
} else {
|
||||
privKeyBytes = block.Bytes
|
||||
}
|
||||
|
||||
switch block.Type {
|
||||
case "RSA PRIVATE KEY":
|
||||
var privKeyBytes []byte
|
||||
var err error
|
||||
|
||||
if x509.IsEncryptedPEMBlock(block) {
|
||||
privKeyBytes, err = x509.DecryptPEMBlock(block, []byte(passphrase))
|
||||
if err != nil {
|
||||
return nil, errors.New("could not decrypt private key")
|
||||
}
|
||||
} else {
|
||||
privKeyBytes = block.Bytes
|
||||
}
|
||||
|
||||
rsaPrivKey, err := x509.ParsePKCS1PrivateKey(privKeyBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse DER encoded key: %v", err)
|
||||
|
@ -250,18 +261,6 @@ func ParsePEMPrivateKey(pemBytes []byte, passphrase string) (data.PrivateKey, er
|
|||
|
||||
return tufRSAPrivateKey, nil
|
||||
case "EC PRIVATE KEY":
|
||||
var privKeyBytes []byte
|
||||
var err error
|
||||
|
||||
if x509.IsEncryptedPEMBlock(block) {
|
||||
privKeyBytes, err = x509.DecryptPEMBlock(block, []byte(passphrase))
|
||||
if err != nil {
|
||||
return nil, errors.New("could not decrypt private key")
|
||||
}
|
||||
} else {
|
||||
privKeyBytes = block.Bytes
|
||||
}
|
||||
|
||||
ecdsaPrivKey, err := x509.ParseECPrivateKey(privKeyBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse DER encoded private key: %v", err)
|
||||
|
@ -277,18 +276,6 @@ func ParsePEMPrivateKey(pemBytes []byte, passphrase string) (data.PrivateKey, er
|
|||
// We serialize ED25519 keys by concatenating the private key
|
||||
// to the public key and encoding with PEM. See the
|
||||
// ED25519ToPrivateKey function.
|
||||
var privKeyBytes []byte
|
||||
var err error
|
||||
|
||||
if x509.IsEncryptedPEMBlock(block) {
|
||||
privKeyBytes, err = x509.DecryptPEMBlock(block, []byte(passphrase))
|
||||
if err != nil {
|
||||
return nil, errors.New("could not decrypt private key")
|
||||
}
|
||||
} else {
|
||||
privKeyBytes = block.Bytes
|
||||
}
|
||||
|
||||
tufECDSAPrivateKey, err := ED25519ToPrivateKey(privKeyBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not convert ecdsa.PrivateKey to data.PrivateKey: %v", err)
|
||||
|
@ -544,12 +531,34 @@ func CertToKey(cert *x509.Certificate) data.PublicKey {
|
|||
}
|
||||
}
|
||||
|
||||
// CertsToKeys transforms each of the input certificates into it's corresponding
|
||||
// CertsToKeys transforms each of the input certificate chains into its corresponding
|
||||
// PublicKey
|
||||
func CertsToKeys(certs []*x509.Certificate) map[string]data.PublicKey {
|
||||
func CertsToKeys(leafCerts []*x509.Certificate, intCerts map[string][]*x509.Certificate) map[string]data.PublicKey {
|
||||
keys := make(map[string]data.PublicKey)
|
||||
for _, cert := range certs {
|
||||
newKey := CertToKey(cert)
|
||||
for _, leafCert := range leafCerts {
|
||||
certBundle := []*x509.Certificate{leafCert}
|
||||
certID, err := FingerprintCert(leafCert)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if intCertsForLeafs, ok := intCerts[certID]; ok {
|
||||
certBundle = append(certBundle, intCertsForLeafs...)
|
||||
}
|
||||
certChainPEM, err := CertChainToPEM(certBundle)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var newKey data.PublicKey
|
||||
// Use the leaf cert's public key algorithm for typing
|
||||
switch leafCert.PublicKeyAlgorithm {
|
||||
case x509.RSA:
|
||||
newKey = data.NewRSAx509PublicKey(certChainPEM)
|
||||
case x509.ECDSA:
|
||||
newKey = data.NewECDSAx509PublicKey(certChainPEM)
|
||||
default:
|
||||
logrus.Debugf("Unknown key type parsed from certificate: %v", leafCert.PublicKeyAlgorithm)
|
||||
continue
|
||||
}
|
||||
keys[newKey.ID()] = newKey
|
||||
}
|
||||
return keys
|
||||
|
@ -581,6 +590,7 @@ func NewCertificate(gun string, startTime, endTime time.Time) (*x509.Certificate
|
|||
// X509PublicKeyID returns a public key ID as a string, given a
|
||||
// data.PublicKey that contains an X509 Certificate
|
||||
func X509PublicKeyID(certPubKey data.PublicKey) (string, error) {
|
||||
// Note that this only loads the first certificate from the public key
|
||||
cert, err := LoadCertFromPEM(certPubKey.Public())
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
|
|
@ -25,14 +25,22 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
USER_PIN = "123456"
|
||||
SO_USER_PIN = "010203040506070801020304050607080102030405060708"
|
||||
numSlots = 4 // number of slots in the yubikey
|
||||
// UserPin is the user pin of a yubikey (in PIV parlance, is the PIN)
|
||||
UserPin = "123456"
|
||||
// SOUserPin is the "Security Officer" user pin - this is the PIV management
|
||||
// (MGM) key, which is different than the admin pin of the Yubikey PGP interface
|
||||
// (which in PIV parlance is the PUK, and defaults to 12345678)
|
||||
SOUserPin = "010203040506070801020304050607080102030405060708"
|
||||
numSlots = 4 // number of slots in the yubikey
|
||||
|
||||
KeymodeNone = 0
|
||||
KeymodeTouch = 1 // touch enabled
|
||||
KeymodePinOnce = 2 // require pin entry once
|
||||
KeymodePinAlways = 4 // require pin entry all the time
|
||||
// KeymodeNone means that no touch or PIN is required to sign with the yubikey
|
||||
KeymodeNone = 0
|
||||
// KeymodeTouch means that only touch is required to sign with the yubikey
|
||||
KeymodeTouch = 1
|
||||
// KeymodePinOnce means that the pin entry is required once the first time to sign with the yubikey
|
||||
KeymodePinOnce = 2
|
||||
// KeymodePinAlways means that pin entry is required every time to sign with the yubikey
|
||||
KeymodePinAlways = 4
|
||||
|
||||
// the key size, when importing a key into yubikey, MUST be 32 bytes
|
||||
ecdsaPrivateKeySize = 32
|
||||
|
@ -95,6 +103,8 @@ func init() {
|
|||
}
|
||||
}
|
||||
|
||||
// ErrBackupFailed is returned when a YubiStore fails to back up a key that
|
||||
// is added
|
||||
type ErrBackupFailed struct {
|
||||
err string
|
||||
}
|
||||
|
@ -103,6 +113,17 @@ func (err ErrBackupFailed) Error() string {
|
|||
return fmt.Sprintf("Failed to backup private key to: %s", err.err)
|
||||
}
|
||||
|
||||
// An error indicating that the HSM is not present (as opposed to failing),
|
||||
// i.e. that we can confidently claim that the key is not stored in the HSM
|
||||
// without notifying the user about a missing or failing HSM.
|
||||
type errHSMNotPresent struct {
|
||||
err string
|
||||
}
|
||||
|
||||
func (err errHSMNotPresent) Error() string {
|
||||
return err.err
|
||||
}
|
||||
|
||||
type yubiSlot struct {
|
||||
role string
|
||||
slotID []byte
|
||||
|
@ -116,10 +137,13 @@ type YubiPrivateKey struct {
|
|||
libLoader pkcs11LibLoader
|
||||
}
|
||||
|
||||
type YubikeySigner struct {
|
||||
// YubiKeySigner wraps a YubiPrivateKey and implements the crypto.Signer interface
|
||||
type yubikeySigner struct {
|
||||
YubiPrivateKey
|
||||
}
|
||||
|
||||
// NewYubiPrivateKey returns a YubiPrivateKey, which implements the data.PrivateKey
|
||||
// interface except that the private material is inacessible
|
||||
func NewYubiPrivateKey(slot []byte, pubKey data.ECDSAPublicKey,
|
||||
passRetriever passphrase.Retriever) *YubiPrivateKey {
|
||||
|
||||
|
@ -131,7 +155,8 @@ func NewYubiPrivateKey(slot []byte, pubKey data.ECDSAPublicKey,
|
|||
}
|
||||
}
|
||||
|
||||
func (ys *YubikeySigner) Public() crypto.PublicKey {
|
||||
// Public is a required method of the crypto.Signer interface
|
||||
func (ys *yubikeySigner) Public() crypto.PublicKey {
|
||||
publicKey, err := x509.ParsePKIXPublicKey(ys.YubiPrivateKey.Public())
|
||||
if err != nil {
|
||||
return nil
|
||||
|
@ -147,7 +172,7 @@ func (y *YubiPrivateKey) setLibLoader(loader pkcs11LibLoader) {
|
|||
// CryptoSigner returns a crypto.Signer tha wraps the YubiPrivateKey. Needed for
|
||||
// Certificate generation only
|
||||
func (y *YubiPrivateKey) CryptoSigner() crypto.Signer {
|
||||
return &YubikeySigner{YubiPrivateKey: *y}
|
||||
return &yubikeySigner{YubiPrivateKey: *y}
|
||||
}
|
||||
|
||||
// Private is not implemented in hardware keys
|
||||
|
@ -157,10 +182,14 @@ func (y *YubiPrivateKey) Private() []byte {
|
|||
return nil
|
||||
}
|
||||
|
||||
// SignatureAlgorithm returns which algorithm this key uses to sign - currently
|
||||
// hardcoded to ECDSA
|
||||
func (y YubiPrivateKey) SignatureAlgorithm() data.SigAlgorithm {
|
||||
return data.ECDSASignature
|
||||
}
|
||||
|
||||
// Sign is a required method of the crypto.Signer interface and the data.PrivateKey
|
||||
// interface
|
||||
func (y *YubiPrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) ([]byte, error) {
|
||||
ctx, session, err := SetupHSMEnv(pkcs11Lib, y.libLoader)
|
||||
if err != nil {
|
||||
|
@ -204,7 +233,7 @@ func addECDSAKey(
|
|||
) error {
|
||||
logrus.Debugf("Attempting to add key to yubikey with ID: %s", privKey.ID())
|
||||
|
||||
err := login(ctx, session, passRetriever, pkcs11.CKU_SO, SO_USER_PIN)
|
||||
err := login(ctx, session, passRetriever, pkcs11.CKU_SO, SOUserPin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -317,7 +346,7 @@ func getECDSAKey(ctx IPKCS11Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byt
|
|||
|
||||
// Sign returns a signature for a given signature request
|
||||
func sign(ctx IPKCS11Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byte, passRetriever passphrase.Retriever, payload []byte) ([]byte, error) {
|
||||
err := login(ctx, session, passRetriever, pkcs11.CKU_USER, USER_PIN)
|
||||
err := login(ctx, session, passRetriever, pkcs11.CKU_USER, UserPin)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error logging in: %v", err)
|
||||
}
|
||||
|
@ -376,7 +405,7 @@ func sign(ctx IPKCS11Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byte, pass
|
|||
}
|
||||
|
||||
func yubiRemoveKey(ctx IPKCS11Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byte, passRetriever passphrase.Retriever, keyID string) error {
|
||||
err := login(ctx, session, passRetriever, pkcs11.CKU_SO, SO_USER_PIN)
|
||||
err := login(ctx, session, passRetriever, pkcs11.CKU_SO, SOUserPin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -584,20 +613,20 @@ func getNextEmptySlot(ctx IPKCS11Ctx, session pkcs11.SessionHandle) ([]byte, err
|
|||
return nil, errors.New("Yubikey has no available slots.")
|
||||
}
|
||||
|
||||
// YubiKeyStore is a KeyStore for private keys inside a Yubikey
|
||||
type YubiKeyStore struct {
|
||||
// YubiStore is a KeyStore for private keys inside a Yubikey
|
||||
type YubiStore struct {
|
||||
passRetriever passphrase.Retriever
|
||||
keys map[string]yubiSlot
|
||||
backupStore trustmanager.KeyStore
|
||||
libLoader pkcs11LibLoader
|
||||
}
|
||||
|
||||
// NewYubiKeyStore returns a YubiKeyStore, given a backup key store to write any
|
||||
// NewYubiStore returns a YubiStore, given a backup key store to write any
|
||||
// generated keys to (usually a KeyFileStore)
|
||||
func NewYubiKeyStore(backupStore trustmanager.KeyStore, passphraseRetriever passphrase.Retriever) (
|
||||
*YubiKeyStore, error) {
|
||||
func NewYubiStore(backupStore trustmanager.KeyStore, passphraseRetriever passphrase.Retriever) (
|
||||
*YubiStore, error) {
|
||||
|
||||
s := &YubiKeyStore{
|
||||
s := &YubiStore{
|
||||
passRetriever: passphraseRetriever,
|
||||
keys: make(map[string]yubiSlot),
|
||||
backupStore: backupStore,
|
||||
|
@ -609,15 +638,16 @@ func NewYubiKeyStore(backupStore trustmanager.KeyStore, passphraseRetriever pass
|
|||
|
||||
// Name returns a user friendly name for the location this store
|
||||
// keeps its data
|
||||
func (s YubiKeyStore) Name() string {
|
||||
func (s YubiStore) Name() string {
|
||||
return "yubikey"
|
||||
}
|
||||
|
||||
func (s *YubiKeyStore) setLibLoader(loader pkcs11LibLoader) {
|
||||
func (s *YubiStore) setLibLoader(loader pkcs11LibLoader) {
|
||||
s.libLoader = loader
|
||||
}
|
||||
|
||||
func (s *YubiKeyStore) ListKeys() map[string]trustmanager.KeyInfo {
|
||||
// ListKeys returns a list of keys in the yubikey store
|
||||
func (s *YubiStore) ListKeys() map[string]trustmanager.KeyInfo {
|
||||
if len(s.keys) > 0 {
|
||||
return buildKeyMap(s.keys)
|
||||
}
|
||||
|
@ -639,7 +669,7 @@ func (s *YubiKeyStore) ListKeys() map[string]trustmanager.KeyInfo {
|
|||
}
|
||||
|
||||
// AddKey puts a key inside the Yubikey, as well as writing it to the backup store
|
||||
func (s *YubiKeyStore) AddKey(keyInfo trustmanager.KeyInfo, privKey data.PrivateKey) error {
|
||||
func (s *YubiStore) AddKey(keyInfo trustmanager.KeyInfo, privKey data.PrivateKey) error {
|
||||
added, err := s.addKey(privKey.ID(), keyInfo.Role, privKey)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -656,7 +686,7 @@ func (s *YubiKeyStore) AddKey(keyInfo trustmanager.KeyInfo, privKey data.Private
|
|||
|
||||
// Only add if we haven't seen the key already. Return whether the key was
|
||||
// added.
|
||||
func (s *YubiKeyStore) addKey(keyID, role string, privKey data.PrivateKey) (
|
||||
func (s *YubiStore) addKey(keyID, role string, privKey data.PrivateKey) (
|
||||
bool, error) {
|
||||
|
||||
// We only allow adding root keys for now
|
||||
|
@ -702,17 +732,20 @@ func (s *YubiKeyStore) addKey(keyID, role string, privKey data.PrivateKey) (
|
|||
|
||||
// GetKey retrieves a key from the Yubikey only (it does not look inside the
|
||||
// backup store)
|
||||
func (s *YubiKeyStore) GetKey(keyID string) (data.PrivateKey, string, error) {
|
||||
func (s *YubiStore) GetKey(keyID string) (data.PrivateKey, string, error) {
|
||||
ctx, session, err := SetupHSMEnv(pkcs11Lib, s.libLoader)
|
||||
if err != nil {
|
||||
logrus.Debugf("Failed to initialize PKCS11 environment: %s", err.Error())
|
||||
if _, ok := err.(errHSMNotPresent); ok {
|
||||
err = trustmanager.ErrKeyNotFound{KeyID: keyID}
|
||||
}
|
||||
return nil, "", err
|
||||
}
|
||||
defer cleanup(ctx, session)
|
||||
|
||||
key, ok := s.keys[keyID]
|
||||
if !ok {
|
||||
return nil, "", errors.New("no matching keys found inside of yubikey")
|
||||
return nil, "", trustmanager.ErrKeyNotFound{KeyID: keyID}
|
||||
}
|
||||
|
||||
pubKey, alias, err := getECDSAKey(ctx, session, key.slotID)
|
||||
|
@ -734,7 +767,7 @@ func (s *YubiKeyStore) GetKey(keyID string) (data.PrivateKey, string, error) {
|
|||
|
||||
// RemoveKey deletes a key from the Yubikey only (it does not remove it from the
|
||||
// backup store)
|
||||
func (s *YubiKeyStore) RemoveKey(keyID string) error {
|
||||
func (s *YubiStore) RemoveKey(keyID string) error {
|
||||
ctx, session, err := SetupHSMEnv(pkcs11Lib, s.libLoader)
|
||||
if err != nil {
|
||||
logrus.Debugf("Failed to initialize PKCS11 environment: %s", err.Error())
|
||||
|
@ -757,13 +790,13 @@ func (s *YubiKeyStore) RemoveKey(keyID string) error {
|
|||
}
|
||||
|
||||
// ExportKey doesn't work, because you can't export data from a Yubikey
|
||||
func (s *YubiKeyStore) ExportKey(keyID string) ([]byte, error) {
|
||||
logrus.Debugf("Attempting to export: %s key inside of YubiKeyStore", keyID)
|
||||
func (s *YubiStore) ExportKey(keyID string) ([]byte, error) {
|
||||
logrus.Debugf("Attempting to export: %s key inside of YubiStore", keyID)
|
||||
return nil, errors.New("Keys cannot be exported from a Yubikey.")
|
||||
}
|
||||
|
||||
// Not yet implemented
|
||||
func (s *YubiKeyStore) GetKeyInfo(keyID string) (trustmanager.KeyInfo, error) {
|
||||
// GetKeyInfo is not yet implemented
|
||||
func (s *YubiStore) GetKeyInfo(keyID string) (trustmanager.KeyInfo, error) {
|
||||
return trustmanager.KeyInfo{}, fmt.Errorf("Not yet implemented")
|
||||
}
|
||||
|
||||
|
@ -788,7 +821,7 @@ func SetupHSMEnv(libraryPath string, libLoader pkcs11LibLoader) (
|
|||
IPKCS11Ctx, pkcs11.SessionHandle, error) {
|
||||
|
||||
if libraryPath == "" {
|
||||
return nil, 0, fmt.Errorf("no library found.")
|
||||
return nil, 0, errHSMNotPresent{err: "no library found"}
|
||||
}
|
||||
p := libLoader(libraryPath)
|
||||
|
||||
|
@ -798,8 +831,7 @@ func SetupHSMEnv(libraryPath string, libLoader pkcs11LibLoader) (
|
|||
|
||||
if err := p.Initialize(); err != nil {
|
||||
defer finalizeAndDestroy(p)
|
||||
return nil, 0, fmt.Errorf(
|
||||
"found library %s, but initialize error %s", libraryPath, err.Error())
|
||||
return nil, 0, fmt.Errorf("found library %s, but initialize error %s", libraryPath, err.Error())
|
||||
}
|
||||
|
||||
slots, err := p.GetSlotList(true)
|
||||
|
@ -829,8 +861,8 @@ func SetupHSMEnv(libraryPath string, libLoader pkcs11LibLoader) (
|
|||
return p, session, nil
|
||||
}
|
||||
|
||||
// YubikeyAccessible returns true if a Yubikey can be accessed
|
||||
func YubikeyAccessible() bool {
|
||||
// IsAccessible returns true if a Yubikey can be accessed
|
||||
func IsAccessible() bool {
|
||||
if pkcs11Lib == "" {
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
package certs
|
||||
package trustpinning
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
|
@ -36,6 +37,18 @@ func (err ErrRootRotationFail) Error() string {
|
|||
return fmt.Sprintf("could not rotate trust to a new trusted root: %s", err.Reason)
|
||||
}
|
||||
|
||||
func prettyFormatCertIDs(certs []*x509.Certificate) string {
|
||||
ids := make([]string, 0, len(certs))
|
||||
for _, cert := range certs {
|
||||
id, err := trustmanager.FingerprintCert(cert)
|
||||
if err != nil {
|
||||
id = fmt.Sprintf("[Error %s]", err)
|
||||
}
|
||||
ids = append(ids, id)
|
||||
}
|
||||
return strings.Join(ids, ", ")
|
||||
}
|
||||
|
||||
/*
|
||||
ValidateRoot receives a new root, validates its correctness and attempts to
|
||||
do root key rotation if needed.
|
||||
|
@ -45,40 +58,56 @@ that list is non-empty means that we've already seen this repository before, and
|
|||
have a list of trusted certificates for it. In this case, we use this list of
|
||||
certificates to attempt to validate this root file.
|
||||
|
||||
If the previous validation succeeds, or in the case where we found no trusted
|
||||
certificates for this particular GUN, we check the integrity of the root by
|
||||
If the previous validation succeeds, we check the integrity of the root by
|
||||
making sure that it is validated by itself. This means that we will attempt to
|
||||
validate the root data with the certificates that are included in the root keys
|
||||
themselves.
|
||||
|
||||
If this last steps succeeds, we attempt to do root rotation, by ensuring that
|
||||
we only trust the certificates that are present in the new root.
|
||||
However, if we do not have any current trusted certificates for this GUN, we
|
||||
check if there are any pinned certificates specified in the trust_pinning section
|
||||
of the notary client config. If this section specifies a Certs section with this
|
||||
GUN, we attempt to validate that the certificates present in the downloaded root
|
||||
file match the pinned ID.
|
||||
|
||||
This mechanism of operation is essentially Trust On First Use (TOFU): if we
|
||||
have never seen a certificate for a particular CN, we trust it. If later we see
|
||||
a different certificate for that certificate, we return an ErrValidationFailed error.
|
||||
If the Certs section is empty for this GUN, we check if the trust_pinning
|
||||
section specifies a CA section specified in the config for this GUN. If so, we check
|
||||
that the specified CA is valid and has signed a certificate included in the downloaded
|
||||
root file. The specified CA can be a prefix for this GUN.
|
||||
|
||||
If both the Certs and CA configs do not match this GUN, we fall back to the TOFU
|
||||
section in the config: if true, we trust certificates specified in the root for
|
||||
this GUN. If later we see a different certificate for that certificate, we return
|
||||
an ErrValidationFailed error.
|
||||
|
||||
Note that since we only allow trust data to be downloaded over an HTTPS channel
|
||||
we are using the current public PKI to validate the first download of the certificate
|
||||
adding an extra layer of security over the normal (SSH style) trust model.
|
||||
We shall call this: TOFUS.
|
||||
|
||||
Validation failure at any step will result in an ErrValidationFailed error.
|
||||
*/
|
||||
func ValidateRoot(certStore trustmanager.X509Store, root *data.Signed, gun string) error {
|
||||
func ValidateRoot(certStore trustmanager.X509Store, root *data.Signed, gun string, trustPinning TrustPinConfig) error {
|
||||
logrus.Debugf("entered ValidateRoot with dns: %s", gun)
|
||||
signedRoot, err := data.RootFromSigned(root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Retrieve all the leaf certificates in root for which the CN matches the GUN
|
||||
allValidCerts, err := validRootLeafCerts(signedRoot, gun)
|
||||
rootRole, err := signedRoot.BuildBaseRole(data.CanonicalRootRole)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Retrieve all the leaf and intermediate certificates in root for which the CN matches the GUN
|
||||
allLeafCerts, allIntCerts := parseAllCerts(signedRoot)
|
||||
certsFromRoot, err := validRootLeafCerts(allLeafCerts, gun)
|
||||
if err != nil {
|
||||
logrus.Debugf("error retrieving valid leaf certificates for: %s, %v", gun, err)
|
||||
return &ErrValidationFail{Reason: "unable to retrieve valid leaf certificates"}
|
||||
}
|
||||
|
||||
// Retrieve all the trusted certificates that match this gun
|
||||
certsForCN, err := certStore.GetCertificatesByCN(gun)
|
||||
trustedCerts, err := certStore.GetCertificatesByCN(gun)
|
||||
if err != nil {
|
||||
// If the error that we get back is different than ErrNoCertificatesFound
|
||||
// we couldn't check if there are any certificates with this CN already
|
||||
|
@ -88,35 +117,60 @@ func ValidateRoot(certStore trustmanager.X509Store, root *data.Signed, gun strin
|
|||
return &ErrValidationFail{Reason: "unable to retrieve trusted certificates"}
|
||||
}
|
||||
}
|
||||
|
||||
// If we have certificates that match this specific GUN, let's make sure to
|
||||
// use them first to validate that this new root is valid.
|
||||
if len(certsForCN) != 0 {
|
||||
logrus.Debugf("found %d valid root certificates for %s", len(certsForCN), gun)
|
||||
err = signed.VerifyRoot(root, 0, trustmanager.CertsToKeys(certsForCN))
|
||||
if len(trustedCerts) != 0 {
|
||||
logrus.Debugf("found %d valid root certificates for %s: %s", len(trustedCerts), gun,
|
||||
prettyFormatCertIDs(trustedCerts))
|
||||
err = signed.VerifySignatures(
|
||||
root, data.BaseRole{Keys: trustmanager.CertsToKeys(trustedCerts, allIntCerts), Threshold: 1})
|
||||
if err != nil {
|
||||
logrus.Debugf("failed to verify TUF data for: %s, %v", gun, err)
|
||||
return &ErrValidationFail{Reason: "failed to validate data with current trusted certificates"}
|
||||
}
|
||||
} else {
|
||||
logrus.Debugf("found no currently valid root certificates for %s", gun)
|
||||
logrus.Debugf("found no currently valid root certificates for %s, using trust_pinning config to bootstrap trust", gun)
|
||||
trustPinCheckFunc, err := NewTrustPinChecker(trustPinning, gun)
|
||||
if err != nil {
|
||||
return &ErrValidationFail{Reason: err.Error()}
|
||||
}
|
||||
|
||||
validPinnedCerts := []*x509.Certificate{}
|
||||
for _, cert := range certsFromRoot {
|
||||
certID, err := trustmanager.FingerprintCert(cert)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if ok := trustPinCheckFunc(cert, allIntCerts[certID]); !ok {
|
||||
continue
|
||||
}
|
||||
validPinnedCerts = append(validPinnedCerts, cert)
|
||||
}
|
||||
if len(validPinnedCerts) == 0 {
|
||||
return &ErrValidationFail{Reason: "unable to match any certificates to trust_pinning config"}
|
||||
}
|
||||
certsFromRoot = validPinnedCerts
|
||||
}
|
||||
|
||||
// Validate the integrity of the new root (does it have valid signatures)
|
||||
err = signed.VerifyRoot(root, 0, trustmanager.CertsToKeys(allValidCerts))
|
||||
// Note that certsFromRoot is guaranteed to be unchanged only if we had prior cert data for this GUN or enabled TOFUS
|
||||
// If we attempted to pin a certain certificate or CA, certsFromRoot could have been pruned accordingly
|
||||
err = signed.VerifySignatures(root, data.BaseRole{
|
||||
Keys: trustmanager.CertsToKeys(certsFromRoot, allIntCerts), Threshold: rootRole.Threshold})
|
||||
if err != nil {
|
||||
logrus.Debugf("failed to verify TUF data for: %s, %v", gun, err)
|
||||
return &ErrValidationFail{Reason: "failed to validate integrity of roots"}
|
||||
}
|
||||
|
||||
// Getting here means A) we had trusted certificates and both the
|
||||
// old and new validated this root; or B) we had no trusted certificates but
|
||||
// the new set of certificates has integrity (self-signed)
|
||||
// Getting here means:
|
||||
// A) we had trusted certificates and both the old and new validated this root.
|
||||
// or
|
||||
// B) we had no trusted certificates but the new set of certificates has integrity (self-signed).
|
||||
logrus.Debugf("entering root certificate rotation for: %s", gun)
|
||||
|
||||
// Do root certificate rotation: we trust only the certs present in the new root
|
||||
// First we add all the new certificates (even if they already exist)
|
||||
for _, cert := range allValidCerts {
|
||||
for _, cert := range certsFromRoot {
|
||||
err := certStore.AddCert(cert)
|
||||
if err != nil {
|
||||
// If the error is already exists we don't fail the rotation
|
||||
|
@ -129,7 +183,12 @@ func ValidateRoot(certStore trustmanager.X509Store, root *data.Signed, gun strin
|
|||
}
|
||||
|
||||
// Now we delete old certificates that aren't present in the new root
|
||||
for certID, cert := range certsToRemove(certsForCN, allValidCerts) {
|
||||
oldCertsToRemove, err := certsToRemove(trustedCerts, certsFromRoot)
|
||||
if err != nil {
|
||||
logrus.Debugf("inconsistency when removing old certificates: %v", err)
|
||||
return err
|
||||
}
|
||||
for certID, cert := range oldCertsToRemove {
|
||||
logrus.Debugf("removing certificate with certID: %s", certID)
|
||||
err = certStore.RemoveCert(cert)
|
||||
if err != nil {
|
||||
|
@ -142,11 +201,10 @@ func ValidateRoot(certStore trustmanager.X509Store, root *data.Signed, gun strin
|
|||
return nil
|
||||
}
|
||||
|
||||
// validRootLeafCerts returns a list of non-exipired, non-sha1 certificates whose
|
||||
// Common-Names match the provided GUN
|
||||
func validRootLeafCerts(root *data.SignedRoot, gun string) ([]*x509.Certificate, error) {
|
||||
// Get a list of all of the leaf certificates present in root
|
||||
allLeafCerts, _ := parseAllCerts(root)
|
||||
// validRootLeafCerts returns a list of non-expired, non-sha1 certificates
|
||||
// found in root whose Common-Names match the provided GUN. Note that this
|
||||
// "validity" alone does not imply any measure of trust.
|
||||
func validRootLeafCerts(allLeafCerts map[string]*x509.Certificate, gun string) ([]*x509.Certificate, error) {
|
||||
var validLeafCerts []*x509.Certificate
|
||||
|
||||
// Go through every leaf certificate and check that the CN matches the gun
|
||||
|
@ -180,7 +238,8 @@ func validRootLeafCerts(root *data.SignedRoot, gun string) ([]*x509.Certificate,
|
|||
return nil, errors.New("no valid leaf certificates found in any of the root keys")
|
||||
}
|
||||
|
||||
logrus.Debugf("found %d valid leaf certificates for %s", len(validLeafCerts), gun)
|
||||
logrus.Debugf("found %d valid leaf certificates for %s: %s", len(validLeafCerts), gun,
|
||||
prettyFormatCertIDs(validLeafCerts))
|
||||
return validLeafCerts, nil
|
||||
}
|
||||
|
||||
|
@ -223,6 +282,11 @@ func parseAllCerts(signedRoot *data.SignedRoot) (map[string]*x509.Certificate, m
|
|||
logrus.Debugf("invalid chain due to leaf certificate missing or too many leaf certificates for keyID: %s", keyID)
|
||||
continue
|
||||
}
|
||||
// If we found a leaf certificate, assert that the cert bundle started with a leaf
|
||||
if decodedCerts[0].IsCA {
|
||||
logrus.Debugf("invalid chain due to leaf certificate not being first certificate for keyID: %s", keyID)
|
||||
continue
|
||||
}
|
||||
|
||||
// Get the ID of the leaf certificate
|
||||
leafCert := leafCertList[0]
|
||||
|
@ -243,16 +307,12 @@ func parseAllCerts(signedRoot *data.SignedRoot) (map[string]*x509.Certificate, m
|
|||
return leafCerts, intCerts
|
||||
}
|
||||
|
||||
// certsToRemove returns all the certifificates from oldCerts that aren't present
|
||||
// in newCerts
|
||||
func certsToRemove(oldCerts, newCerts []*x509.Certificate) map[string]*x509.Certificate {
|
||||
// certsToRemove returns all the certificates from oldCerts that aren't present
|
||||
// in newCerts. Note that newCerts should never be empty, else this function will error.
|
||||
// We expect newCerts to come from validateRootLeafCerts, which does not return empty sets.
|
||||
func certsToRemove(oldCerts, newCerts []*x509.Certificate) (map[string]*x509.Certificate, error) {
|
||||
certsToRemove := make(map[string]*x509.Certificate)
|
||||
|
||||
// If no newCerts were provided
|
||||
if len(newCerts) == 0 {
|
||||
return certsToRemove
|
||||
}
|
||||
|
||||
// Populate a map with all the IDs from newCert
|
||||
var newCertMap = make(map[string]struct{})
|
||||
for _, cert := range newCerts {
|
||||
|
@ -264,6 +324,14 @@ func certsToRemove(oldCerts, newCerts []*x509.Certificate) map[string]*x509.Cert
|
|||
newCertMap[certID] = struct{}{}
|
||||
}
|
||||
|
||||
// We don't want to "rotate" certificates to an empty set, nor keep old certificates if the
|
||||
// new root does not trust them. newCerts should come from validRootLeafCerts, which refuses
|
||||
// to return an empty set, and they should all be fingerprintable, so this should never happen
|
||||
// - fail just to be sure.
|
||||
if len(newCertMap) == 0 {
|
||||
return nil, &ErrRootRotationFail{Reason: "internal error, got no certificates to rotate to"}
|
||||
}
|
||||
|
||||
// Iterate over all the old certificates and check to see if we should remove them
|
||||
for _, cert := range oldCerts {
|
||||
certID, err := trustmanager.FingerprintCert(cert)
|
||||
|
@ -276,5 +344,5 @@ func certsToRemove(oldCerts, newCerts []*x509.Certificate) map[string]*x509.Cert
|
|||
}
|
||||
}
|
||||
|
||||
return certsToRemove
|
||||
return certsToRemove, nil
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
package trustpinning
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"github.com/docker/notary/trustmanager"
|
||||
"github.com/docker/notary/tuf/utils"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TrustPinConfig represents the configuration under the trust_pinning section of the config file
|
||||
// This struct represents the preferred way to bootstrap trust for this repository
|
||||
type TrustPinConfig struct {
|
||||
CA map[string]string
|
||||
Certs map[string][]string
|
||||
DisableTOFU bool
|
||||
}
|
||||
|
||||
type trustPinChecker struct {
|
||||
gun string
|
||||
config TrustPinConfig
|
||||
pinnedCAPool *x509.CertPool
|
||||
pinnedCertIDs []string
|
||||
}
|
||||
|
||||
// CertChecker is a function type that will be used to check leaf certs against pinned trust
|
||||
type CertChecker func(leafCert *x509.Certificate, intCerts []*x509.Certificate) bool
|
||||
|
||||
// NewTrustPinChecker returns a new certChecker function from a TrustPinConfig for a GUN
|
||||
func NewTrustPinChecker(trustPinConfig TrustPinConfig, gun string) (CertChecker, error) {
|
||||
t := trustPinChecker{gun: gun, config: trustPinConfig}
|
||||
// Determine the mode, and if it's even valid
|
||||
if pinnedCerts, ok := trustPinConfig.Certs[gun]; ok {
|
||||
t.pinnedCertIDs = pinnedCerts
|
||||
return t.certsCheck, nil
|
||||
}
|
||||
|
||||
if caFilepath, err := getPinnedCAFilepathByPrefix(gun, trustPinConfig); err == nil {
|
||||
// Try to add the CA certs from its bundle file to our certificate store,
|
||||
// and use it to validate certs in the root.json later
|
||||
caCerts, err := trustmanager.LoadCertBundleFromFile(caFilepath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not load root cert from CA path")
|
||||
}
|
||||
// Now only consider certificates that are direct children from this CA cert chain
|
||||
caRootPool := x509.NewCertPool()
|
||||
for _, caCert := range caCerts {
|
||||
if err = trustmanager.ValidateCertificate(caCert); err != nil {
|
||||
continue
|
||||
}
|
||||
caRootPool.AddCert(caCert)
|
||||
}
|
||||
// If we didn't have any valid CA certs, error out
|
||||
if len(caRootPool.Subjects()) == 0 {
|
||||
return nil, fmt.Errorf("invalid CA certs provided")
|
||||
}
|
||||
t.pinnedCAPool = caRootPool
|
||||
return t.caCheck, nil
|
||||
}
|
||||
|
||||
if !trustPinConfig.DisableTOFU {
|
||||
return t.tofusCheck, nil
|
||||
}
|
||||
return nil, fmt.Errorf("invalid trust pinning specified")
|
||||
}
|
||||
|
||||
func (t trustPinChecker) certsCheck(leafCert *x509.Certificate, intCerts []*x509.Certificate) bool {
|
||||
// reconstruct the leaf + intermediate cert chain, which is bundled as {leaf, intermediates...},
|
||||
// in order to get the matching id in the root file
|
||||
leafCertID, err := trustmanager.FingerprintCert(leafCert)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
rootKeys := trustmanager.CertsToKeys([]*x509.Certificate{leafCert}, map[string][]*x509.Certificate{leafCertID: intCerts})
|
||||
for keyID := range rootKeys {
|
||||
if utils.StrSliceContains(t.pinnedCertIDs, keyID) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (t trustPinChecker) caCheck(leafCert *x509.Certificate, intCerts []*x509.Certificate) bool {
|
||||
// Use intermediate certificates included in the root TUF metadata for our validation
|
||||
caIntPool := x509.NewCertPool()
|
||||
for _, intCert := range intCerts {
|
||||
caIntPool.AddCert(intCert)
|
||||
}
|
||||
// Attempt to find a valid certificate chain from the leaf cert to CA root
|
||||
// Use this certificate if such a valid chain exists (possibly using intermediates)
|
||||
if _, err := leafCert.Verify(x509.VerifyOptions{Roots: t.pinnedCAPool, Intermediates: caIntPool}); err == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (t trustPinChecker) tofusCheck(leafCert *x509.Certificate, intCerts []*x509.Certificate) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Will return the CA filepath corresponding to the most specific (longest) entry in the map that is still a prefix
|
||||
// of the provided gun. Returns an error if no entry matches this GUN as a prefix.
|
||||
func getPinnedCAFilepathByPrefix(gun string, t TrustPinConfig) (string, error) {
|
||||
specificGUN := ""
|
||||
specificCAFilepath := ""
|
||||
foundCA := false
|
||||
for gunPrefix, caFilepath := range t.CA {
|
||||
if strings.HasPrefix(gun, gunPrefix) && len(gunPrefix) >= len(specificGUN) {
|
||||
specificGUN = gunPrefix
|
||||
specificCAFilepath = caFilepath
|
||||
foundCA = true
|
||||
}
|
||||
}
|
||||
if !foundCA {
|
||||
return "", fmt.Errorf("could not find pinned CA for GUN: %s\n", gun)
|
||||
}
|
||||
return specificCAFilepath, nil
|
||||
}
|
|
@ -75,7 +75,7 @@ func (c *Client) update() error {
|
|||
return err
|
||||
}
|
||||
// will always need top level targets at a minimum
|
||||
err = c.downloadTargets("targets")
|
||||
err = c.downloadTargets(data.CanonicalTargetsRole)
|
||||
if err != nil {
|
||||
logrus.Debugf("Client Update (Targets): %s", err.Error())
|
||||
return err
|
||||
|
@ -93,7 +93,7 @@ func (c Client) checkRoot() error {
|
|||
|
||||
expectedHashes := c.local.Snapshot.Signed.Meta[role].Hashes
|
||||
|
||||
raw, err := c.cache.GetMeta("root", size)
|
||||
raw, err := c.cache.GetMeta(data.CanonicalRootRole, size)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -331,7 +331,7 @@ func (c *Client) downloadSnapshot() error {
|
|||
size := c.local.Timestamp.Signed.Meta[role].Length
|
||||
expectedHashes := c.local.Timestamp.Signed.Meta[role].Hashes
|
||||
if len(expectedHashes) == 0 {
|
||||
return data.ErrMissingMeta{Role: "snapshot"}
|
||||
return data.ErrMissingMeta{Role: data.CanonicalSnapshotRole}
|
||||
}
|
||||
|
||||
var download bool
|
||||
|
|
|
@ -20,3 +20,21 @@ type ErrMissingMeta struct {
|
|||
func (e ErrMissingMeta) Error() string {
|
||||
return fmt.Sprintf("tuf: sha256 checksum required for %s", e.Role)
|
||||
}
|
||||
|
||||
// ErrInvalidChecksum is the error to be returned when checksum is invalid
|
||||
type ErrInvalidChecksum struct {
|
||||
alg string
|
||||
}
|
||||
|
||||
func (e ErrInvalidChecksum) Error() string {
|
||||
return fmt.Sprintf("%s checksum invalid", e.alg)
|
||||
}
|
||||
|
||||
// ErrMismatchedChecksum is the error to be returned when checksum is mismatched
|
||||
type ErrMismatchedChecksum struct {
|
||||
alg string
|
||||
}
|
||||
|
||||
func (e ErrMismatchedChecksum) Error() string {
|
||||
return fmt.Sprintf("%s checksum mismatched", e.alg)
|
||||
}
|
||||
|
|
|
@ -116,6 +116,22 @@ func (b BaseRole) ListKeyIDs() []string {
|
|||
return listKeyIDs(b.Keys)
|
||||
}
|
||||
|
||||
// Equals returns whether this BaseRole equals another BaseRole
|
||||
func (b BaseRole) Equals(o BaseRole) bool {
|
||||
if b.Threshold != o.Threshold || b.Name != o.Name || len(b.Keys) != len(o.Keys) {
|
||||
return false
|
||||
}
|
||||
|
||||
for keyID, key := range b.Keys {
|
||||
oKey, ok := o.Keys[keyID]
|
||||
if !ok || key.ID() != oKey.ID() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// DelegationRole is an internal representation of a delegation role, with its public keys included
|
||||
type DelegationRole struct {
|
||||
BaseRole
|
||||
|
|
|
@ -2,7 +2,6 @@ package data
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/docker/go/canonical/json"
|
||||
)
|
||||
|
@ -16,9 +15,7 @@ type SignedRoot struct {
|
|||
|
||||
// Root is the Signed component of a root.json
|
||||
type Root struct {
|
||||
Type string `json:"_type"`
|
||||
Version int `json:"version"`
|
||||
Expires time.Time `json:"expires"`
|
||||
SignedCommon
|
||||
Keys Keys `json:"keys"`
|
||||
Roles map[string]*RootRole `json:"roles"`
|
||||
ConsistentSnapshot bool `json:"consistent_snapshot"`
|
||||
|
@ -34,6 +31,11 @@ func isValidRootStructure(r Root) error {
|
|||
role: CanonicalRootRole, msg: fmt.Sprintf("expected type %s, not %s", expectedType, r.Type)}
|
||||
}
|
||||
|
||||
if r.Version < 0 {
|
||||
return ErrInvalidMetadata{
|
||||
role: CanonicalRootRole, msg: "version cannot be negative"}
|
||||
}
|
||||
|
||||
// all the base roles MUST appear in the root.json - other roles are allowed,
|
||||
// but other than the mirror role (not currently supported) are out of spec
|
||||
for _, roleName := range BaseRoles {
|
||||
|
@ -72,9 +74,11 @@ func NewRoot(keys map[string]PublicKey, roles map[string]*RootRole, consistent b
|
|||
signedRoot := &SignedRoot{
|
||||
Signatures: make([]Signature, 0),
|
||||
Signed: Root{
|
||||
Type: TUFTypes[CanonicalRootRole],
|
||||
Version: 0,
|
||||
Expires: DefaultExpires(CanonicalRootRole),
|
||||
SignedCommon: SignedCommon{
|
||||
Type: TUFTypes[CanonicalRootRole],
|
||||
Version: 0,
|
||||
Expires: DefaultExpires(CanonicalRootRole),
|
||||
},
|
||||
Keys: keys,
|
||||
Roles: roles,
|
||||
ConsistentSnapshot: consistent,
|
||||
|
@ -146,6 +150,12 @@ func (r SignedRoot) MarshalJSON() ([]byte, error) {
|
|||
// that it is a valid SignedRoot
|
||||
func RootFromSigned(s *Signed) (*SignedRoot, error) {
|
||||
r := Root{}
|
||||
if s.Signed == nil {
|
||||
return nil, ErrInvalidMetadata{
|
||||
role: CanonicalRootRole,
|
||||
msg: "root file contained an empty payload",
|
||||
}
|
||||
}
|
||||
if err := defaultSerializer.Unmarshal(*s.Signed, &r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package data
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/go/canonical/json"
|
||||
|
@ -19,10 +18,8 @@ type SignedSnapshot struct {
|
|||
|
||||
// Snapshot is the Signed component of a snapshot.json
|
||||
type Snapshot struct {
|
||||
Type string `json:"_type"`
|
||||
Version int `json:"version"`
|
||||
Expires time.Time `json:"expires"`
|
||||
Meta Files `json:"meta"`
|
||||
SignedCommon
|
||||
Meta Files `json:"meta"`
|
||||
}
|
||||
|
||||
// isValidSnapshotStructure returns an error, or nil, depending on whether the content of the
|
||||
|
@ -35,6 +32,11 @@ func isValidSnapshotStructure(s Snapshot) error {
|
|||
role: CanonicalSnapshotRole, msg: fmt.Sprintf("expected type %s, not %s", expectedType, s.Type)}
|
||||
}
|
||||
|
||||
if s.Version < 0 {
|
||||
return ErrInvalidMetadata{
|
||||
role: CanonicalSnapshotRole, msg: "version cannot be negative"}
|
||||
}
|
||||
|
||||
for _, role := range []string{CanonicalRootRole, CanonicalTargetsRole} {
|
||||
// Meta is a map of FileMeta, so if the role isn't in the map it returns
|
||||
// an empty FileMeta, which has an empty map, and you can check on keys
|
||||
|
@ -82,9 +84,11 @@ func NewSnapshot(root *Signed, targets *Signed) (*SignedSnapshot, error) {
|
|||
return &SignedSnapshot{
|
||||
Signatures: make([]Signature, 0),
|
||||
Signed: Snapshot{
|
||||
Type: TUFTypes["snapshot"],
|
||||
Version: 0,
|
||||
Expires: DefaultExpires("snapshot"),
|
||||
SignedCommon: SignedCommon{
|
||||
Type: TUFTypes[CanonicalSnapshotRole],
|
||||
Version: 0,
|
||||
Expires: DefaultExpires(CanonicalSnapshotRole),
|
||||
},
|
||||
Meta: Files{
|
||||
CanonicalRootRole: rootMeta,
|
||||
CanonicalTargetsRole: targetsMeta,
|
||||
|
|
|
@ -38,6 +38,10 @@ func isValidTargetsStructure(t Targets, roleName string) error {
|
|||
role: roleName, msg: fmt.Sprintf("expected type %s, not %s", expectedType, t.Type)}
|
||||
}
|
||||
|
||||
if t.Version < 0 {
|
||||
return ErrInvalidMetadata{role: roleName, msg: "version cannot be negative"}
|
||||
}
|
||||
|
||||
for _, roleObj := range t.Delegations.Roles {
|
||||
if !IsDelegation(roleObj.Name) || path.Dir(roleObj.Name) != roleName {
|
||||
return ErrInvalidMetadata{
|
||||
|
|
|
@ -3,7 +3,6 @@ package data
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/docker/go/canonical/json"
|
||||
"github.com/docker/notary"
|
||||
|
@ -18,10 +17,8 @@ type SignedTimestamp struct {
|
|||
|
||||
// Timestamp is the Signed component of a timestamp.json
|
||||
type Timestamp struct {
|
||||
Type string `json:"_type"`
|
||||
Version int `json:"version"`
|
||||
Expires time.Time `json:"expires"`
|
||||
Meta Files `json:"meta"`
|
||||
SignedCommon
|
||||
Meta Files `json:"meta"`
|
||||
}
|
||||
|
||||
// isValidTimestampStructure returns an error, or nil, depending on whether the content of the struct
|
||||
|
@ -34,6 +31,11 @@ func isValidTimestampStructure(t Timestamp) error {
|
|||
role: CanonicalTimestampRole, msg: fmt.Sprintf("expected type %s, not %s", expectedType, t.Type)}
|
||||
}
|
||||
|
||||
if t.Version < 0 {
|
||||
return ErrInvalidMetadata{
|
||||
role: CanonicalTimestampRole, msg: "version cannot be negative"}
|
||||
}
|
||||
|
||||
// Meta is a map of FileMeta, so if the role isn't in the map it returns
|
||||
// an empty FileMeta, which has an empty map, and you can check on keys
|
||||
// from an empty map.
|
||||
|
@ -64,9 +66,11 @@ func NewTimestamp(snapshot *Signed) (*SignedTimestamp, error) {
|
|||
return &SignedTimestamp{
|
||||
Signatures: make([]Signature, 0),
|
||||
Signed: Timestamp{
|
||||
Type: TUFTypes["timestamp"],
|
||||
Version: 0,
|
||||
Expires: DefaultExpires("timestamp"),
|
||||
SignedCommon: SignedCommon{
|
||||
Type: TUFTypes[CanonicalTimestampRole],
|
||||
Version: 0,
|
||||
Expires: DefaultExpires(CanonicalTimestampRole),
|
||||
},
|
||||
Meta: Files{
|
||||
CanonicalSnapshotRole: snapshotMeta,
|
||||
},
|
||||
|
|
|
@ -141,13 +141,13 @@ func CheckHashes(payload []byte, hashes Hashes) error {
|
|||
case notary.SHA256:
|
||||
checksum := sha256.Sum256(payload)
|
||||
if subtle.ConstantTimeCompare(checksum[:], v) == 0 {
|
||||
return fmt.Errorf("%s checksum mismatched", k)
|
||||
return ErrMismatchedChecksum{alg: notary.SHA256}
|
||||
}
|
||||
cnt++
|
||||
case notary.SHA512:
|
||||
checksum := sha512.Sum512(payload)
|
||||
if subtle.ConstantTimeCompare(checksum[:], v) == 0 {
|
||||
return fmt.Errorf("%s checksum mismatched", k)
|
||||
return ErrMismatchedChecksum{alg: notary.SHA512}
|
||||
}
|
||||
cnt++
|
||||
}
|
||||
|
@ -169,12 +169,12 @@ func CheckValidHashStructures(hashes Hashes) error {
|
|||
switch k {
|
||||
case notary.SHA256:
|
||||
if len(v) != sha256.Size {
|
||||
return fmt.Errorf("invalid %s checksum", notary.SHA256)
|
||||
return ErrInvalidChecksum{alg: notary.SHA256}
|
||||
}
|
||||
cnt++
|
||||
case notary.SHA512:
|
||||
if len(v) != sha512.Size {
|
||||
return fmt.Errorf("invalid %s checksum", notary.SHA512)
|
||||
return ErrInvalidChecksum{alg: notary.SHA512}
|
||||
}
|
||||
cnt++
|
||||
}
|
||||
|
|
|
@ -5,14 +5,21 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
// ErrInsufficientSignatures - do not have enough signatures on a piece of
|
||||
// ErrInsufficientSignatures - can not create enough signatures on a piece of
|
||||
// metadata
|
||||
type ErrInsufficientSignatures struct {
|
||||
Name string
|
||||
FoundKeys int
|
||||
NeededKeys int
|
||||
MissingKeyIDs []string
|
||||
}
|
||||
|
||||
func (e ErrInsufficientSignatures) Error() string {
|
||||
return fmt.Sprintf("tuf: insufficient signatures: %s", e.Name)
|
||||
candidates := strings.Join(e.MissingKeyIDs, ", ")
|
||||
if e.FoundKeys == 0 {
|
||||
return fmt.Sprintf("signing keys not available, need %d keys out of: %s", e.NeededKeys, candidates)
|
||||
}
|
||||
return fmt.Sprintf("not enough signing keys: got %d of %d needed keys, other candidates: %s",
|
||||
e.FoundKeys, e.NeededKeys, candidates)
|
||||
}
|
||||
|
||||
// ErrExpired indicates a piece of metadata has expired
|
||||
|
@ -51,6 +58,13 @@ func (e ErrInvalidKeyType) Error() string {
|
|||
return "key type is not valid for signature"
|
||||
}
|
||||
|
||||
// ErrInvalidKeyID indicates the specified key ID was incorrect for its associated data
|
||||
type ErrInvalidKeyID struct{}
|
||||
|
||||
func (e ErrInvalidKeyID) Error() string {
|
||||
return "key ID is not valid for key content"
|
||||
}
|
||||
|
||||
// ErrInvalidKeyLength indicates that while we may support the cipher, the provided
|
||||
// key length is not specifically supported, i.e. we support RSA, but not 1024 bit keys
|
||||
type ErrInvalidKeyLength struct {
|
||||
|
|
|
@ -13,54 +13,73 @@ package signed
|
|||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/notary/trustmanager"
|
||||
"github.com/docker/notary/tuf/data"
|
||||
"github.com/docker/notary/tuf/utils"
|
||||
)
|
||||
|
||||
// Sign takes a data.Signed and a key, calculated and adds the signature
|
||||
// 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 {
|
||||
logrus.Debugf("sign called with %d keys", len(keys))
|
||||
// Sign takes a data.Signed and a cryptoservice containing private keys,
|
||||
// calculates and adds at least minSignature signatures using signingKeys the
|
||||
// data.Signed. It will also clean up any signatures that are not in produced
|
||||
// by either a signingKey or an otherWhitelistedKey.
|
||||
// Note that in most cases, otherWhitelistedKeys should probably be null. They
|
||||
// are for keys you don't want to sign with, but you also don't want to remove
|
||||
// existing signatures by those keys. For instance, if you want to call Sign
|
||||
// multiple times with different sets of signing keys without undoing removing
|
||||
// signatures produced by the previous call to Sign.
|
||||
func Sign(service CryptoService, s *data.Signed, signingKeys []data.PublicKey,
|
||||
minSignatures int, otherWhitelistedKeys []data.PublicKey) error {
|
||||
|
||||
logrus.Debugf("sign called with %d/%d required keys", minSignatures, len(signingKeys))
|
||||
signatures := make([]data.Signature, 0, len(s.Signatures)+1)
|
||||
signingKeyIDs := make(map[string]struct{})
|
||||
tufIDs := make(map[string]data.PublicKey)
|
||||
ids := make([]string, 0, len(keys))
|
||||
|
||||
privKeys := make(map[string]data.PrivateKey)
|
||||
|
||||
// Get all the private key objects related to the public keys
|
||||
for _, key := range keys {
|
||||
missingKeyIDs := []string{}
|
||||
for _, key := range signingKeys {
|
||||
canonicalID, err := utils.CanonicalKeyID(key)
|
||||
ids = append(ids, canonicalID)
|
||||
tufIDs[key.ID()] = key
|
||||
if err != nil {
|
||||
continue
|
||||
return err
|
||||
}
|
||||
k, _, err := service.GetPrivateKey(canonicalID)
|
||||
if err != nil {
|
||||
continue
|
||||
if _, ok := err.(trustmanager.ErrKeyNotFound); ok {
|
||||
missingKeyIDs = append(missingKeyIDs, canonicalID)
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
privKeys[key.ID()] = k
|
||||
}
|
||||
|
||||
// Check to ensure we have at least one signing key
|
||||
if len(privKeys) == 0 {
|
||||
return ErrNoKeys{KeyIDs: ids}
|
||||
// include the list of otherWhitelistedKeys
|
||||
for _, key := range otherWhitelistedKeys {
|
||||
if _, ok := tufIDs[key.ID()]; !ok {
|
||||
tufIDs[key.ID()] = key
|
||||
}
|
||||
}
|
||||
|
||||
// Check to ensure we have enough signing keys
|
||||
if len(privKeys) < minSignatures {
|
||||
return ErrInsufficientSignatures{FoundKeys: len(privKeys),
|
||||
NeededKeys: minSignatures, MissingKeyIDs: missingKeyIDs}
|
||||
}
|
||||
|
||||
emptyStruct := struct{}{}
|
||||
// Do signing and generate list of signatures
|
||||
for keyID, pk := range privKeys {
|
||||
sig, err := pk.Sign(rand.Reader, *s.Signed, nil)
|
||||
if err != nil {
|
||||
logrus.Debugf("Failed to sign with key: %s. Reason: %v", keyID, err)
|
||||
continue
|
||||
return err
|
||||
}
|
||||
signingKeyIDs[keyID] = struct{}{}
|
||||
signingKeyIDs[keyID] = emptyStruct
|
||||
signatures = append(signatures, data.Signature{
|
||||
KeyID: keyID,
|
||||
Method: pk.SignatureAlgorithm(),
|
||||
|
@ -68,15 +87,6 @@ func Sign(service CryptoService, s *data.Signed, keys ...data.PublicKey) error {
|
|||
})
|
||||
}
|
||||
|
||||
// Check we produced at least on signature
|
||||
if len(signatures) < 1 {
|
||||
return ErrInsufficientSignatures{
|
||||
Name: fmt.Sprintf(
|
||||
"cryptoservice failed to produce any signatures for keys with IDs: %v",
|
||||
ids),
|
||||
}
|
||||
}
|
||||
|
||||
for _, sig := range s.Signatures {
|
||||
if _, ok := signingKeyIDs[sig.KeyID]; ok {
|
||||
// key is in the set of key IDs for which a signature has been created
|
||||
|
|
|
@ -21,47 +21,6 @@ var (
|
|||
ErrWrongType = errors.New("tuf: meta file has wrong type")
|
||||
)
|
||||
|
||||
// VerifyRoot checks if a given root file is valid against a known set of keys.
|
||||
// Threshold is always assumed to be 1
|
||||
func VerifyRoot(s *data.Signed, minVersion int, keys map[string]data.PublicKey) error {
|
||||
if len(s.Signatures) == 0 {
|
||||
return ErrNoSignatures
|
||||
}
|
||||
|
||||
var decoded map[string]interface{}
|
||||
if err := json.Unmarshal(*s.Signed, &decoded); err != nil {
|
||||
return err
|
||||
}
|
||||
msg, err := json.MarshalCanonical(decoded)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, sig := range s.Signatures {
|
||||
// method lookup is consistent due to Unmarshal JSON doing lower case for us.
|
||||
method := sig.Method
|
||||
verifier, ok := Verifiers[method]
|
||||
if !ok {
|
||||
logrus.Debugf("continuing b/c signing method is not supported for verify root: %s\n", sig.Method)
|
||||
continue
|
||||
}
|
||||
|
||||
key, ok := keys[sig.KeyID]
|
||||
if !ok {
|
||||
logrus.Debugf("continuing b/c signing key isn't present in keys: %s\n", sig.KeyID)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := verifier.Verify(key, sig.Signature, msg); err != nil {
|
||||
logrus.Debugf("continuing b/c signature was invalid\n")
|
||||
continue
|
||||
}
|
||||
// threshold of 1 so return on first success
|
||||
return verifyMeta(s, data.CanonicalRootRole, minVersion)
|
||||
}
|
||||
return ErrRoleThreshold{}
|
||||
}
|
||||
|
||||
// Verify checks the signatures and metadata (expiry, version) for the signed role
|
||||
// data
|
||||
func Verify(s *data.Signed, role data.BaseRole, minVersion int) error {
|
||||
|
@ -125,6 +84,10 @@ func VerifySignatures(s *data.Signed, roleData data.BaseRole) error {
|
|||
logrus.Debugf("continuing b/c keyid lookup was nil: %s\n", sig.KeyID)
|
||||
continue
|
||||
}
|
||||
// Check that the signature key ID actually matches the content ID of the key
|
||||
if key.ID() != sig.KeyID {
|
||||
return ErrInvalidKeyID{}
|
||||
}
|
||||
if err := VerifySignature(msg, sig, key); err != nil {
|
||||
logrus.Debugf("continuing b/c %s", err.Error())
|
||||
continue
|
||||
|
|
|
@ -39,7 +39,8 @@ func (f *FilesystemStore) getPath(name string) string {
|
|||
}
|
||||
|
||||
// GetMeta returns the meta for the given name (a role) up to size bytes
|
||||
// If size is -1, this corresponds to "infinite," but we cut off at 100MB
|
||||
// If size is -1, this corresponds to "infinite," but we cut off at the
|
||||
// predefined threshold "notary.MaxDownloadSize".
|
||||
func (f *FilesystemStore) GetMeta(name string, size int64) ([]byte, error) {
|
||||
meta, err := ioutil.ReadFile(f.getPath(name))
|
||||
if err != nil {
|
||||
|
|
|
@ -6,6 +6,8 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"path"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -62,6 +64,13 @@ type Repo struct {
|
|||
Snapshot *data.SignedSnapshot
|
||||
Timestamp *data.SignedTimestamp
|
||||
cryptoService signed.CryptoService
|
||||
|
||||
// Because Repo is a mutable structure, these keep track of what the root
|
||||
// role was when a root is set on the repo (as opposed to what it might be
|
||||
// after things like AddBaseKeys and RemoveBaseKeys have been called on it).
|
||||
// If we know what the original was, we'll if and how to handle root
|
||||
// rotations.
|
||||
originalRootRole data.BaseRole
|
||||
}
|
||||
|
||||
// NewRepo initializes a Repo instance with a CryptoService.
|
||||
|
@ -90,7 +99,7 @@ func (tr *Repo) AddBaseKeys(role string, keys ...data.PublicKey) error {
|
|||
tr.Root.Dirty = true
|
||||
|
||||
// also, whichever role was switched out needs to be re-signed
|
||||
// root has already been marked dirty
|
||||
// root has already been marked dirty.
|
||||
switch role {
|
||||
case data.CanonicalSnapshotRole:
|
||||
if tr.Snapshot != nil {
|
||||
|
@ -128,17 +137,38 @@ func (tr *Repo) RemoveBaseKeys(role string, keyIDs ...string) error {
|
|||
}
|
||||
var keep []string
|
||||
toDelete := make(map[string]struct{})
|
||||
emptyStruct := struct{}{}
|
||||
// remove keys from specified role
|
||||
for _, k := range keyIDs {
|
||||
toDelete[k] = struct{}{}
|
||||
for _, rk := range tr.Root.Signed.Roles[role].KeyIDs {
|
||||
if k != rk {
|
||||
keep = append(keep, rk)
|
||||
}
|
||||
toDelete[k] = emptyStruct
|
||||
}
|
||||
|
||||
oldKeyIDs := tr.Root.Signed.Roles[role].KeyIDs
|
||||
for _, rk := range oldKeyIDs {
|
||||
if _, ok := toDelete[rk]; !ok {
|
||||
keep = append(keep, rk)
|
||||
}
|
||||
}
|
||||
|
||||
tr.Root.Signed.Roles[role].KeyIDs = keep
|
||||
|
||||
// also, whichever role had keys removed needs to be re-signed
|
||||
// root has already been marked dirty.
|
||||
switch role {
|
||||
case data.CanonicalSnapshotRole:
|
||||
if tr.Snapshot != nil {
|
||||
tr.Snapshot.Dirty = true
|
||||
}
|
||||
case data.CanonicalTargetsRole:
|
||||
if target, ok := tr.Targets[data.CanonicalTargetsRole]; ok {
|
||||
target.Dirty = true
|
||||
}
|
||||
case data.CanonicalTimestampRole:
|
||||
if tr.Timestamp != nil {
|
||||
tr.Timestamp.Dirty = true
|
||||
}
|
||||
}
|
||||
|
||||
// determine which keys are no longer in use by any roles
|
||||
for roleName, r := range tr.Root.Signed.Roles {
|
||||
if roleName == role {
|
||||
|
@ -151,13 +181,16 @@ func (tr *Repo) RemoveBaseKeys(role string, keyIDs ...string) error {
|
|||
}
|
||||
}
|
||||
|
||||
// remove keys no longer in use by any roles
|
||||
for k := range toDelete {
|
||||
delete(tr.Root.Signed.Keys, k)
|
||||
// remove the signing key from the cryptoservice if it
|
||||
// isn't a root key. Root keys must be kept for rotation
|
||||
// signing
|
||||
if role != data.CanonicalRootRole {
|
||||
// Remove keys no longer in use by any roles, except for root keys.
|
||||
// Root private keys must be kept in tr.cryptoService to be able to sign
|
||||
// for rotation, and root certificates must be kept in tr.Root.SignedKeys
|
||||
// because we are not necessarily storing them elsewhere (tuf.Repo does not
|
||||
// depend on certs.Manager, that is an upper layer), and without storing
|
||||
// the certificates in their x509 form we are not able to do the
|
||||
// util.CanonicalKeyID conversion.
|
||||
if role != data.CanonicalRootRole {
|
||||
for k := range toDelete {
|
||||
delete(tr.Root.Signed.Keys, k)
|
||||
tr.cryptoService.RemoveKey(k)
|
||||
}
|
||||
}
|
||||
|
@ -459,6 +492,7 @@ func (tr *Repo) InitRoot(root, timestamp, snapshot, targets data.BaseRole, consi
|
|||
return err
|
||||
}
|
||||
tr.Root = r
|
||||
tr.originalRootRole = root
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -518,7 +552,11 @@ func (tr *Repo) InitTimestamp() error {
|
|||
// SetRoot sets the Repo.Root field to the SignedRoot object.
|
||||
func (tr *Repo) SetRoot(s *data.SignedRoot) error {
|
||||
tr.Root = s
|
||||
return nil
|
||||
var err error
|
||||
// originalRootRole is the root role prior to any mutations that might
|
||||
// occur on tr.Root.
|
||||
tr.originalRootRole, err = tr.Root.BuildBaseRole(data.CanonicalRootRole)
|
||||
return err
|
||||
}
|
||||
|
||||
// SetTimestamp parses the Signed object into a SignedTimestamp object
|
||||
|
@ -781,32 +819,161 @@ func (tr *Repo) UpdateTimestamp(s *data.Signed) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tr.Timestamp.Signed.Meta["snapshot"] = meta
|
||||
tr.Timestamp.Signed.Meta[data.CanonicalSnapshotRole] = meta
|
||||
tr.Timestamp.Dirty = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// SignRoot signs the root
|
||||
type versionedRootRole struct {
|
||||
data.BaseRole
|
||||
version int
|
||||
}
|
||||
|
||||
type versionedRootRoles []versionedRootRole
|
||||
|
||||
func (v versionedRootRoles) Len() int { return len(v) }
|
||||
func (v versionedRootRoles) Swap(i, j int) { v[i], v[j] = v[j], v[i] }
|
||||
func (v versionedRootRoles) Less(i, j int) bool { return v[i].version < v[j].version }
|
||||
|
||||
// SignRoot signs the root, using all keys from the "root" role (i.e. currently trusted)
|
||||
// as well as available keys used to sign the previous version, if the public part is
|
||||
// carried in tr.Root.Keys and the private key is available (i.e. probably previously
|
||||
// trusted keys, to allow rollover). If there are any errors, attempt to put root
|
||||
// back to the way it was (so version won't be incremented, for instance).
|
||||
func (tr *Repo) SignRoot(expires time.Time) (*data.Signed, error) {
|
||||
logrus.Debug("signing root...")
|
||||
tr.Root.Signed.Expires = expires
|
||||
tr.Root.Signed.Version++
|
||||
root, err := tr.GetBaseRole(data.CanonicalRootRole)
|
||||
|
||||
// duplicate root and attempt to modify it rather than the existing root
|
||||
rootBytes, err := tr.Root.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signed, err := tr.Root.ToSigned()
|
||||
tempRoot := data.SignedRoot{}
|
||||
if err := json.Unmarshal(rootBytes, &tempRoot); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
currRoot, err := tr.GetBaseRole(data.CanonicalRootRole)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signed, err = tr.sign(signed, root)
|
||||
|
||||
oldRootRoles := tr.getOldRootRoles()
|
||||
|
||||
var latestSavedRole data.BaseRole
|
||||
rolesToSignWith := make([]data.BaseRole, 0, len(oldRootRoles))
|
||||
|
||||
if len(oldRootRoles) > 0 {
|
||||
sort.Sort(oldRootRoles)
|
||||
for _, vRole := range oldRootRoles {
|
||||
rolesToSignWith = append(rolesToSignWith, vRole.BaseRole)
|
||||
}
|
||||
latest := rolesToSignWith[len(rolesToSignWith)-1]
|
||||
latestSavedRole = data.BaseRole{
|
||||
Name: data.CanonicalRootRole,
|
||||
Threshold: latest.Threshold,
|
||||
Keys: latest.Keys,
|
||||
}
|
||||
}
|
||||
|
||||
// if the root role has changed and original role had not been saved as a previous role, save it now
|
||||
if !tr.originalRootRole.Equals(currRoot) && !tr.originalRootRole.Equals(latestSavedRole) {
|
||||
rolesToSignWith = append(rolesToSignWith, tr.originalRootRole)
|
||||
latestSavedRole = tr.originalRootRole
|
||||
|
||||
versionName := oldRootVersionName(tempRoot.Signed.Version)
|
||||
tempRoot.Signed.Roles[versionName] = &data.RootRole{
|
||||
KeyIDs: latestSavedRole.ListKeyIDs(), Threshold: latestSavedRole.Threshold}
|
||||
|
||||
}
|
||||
|
||||
tempRoot.Signed.Expires = expires
|
||||
tempRoot.Signed.Version++
|
||||
|
||||
// if the current role doesn't match with the latest saved role, save it
|
||||
if !currRoot.Equals(latestSavedRole) {
|
||||
rolesToSignWith = append(rolesToSignWith, currRoot)
|
||||
|
||||
versionName := oldRootVersionName(tempRoot.Signed.Version)
|
||||
tempRoot.Signed.Roles[versionName] = &data.RootRole{
|
||||
KeyIDs: currRoot.ListKeyIDs(), Threshold: currRoot.Threshold}
|
||||
}
|
||||
|
||||
signed, err := tempRoot.ToSigned()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signed, err = tr.sign(signed, rolesToSignWith, tr.getOptionalRootKeys(rolesToSignWith))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tr.Root = &tempRoot
|
||||
tr.Root.Signatures = signed.Signatures
|
||||
tr.originalRootRole = currRoot
|
||||
return signed, nil
|
||||
}
|
||||
|
||||
// get all the saved previous roles <= the current root version
|
||||
func (tr *Repo) getOldRootRoles() versionedRootRoles {
|
||||
oldRootRoles := make(versionedRootRoles, 0, len(tr.Root.Signed.Roles))
|
||||
|
||||
// now go through the old roles
|
||||
for roleName := range tr.Root.Signed.Roles {
|
||||
// ensure that the rolename matches our format and that the version is
|
||||
// not too high
|
||||
if data.ValidRole(roleName) {
|
||||
continue
|
||||
}
|
||||
nameTokens := strings.Split(roleName, ".")
|
||||
if len(nameTokens) != 2 || nameTokens[0] != data.CanonicalRootRole {
|
||||
continue
|
||||
}
|
||||
version, err := strconv.Atoi(nameTokens[1])
|
||||
if err != nil || version > tr.Root.Signed.Version {
|
||||
continue
|
||||
}
|
||||
|
||||
// ignore invalid roles, which shouldn't happen
|
||||
oldRole, err := tr.Root.BuildBaseRole(roleName)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
oldRootRoles = append(oldRootRoles, versionedRootRole{BaseRole: oldRole, version: version})
|
||||
}
|
||||
|
||||
return oldRootRoles
|
||||
}
|
||||
|
||||
// gets any extra optional root keys from the existing root.json signatures
|
||||
// (because older repositories that have already done root rotation may not
|
||||
// necessarily have older root roles)
|
||||
func (tr *Repo) getOptionalRootKeys(signingRoles []data.BaseRole) []data.PublicKey {
|
||||
oldKeysMap := make(map[string]data.PublicKey)
|
||||
for _, oldSig := range tr.Root.Signatures {
|
||||
if k, ok := tr.Root.Signed.Keys[oldSig.KeyID]; ok {
|
||||
oldKeysMap[k.ID()] = k
|
||||
}
|
||||
}
|
||||
for _, role := range signingRoles {
|
||||
for keyID := range role.Keys {
|
||||
delete(oldKeysMap, keyID)
|
||||
}
|
||||
}
|
||||
|
||||
oldKeys := make([]data.PublicKey, 0, len(oldKeysMap))
|
||||
for _, key := range oldKeysMap {
|
||||
oldKeys = append(oldKeys, key)
|
||||
}
|
||||
|
||||
return oldKeys
|
||||
}
|
||||
|
||||
func oldRootVersionName(version int) string {
|
||||
return fmt.Sprintf("%s.%v", data.CanonicalRootRole, version)
|
||||
}
|
||||
|
||||
// SignTargets signs the targets file for the given top level or delegated targets role
|
||||
func (tr *Repo) SignTargets(role string, expires time.Time) (*data.Signed, error) {
|
||||
logrus.Debugf("sign targets called for role %s", role)
|
||||
|
@ -838,7 +1005,7 @@ func (tr *Repo) SignTargets(role string, expires time.Time) (*data.Signed, error
|
|||
return nil, err
|
||||
}
|
||||
|
||||
signed, err = tr.sign(signed, targets)
|
||||
signed, err = tr.sign(signed, []data.BaseRole{targets}, nil)
|
||||
if err != nil {
|
||||
logrus.Debug("errored signing ", role)
|
||||
return nil, err
|
||||
|
@ -854,7 +1021,7 @@ func (tr *Repo) SignSnapshot(expires time.Time) (*data.Signed, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = tr.UpdateSnapshot("root", signedRoot)
|
||||
err = tr.UpdateSnapshot(data.CanonicalRootRole, signedRoot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -880,7 +1047,7 @@ func (tr *Repo) SignSnapshot(expires time.Time) (*data.Signed, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signed, err = tr.sign(signed, snapshot)
|
||||
signed, err = tr.sign(signed, []data.BaseRole{snapshot}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -909,7 +1076,7 @@ func (tr *Repo) SignTimestamp(expires time.Time) (*data.Signed, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signed, err = tr.sign(signed, timestamp)
|
||||
signed, err = tr.sign(signed, []data.BaseRole{timestamp}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -918,9 +1085,17 @@ func (tr *Repo) SignTimestamp(expires time.Time) (*data.Signed, error) {
|
|||
return signed, nil
|
||||
}
|
||||
|
||||
func (tr Repo) sign(signedData *data.Signed, role data.BaseRole) (*data.Signed, error) {
|
||||
if err := signed.Sign(tr.cryptoService, signedData, role.ListKeys()...); err != nil {
|
||||
return nil, err
|
||||
func (tr Repo) sign(signedData *data.Signed, roles []data.BaseRole, optionalKeys []data.PublicKey) (*data.Signed, error) {
|
||||
validKeys := optionalKeys
|
||||
for _, r := range roles {
|
||||
roleKeys := r.ListKeys()
|
||||
validKeys = append(roleKeys, validKeys...)
|
||||
if err := signed.Sign(tr.cryptoService, signedData, roleKeys, r.Threshold, validKeys); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// Attempt to sign with the optional keys, but ignore any errors, because these keys are optional
|
||||
signed.Sign(tr.cryptoService, signedData, optionalKeys, 0, validKeys)
|
||||
|
||||
return signedData, nil
|
||||
}
|
||||
|
|
|
@ -98,7 +98,7 @@ func HashedPaths(path string, hashes data.Hashes) []string {
|
|||
|
||||
// CanonicalKeyID returns the ID of the public bytes version of a TUF key.
|
||||
// On regular RSA/ECDSA TUF keys, this is just the key ID. On X509 RSA/ECDSA
|
||||
// TUF keys, this is the key ID of the public key part of the key.
|
||||
// TUF keys, this is the key ID of the public key part of the key in the leaf cert
|
||||
func CanonicalKeyID(k data.PublicKey) (string, error) {
|
||||
switch k.Algorithm() {
|
||||
case data.ECDSAx509Key, data.RSAx509Key:
|
||||
|
|
Loading…
Reference in New Issue