mirror of https://github.com/docker/cli.git
vendor: github.com/theupdateframework/notary v0.7.0
full diff: https://github.com/theupdateframework/notary/compare/v0.6.1...v0.7.0
Changelog:
v0.7.0 12/01/2021
------------------------
- Switch to Go modules
- Use golang/x/crypto for ed25519
- Update Go version
- Update dependency versions
- Fixes from using Gosec for source analysis
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 9f6966d4ec
)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
parent
897293c7c7
commit
f508ce9db7
|
@ -1,5 +1,4 @@
|
||||||
cloud.google.com/go ceeb313ad77b789a7fa5287b36a1d127b69b7093 # v0.44.3
|
cloud.google.com/go ceeb313ad77b789a7fa5287b36a1d127b69b7093 # v0.44.3
|
||||||
github.com/agl/ed25519 5312a61534124124185d41f09206b9fef1d88403
|
|
||||||
github.com/Azure/go-ansiterm d6e3b3328b783f23731bc4d058875b0371ff8109
|
github.com/Azure/go-ansiterm d6e3b3328b783f23731bc4d058875b0371ff8109
|
||||||
github.com/beorn7/perks 37c8de3658fcb183f997c4e13e8337516ab753e6 # v1.0.1
|
github.com/beorn7/perks 37c8de3658fcb183f997c4e13e8337516ab753e6 # v1.0.1
|
||||||
github.com/cespare/xxhash/v2 d7df74196a9e781ede915320c11c378c1b2f3a1f # v2.1.1
|
github.com/cespare/xxhash/v2 d7df74196a9e781ede915320c11c378c1b2f3a1f # v2.1.1
|
||||||
|
@ -68,7 +67,7 @@ github.com/shurcooL/sanitized_anchor_name 7bfe4c7ecddb3666a94b053b422c
|
||||||
github.com/sirupsen/logrus 6699a89a232f3db797f2e280639854bbc4b89725 # v1.7.0
|
github.com/sirupsen/logrus 6699a89a232f3db797f2e280639854bbc4b89725 # v1.7.0
|
||||||
github.com/spf13/cobra 86f8bfd7fef868a174e1b606783bd7f5c82ddf8f # v1.1.1
|
github.com/spf13/cobra 86f8bfd7fef868a174e1b606783bd7f5c82ddf8f # v1.1.1
|
||||||
github.com/spf13/pflag 2e9d26c8c37aae03e3f9d4e90b7116f5accb7cab # v1.0.5
|
github.com/spf13/pflag 2e9d26c8c37aae03e3f9d4e90b7116f5accb7cab # v1.0.5
|
||||||
github.com/theupdateframework/notary d6e1431feb32348e0650bf7551ac5cffd01d857b # v0.6.1
|
github.com/theupdateframework/notary b0b6bfdd4933081e8d5ae026b24e8337311dd598 # v0.7.0
|
||||||
github.com/tonistiigi/fsutil 0834f99b7b85462efb69b4f571a4fa3ca7da5ac9
|
github.com/tonistiigi/fsutil 0834f99b7b85462efb69b4f571a4fa3ca7da5ac9
|
||||||
github.com/tonistiigi/go-rosetta f79598599c5d34ea253b56a1d7c89bc6a96de7db
|
github.com/tonistiigi/go-rosetta f79598599c5d34ea253b56a1d7c89bc6a96de7db
|
||||||
github.com/tonistiigi/units 6950e57a87eaf136bbe44ef2ec8e75b9e3569de2
|
github.com/tonistiigi/units 6950e57a87eaf136bbe44ef2ec8e75b9e3569de2
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
Copyright (c) 2012 The Go Authors. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above
|
|
||||||
copyright notice, this list of conditions and the following disclaimer
|
|
||||||
in the documentation and/or other materials provided with the
|
|
||||||
distribution.
|
|
||||||
* Neither the name of Google Inc. nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
@ -1,127 +0,0 @@
|
||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package ed25519 implements the Ed25519 signature algorithm. See
|
|
||||||
// http://ed25519.cr.yp.to/.
|
|
||||||
package ed25519
|
|
||||||
|
|
||||||
// This code is a port of the public domain, "ref10" implementation of ed25519
|
|
||||||
// from SUPERCOP.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha512"
|
|
||||||
"crypto/subtle"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/agl/ed25519/edwards25519"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
PublicKeySize = 32
|
|
||||||
PrivateKeySize = 64
|
|
||||||
SignatureSize = 64
|
|
||||||
)
|
|
||||||
|
|
||||||
// GenerateKey generates a public/private key pair using randomness from rand.
|
|
||||||
func GenerateKey(rand io.Reader) (publicKey *[PublicKeySize]byte, privateKey *[PrivateKeySize]byte, err error) {
|
|
||||||
privateKey = new([64]byte)
|
|
||||||
publicKey = new([32]byte)
|
|
||||||
_, err = io.ReadFull(rand, privateKey[:32])
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
h := sha512.New()
|
|
||||||
h.Write(privateKey[:32])
|
|
||||||
digest := h.Sum(nil)
|
|
||||||
|
|
||||||
digest[0] &= 248
|
|
||||||
digest[31] &= 127
|
|
||||||
digest[31] |= 64
|
|
||||||
|
|
||||||
var A edwards25519.ExtendedGroupElement
|
|
||||||
var hBytes [32]byte
|
|
||||||
copy(hBytes[:], digest)
|
|
||||||
edwards25519.GeScalarMultBase(&A, &hBytes)
|
|
||||||
A.ToBytes(publicKey)
|
|
||||||
|
|
||||||
copy(privateKey[32:], publicKey[:])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign signs the message with privateKey and returns a signature.
|
|
||||||
func Sign(privateKey *[PrivateKeySize]byte, message []byte) *[SignatureSize]byte {
|
|
||||||
h := sha512.New()
|
|
||||||
h.Write(privateKey[:32])
|
|
||||||
|
|
||||||
var digest1, messageDigest, hramDigest [64]byte
|
|
||||||
var expandedSecretKey [32]byte
|
|
||||||
h.Sum(digest1[:0])
|
|
||||||
copy(expandedSecretKey[:], digest1[:])
|
|
||||||
expandedSecretKey[0] &= 248
|
|
||||||
expandedSecretKey[31] &= 63
|
|
||||||
expandedSecretKey[31] |= 64
|
|
||||||
|
|
||||||
h.Reset()
|
|
||||||
h.Write(digest1[32:])
|
|
||||||
h.Write(message)
|
|
||||||
h.Sum(messageDigest[:0])
|
|
||||||
|
|
||||||
var messageDigestReduced [32]byte
|
|
||||||
edwards25519.ScReduce(&messageDigestReduced, &messageDigest)
|
|
||||||
var R edwards25519.ExtendedGroupElement
|
|
||||||
edwards25519.GeScalarMultBase(&R, &messageDigestReduced)
|
|
||||||
|
|
||||||
var encodedR [32]byte
|
|
||||||
R.ToBytes(&encodedR)
|
|
||||||
|
|
||||||
h.Reset()
|
|
||||||
h.Write(encodedR[:])
|
|
||||||
h.Write(privateKey[32:])
|
|
||||||
h.Write(message)
|
|
||||||
h.Sum(hramDigest[:0])
|
|
||||||
var hramDigestReduced [32]byte
|
|
||||||
edwards25519.ScReduce(&hramDigestReduced, &hramDigest)
|
|
||||||
|
|
||||||
var s [32]byte
|
|
||||||
edwards25519.ScMulAdd(&s, &hramDigestReduced, &expandedSecretKey, &messageDigestReduced)
|
|
||||||
|
|
||||||
signature := new([64]byte)
|
|
||||||
copy(signature[:], encodedR[:])
|
|
||||||
copy(signature[32:], s[:])
|
|
||||||
return signature
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify returns true iff sig is a valid signature of message by publicKey.
|
|
||||||
func Verify(publicKey *[PublicKeySize]byte, message []byte, sig *[SignatureSize]byte) bool {
|
|
||||||
if sig[63]&224 != 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var A edwards25519.ExtendedGroupElement
|
|
||||||
if !A.FromBytes(publicKey) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
edwards25519.FeNeg(&A.X, &A.X)
|
|
||||||
edwards25519.FeNeg(&A.T, &A.T)
|
|
||||||
|
|
||||||
h := sha512.New()
|
|
||||||
h.Write(sig[:32])
|
|
||||||
h.Write(publicKey[:])
|
|
||||||
h.Write(message)
|
|
||||||
var digest [64]byte
|
|
||||||
h.Sum(digest[:0])
|
|
||||||
|
|
||||||
var hReduced [32]byte
|
|
||||||
edwards25519.ScReduce(&hReduced, &digest)
|
|
||||||
|
|
||||||
var R edwards25519.ProjectiveGroupElement
|
|
||||||
var b [32]byte
|
|
||||||
copy(b[:], sig[32:])
|
|
||||||
edwards25519.GeDoubleScalarMultVartime(&R, &hReduced, &A, &b)
|
|
||||||
|
|
||||||
var checkR [32]byte
|
|
||||||
R.ToBytes(&checkR)
|
|
||||||
return subtle.ConstantTimeCompare(sig[:32], checkR[:]) == 1
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -21,7 +21,7 @@ for more information.
|
||||||
|
|
||||||
Notary aims to make the internet more secure by making it easy for people to
|
Notary aims to make the internet more secure by making it easy for people to
|
||||||
publish and verify content. We often rely on TLS to secure our communications
|
publish and verify content. We often rely on TLS to secure our communications
|
||||||
with a web server which is inherently flawed, as any compromise of the server
|
with a web server, which is inherently flawed, as any compromise of the server
|
||||||
enables malicious content to be substituted for the legitimate content.
|
enables malicious content to be substituted for the legitimate content.
|
||||||
|
|
||||||
With Notary, publishers can sign their content offline using keys kept highly
|
With Notary, publishers can sign their content offline using keys kept highly
|
||||||
|
@ -46,11 +46,16 @@ Notary is based on [The Update Framework](https://www.theupdateframework.com/),
|
||||||
|
|
||||||
## Security
|
## Security
|
||||||
|
|
||||||
|
Any security vulnerabilities can be reported to security@docker.com.
|
||||||
|
|
||||||
See Notary's [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.
|
See Notary's [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.
|
||||||
|
|
||||||
Notary's last security audit was on July 31, 2015 by NCC ([results](docs/resources/ncc_docker_notary_audit_2015_07_31.pdf)).
|
### Security Audits
|
||||||
|
|
||||||
Any security vulnerabilities can be reported to security@docker.com.
|
Notary has had two public security audits:
|
||||||
|
|
||||||
|
* [August 7, 2018 by Cure53](docs/resources/cure53_tuf_notary_audit_2018_08_07.pdf) covering TUF and Notary
|
||||||
|
* [July 31, 2015 by NCC](docs/resources/ncc_docker_notary_audit_2015_07_31.pdf) covering Notary
|
||||||
|
|
||||||
# Getting started with the Notary CLI
|
# Getting started with the Notary CLI
|
||||||
|
|
||||||
|
@ -65,7 +70,7 @@ For more advanced usage, see the
|
||||||
|
|
||||||
To use the CLI against a local Notary server rather than against Docker Hub:
|
To use the CLI against a local Notary server rather than against Docker Hub:
|
||||||
|
|
||||||
1. Ensure that you have [docker and docker-compose](http://docs.docker.com/compose/install/) installed.
|
1. Ensure that you have [docker and docker-compose](https://docs.docker.com/compose/install/) installed.
|
||||||
1. `git clone https://github.com/theupdateframework/notary.git` and from the cloned repository path,
|
1. `git clone https://github.com/theupdateframework/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
|
start up a local Notary server and signer and copy the config file and testing certs to your
|
||||||
local Notary config directory:
|
local Notary config directory:
|
||||||
|
@ -88,6 +93,20 @@ 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
|
You can also leave off the `-d ~/.docker/trust` argument if you do not care
|
||||||
to use `notary` with Docker images.
|
to use `notary` with Docker images.
|
||||||
|
|
||||||
|
## Upgrading dependencies
|
||||||
|
|
||||||
|
To prevent mistakes in vendoring the go modules a buildscript has been added to properly vendor the modules using the correct version of Go to mitigate differences in CI and development environment.
|
||||||
|
|
||||||
|
Following procedure should be executed to upgrade a dependency. Preferably keep dependency upgrades in a separate commit from your code changes.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go get -u github.com/spf13/viper
|
||||||
|
buildscripts/circle-validate-vendor.sh
|
||||||
|
git add .
|
||||||
|
git commit -m "Upgraded github.com/spf13/viper"
|
||||||
|
```
|
||||||
|
|
||||||
|
The `buildscripts/circle-validate-vendor.sh` runs `go mod tidy` and `go mod vendor` using the given version of Go to prevent differences if you are for example running on a different version of Go.
|
||||||
|
|
||||||
## Building Notary
|
## Building Notary
|
||||||
|
|
||||||
|
@ -97,25 +116,20 @@ branch and contains features for the next release.
|
||||||
|
|
||||||
Prerequisites:
|
Prerequisites:
|
||||||
|
|
||||||
- Go >= 1.7.1
|
* Go >= 1.12
|
||||||
- Fedora: `dnf install golang`
|
|
||||||
- libtool development headers installed
|
|
||||||
- Ubuntu: `apt-get install libltdl-dev`
|
|
||||||
- CentOS/RedHat: `yum install libtool-ltdl-devel`
|
|
||||||
- Fedora: `dnf install libtool-ltdl-devel`
|
|
||||||
- Mac OS ([Homebrew](http://brew.sh/)): `brew install libtool`
|
|
||||||
|
|
||||||
Set [```GOPATH```](https://golang.org/doc/code.html#GOPATH). Then, run:
|
Set [```GOPATH```](https://golang.org/doc/code.html#GOPATH). Then, run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
$ export GO111MODULE=on
|
||||||
$ go get github.com/theupdateframework/notary
|
$ go get github.com/theupdateframework/notary
|
||||||
# build with pcks11 support by default to support yubikey
|
# build with pkcs11 support by default to support yubikey
|
||||||
$ go install -tags pkcs11 github.com/theupdateframework/notary/cmd/notary
|
$ go install -tags pkcs11 github.com/theupdateframework/notary/cmd/notary
|
||||||
$ notary
|
$ notary
|
||||||
```
|
```
|
||||||
|
|
||||||
To build the server and signer, run `docker-compose build`.
|
To build the server and signer, run `docker-compose build`.
|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Ftheupdateframework%2Fnotary.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Ftheupdateframework%2Fnotary?ref=badge_large)
|
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Ftheupdateframework%2Fnotary.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Ftheupdateframework%2Fnotary?ref=badge_large)
|
||||||
|
|
12
vendor/github.com/theupdateframework/notary/client/changelist/file_changelist.go
generated
vendored
12
vendor/github.com/theupdateframework/notary/client/changelist/file_changelist.go
generated
vendored
|
@ -35,7 +35,10 @@ func getFileNames(dirName string) ([]os.FileInfo, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fileInfos, err
|
return fileInfos, err
|
||||||
}
|
}
|
||||||
defer dir.Close()
|
defer func() {
|
||||||
|
_ = dir.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
dirListing, err = dir.Readdir(0)
|
dirListing, err = dir.Readdir(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fileInfos, err
|
return fileInfos, err
|
||||||
|
@ -89,7 +92,7 @@ func (cl FileChangelist) Add(c Change) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
filename := fmt.Sprintf("%020d_%s.change", time.Now().UnixNano(), uuid.Generate())
|
filename := fmt.Sprintf("%020d_%s.change", time.Now().UnixNano(), uuid.Generate())
|
||||||
return ioutil.WriteFile(filepath.Join(cl.dir, filename), cJSON, 0644)
|
return ioutil.WriteFile(filepath.Join(cl.dir, filename), cJSON, 0600)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove deletes the changes found at the given indices
|
// Remove deletes the changes found at the given indices
|
||||||
|
@ -120,7 +123,10 @@ func (cl FileChangelist) Clear(archive string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer dir.Close()
|
defer func() {
|
||||||
|
_ = dir.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
files, err := dir.Readdir(0)
|
files, err := dir.Readdir(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -20,7 +20,7 @@ type Changelist interface {
|
||||||
// Remove deletes the changes corresponding with the indices given
|
// Remove deletes the changes corresponding with the indices given
|
||||||
Remove(idxs []int) error
|
Remove(idxs []int) error
|
||||||
|
|
||||||
// Close syncronizes any pending writes to the underlying
|
// Close synchronizes any pending writes to the underlying
|
||||||
// storage and closes the file/connection
|
// storage and closes the file/connection
|
||||||
Close() error
|
Close() error
|
||||||
|
|
||||||
|
|
|
@ -7,10 +7,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
canonicaljson "github.com/docker/go/canonical/json"
|
canonicaljson "github.com/docker/go/canonical/json"
|
||||||
|
@ -39,7 +37,6 @@ func init() {
|
||||||
|
|
||||||
// repository stores all the information needed to operate on a notary repository.
|
// repository stores all the information needed to operate on a notary repository.
|
||||||
type repository struct {
|
type repository struct {
|
||||||
baseDir string
|
|
||||||
gun data.GUN
|
gun data.GUN
|
||||||
baseURL string
|
baseURL string
|
||||||
changelist changelist.Changelist
|
changelist changelist.Changelist
|
||||||
|
@ -56,7 +53,8 @@ type repository struct {
|
||||||
// NewFileCachedRepository is a wrapper for NewRepository that initializes
|
// NewFileCachedRepository is a wrapper for NewRepository that initializes
|
||||||
// a file cache from the provided repository, local config information and a crypto service.
|
// a file cache from the provided repository, local config information and a crypto service.
|
||||||
// It also retrieves the remote store associated to the base directory under where all the
|
// It also retrieves the remote store associated to the base directory under where all the
|
||||||
// trust files will be stored and the specified GUN.
|
// trust files will be stored (This is normally defaults to "~/.notary" or "~/.docker/trust"
|
||||||
|
// when enabling Docker content trust) and the specified GUN.
|
||||||
//
|
//
|
||||||
// In case of a nil RoundTripper, a default offline store is used instead.
|
// In case of a nil RoundTripper, a default offline store is used instead.
|
||||||
func NewFileCachedRepository(baseDir string, gun data.GUN, baseURL string, rt http.RoundTripper,
|
func NewFileCachedRepository(baseDir string, gun data.GUN, baseURL string, rt http.RoundTripper,
|
||||||
|
@ -90,16 +88,13 @@ func NewFileCachedRepository(baseDir string, gun data.GUN, baseURL string, rt ht
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewRepository(baseDir, gun, baseURL, remoteStore, cache, trustPinning, cryptoService, cl)
|
return NewRepository(gun, baseURL, remoteStore, cache, trustPinning, cryptoService, cl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRepository is the base method that returns a new notary repository.
|
// NewRepository is the base method that returns a new notary repository.
|
||||||
// It takes the base directory under where all the trust files will be stored
|
|
||||||
// (This is normally defaults to "~/.notary" or "~/.docker/trust" when enabling
|
|
||||||
// docker content trust).
|
|
||||||
// It expects an initialized cache. In case of a nil remote store, a default
|
// It expects an initialized cache. In case of a nil remote store, a default
|
||||||
// offline store is used.
|
// offline store is used.
|
||||||
func NewRepository(baseDir string, gun data.GUN, baseURL string, remoteStore store.RemoteStore, cache store.MetadataStore,
|
func NewRepository(gun data.GUN, baseURL string, remoteStore store.RemoteStore, cache store.MetadataStore,
|
||||||
trustPinning trustpinning.TrustPinConfig, cryptoService signed.CryptoService, cl changelist.Changelist) (Repository, error) {
|
trustPinning trustpinning.TrustPinConfig, cryptoService signed.CryptoService, cl changelist.Changelist) (Repository, error) {
|
||||||
|
|
||||||
// Repo's remote store is either a valid remote store or an OfflineStore
|
// Repo's remote store is either a valid remote store or an OfflineStore
|
||||||
|
@ -114,7 +109,6 @@ func NewRepository(baseDir string, gun data.GUN, baseURL string, remoteStore sto
|
||||||
nRepo := &repository{
|
nRepo := &repository{
|
||||||
gun: gun,
|
gun: gun,
|
||||||
baseURL: baseURL,
|
baseURL: baseURL,
|
||||||
baseDir: baseDir,
|
|
||||||
changelist: cl,
|
changelist: cl,
|
||||||
cache: cache,
|
cache: cache,
|
||||||
remoteStore: remoteStore,
|
remoteStore: remoteStore,
|
||||||
|
@ -131,20 +125,62 @@ func (r *repository) GetGUN() data.GUN {
|
||||||
return r.gun
|
return r.gun
|
||||||
}
|
}
|
||||||
|
|
||||||
// Target represents a simplified version of the data TUF operates on, so external
|
func (r *repository) updateTUF(forWrite bool) error {
|
||||||
// applications don't have to depend on TUF data types.
|
repo, invalid, err := LoadTUFRepo(TUFLoadOptions{
|
||||||
type Target struct {
|
GUN: r.gun,
|
||||||
Name string // the name of the target
|
TrustPinning: r.trustPinning,
|
||||||
Hashes data.Hashes // the hash of the target
|
CryptoService: r.cryptoService,
|
||||||
Length int64 // the size in bytes of the target
|
Cache: r.cache,
|
||||||
Custom *canonicaljson.RawMessage // the custom data provided to describe the file at TARGETPATH
|
RemoteStore: r.remoteStore,
|
||||||
|
AlwaysCheckInitialized: forWrite,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.tufRepo = repo
|
||||||
|
r.invalid = invalid
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TargetWithRole represents a Target that exists in a particular role - this is
|
// ListTargets calls update first before listing targets
|
||||||
// produced by ListTargets and GetTargetByName
|
func (r *repository) ListTargets(roles ...data.RoleName) ([]*TargetWithRole, error) {
|
||||||
type TargetWithRole struct {
|
if err := r.updateTUF(false); err != nil {
|
||||||
Target
|
return nil, err
|
||||||
Role data.RoleName
|
}
|
||||||
|
return NewReadOnly(r.tufRepo).ListTargets(roles...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTargetByName calls update first before getting target by name
|
||||||
|
func (r *repository) GetTargetByName(name string, roles ...data.RoleName) (*TargetWithRole, error) {
|
||||||
|
if err := r.updateTUF(false); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewReadOnly(r.tufRepo).GetTargetByName(name, roles...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllTargetMetadataByName calls update first before getting targets by name
|
||||||
|
func (r *repository) GetAllTargetMetadataByName(name string) ([]TargetSignedStruct, error) {
|
||||||
|
if err := r.updateTUF(false); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewReadOnly(r.tufRepo).GetAllTargetMetadataByName(name)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRoles calls update first before getting roles
|
||||||
|
func (r *repository) ListRoles() ([]RoleWithSignatures, error) {
|
||||||
|
if err := r.updateTUF(false); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewReadOnly(r.tufRepo).ListRoles()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDelegationRoles calls update first before getting all delegation roles
|
||||||
|
func (r *repository) GetDelegationRoles() ([]data.Role, error) {
|
||||||
|
if err := r.updateTUF(false); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewReadOnly(r.tufRepo).GetDelegationRoles()
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTarget is a helper method that returns a Target
|
// NewTarget is a helper method that returns a Target
|
||||||
|
@ -493,167 +529,6 @@ func (r *repository) RemoveTarget(targetName string, roles ...data.RoleName) err
|
||||||
return addChange(r.changelist, template, roles...)
|
return addChange(r.changelist, template, roles...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListTargets lists all targets for the current repository. The list of
|
|
||||||
// roles should be passed in order from highest to lowest priority.
|
|
||||||
//
|
|
||||||
// IMPORTANT: if you pass a set of roles such as [ "targets/a", "targets/x"
|
|
||||||
// "targets/a/b" ], even though "targets/a/b" is part of the "targets/a" subtree
|
|
||||||
// its entries will be strictly shadowed by those in other parts of the "targets/a"
|
|
||||||
// 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 *repository) ListTargets(roles ...data.RoleName) ([]*TargetWithRole, error) {
|
|
||||||
if err := r.Update(false); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(roles) == 0 {
|
|
||||||
roles = []data.RoleName{data.CanonicalTargetsRole}
|
|
||||||
}
|
|
||||||
targets := make(map[string]*TargetWithRole)
|
|
||||||
for _, role := range roles {
|
|
||||||
// Define an array of roles to skip for this walk (see IMPORTANT comment above)
|
|
||||||
skipRoles := utils.RoleNameSliceRemove(roles, role)
|
|
||||||
|
|
||||||
// Define a visitor function to populate the targets map in priority order
|
|
||||||
listVisitorFunc := func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} {
|
|
||||||
// We found targets so we should try to add them to our targets map
|
|
||||||
for targetName, targetMeta := range tgt.Signed.Targets {
|
|
||||||
// Follow the priority by not overriding previously set targets
|
|
||||||
// and check that this path is valid with this role
|
|
||||||
if _, ok := targets[targetName]; ok || !validRole.CheckPaths(targetName) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
targets[targetName] = &TargetWithRole{
|
|
||||||
Target: Target{
|
|
||||||
Name: targetName,
|
|
||||||
Hashes: targetMeta.Hashes,
|
|
||||||
Length: targetMeta.Length,
|
|
||||||
Custom: targetMeta.Custom,
|
|
||||||
},
|
|
||||||
Role: validRole.Name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
r.tufRepo.WalkTargets("", role, listVisitorFunc, skipRoles...)
|
|
||||||
}
|
|
||||||
|
|
||||||
var targetList []*TargetWithRole
|
|
||||||
for _, v := range targets {
|
|
||||||
targetList = append(targetList, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return targetList, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTargetByName returns a target by the given name. If no roles are passed
|
|
||||||
// it uses the targets role and does a search of the entire delegation
|
|
||||||
// graph, finding the first entry in a breadth first search of the delegations.
|
|
||||||
// If roles are passed, they should be passed in descending priority and
|
|
||||||
// the target entry found in the subtree of the highest priority role
|
|
||||||
// will be returned.
|
|
||||||
// See the IMPORTANT section on ListTargets above. Those roles also apply here.
|
|
||||||
func (r *repository) GetTargetByName(name string, roles ...data.RoleName) (*TargetWithRole, error) {
|
|
||||||
if err := r.Update(false); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(roles) == 0 {
|
|
||||||
roles = append(roles, data.CanonicalTargetsRole)
|
|
||||||
}
|
|
||||||
var resultMeta data.FileMeta
|
|
||||||
var resultRoleName data.RoleName
|
|
||||||
var foundTarget bool
|
|
||||||
for _, role := range roles {
|
|
||||||
// Define an array of roles to skip for this walk (see IMPORTANT comment above)
|
|
||||||
skipRoles := utils.RoleNameSliceRemove(roles, role)
|
|
||||||
|
|
||||||
// Define a visitor function to find the specified target
|
|
||||||
getTargetVisitorFunc := func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} {
|
|
||||||
if tgt == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// We found the target and validated path compatibility in our walk,
|
|
||||||
// so we should stop our walk and set the resultMeta and resultRoleName variables
|
|
||||||
if resultMeta, foundTarget = tgt.Signed.Targets[name]; foundTarget {
|
|
||||||
resultRoleName = validRole.Name
|
|
||||||
return tuf.StopWalk{}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Check that we didn't error, and that we assigned to our target
|
|
||||||
if err := r.tufRepo.WalkTargets(name, role, getTargetVisitorFunc, skipRoles...); err == nil && foundTarget {
|
|
||||||
return &TargetWithRole{Target: Target{Name: name, Hashes: resultMeta.Hashes, Length: resultMeta.Length, Custom: resultMeta.Custom}, Role: resultRoleName}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, ErrNoSuchTarget(name)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// TargetSignedStruct is a struct that contains a Target, the role it was found in, and the list of signatures for that role
|
|
||||||
type TargetSignedStruct struct {
|
|
||||||
Role data.DelegationRole
|
|
||||||
Target Target
|
|
||||||
Signatures []data.Signature
|
|
||||||
}
|
|
||||||
|
|
||||||
//ErrNoSuchTarget is returned when no valid trust data is found.
|
|
||||||
type ErrNoSuchTarget string
|
|
||||||
|
|
||||||
func (f ErrNoSuchTarget) Error() string {
|
|
||||||
return fmt.Sprintf("No valid trust data for %s", string(f))
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAllTargetMetadataByName searches the entire delegation role tree to find the specified target by name for all
|
|
||||||
// roles, and returns a list of TargetSignedStructs for each time it finds the specified target.
|
|
||||||
// If given an empty string for a target name, it will return back all targets signed into the repository in every role
|
|
||||||
func (r *repository) GetAllTargetMetadataByName(name string) ([]TargetSignedStruct, error) {
|
|
||||||
if err := r.Update(false); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var targetInfoList []TargetSignedStruct
|
|
||||||
|
|
||||||
// Define a visitor function to find the specified target
|
|
||||||
getAllTargetInfoByNameVisitorFunc := func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} {
|
|
||||||
if tgt == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// We found a target and validated path compatibility in our walk,
|
|
||||||
// so add it to our list if we have a match
|
|
||||||
// if we have an empty name, add all targets, else check if we have it
|
|
||||||
var targetMetaToAdd data.Files
|
|
||||||
if name == "" {
|
|
||||||
targetMetaToAdd = tgt.Signed.Targets
|
|
||||||
} else {
|
|
||||||
if meta, ok := tgt.Signed.Targets[name]; ok {
|
|
||||||
targetMetaToAdd = data.Files{name: meta}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for targetName, resultMeta := range targetMetaToAdd {
|
|
||||||
targetInfo := TargetSignedStruct{
|
|
||||||
Role: validRole,
|
|
||||||
Target: Target{Name: targetName, Hashes: resultMeta.Hashes, Length: resultMeta.Length, Custom: resultMeta.Custom},
|
|
||||||
Signatures: tgt.Signatures,
|
|
||||||
}
|
|
||||||
targetInfoList = append(targetInfoList, targetInfo)
|
|
||||||
}
|
|
||||||
// continue walking to all child roles
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that we didn't error, and that we found the target at least once
|
|
||||||
if err := r.tufRepo.WalkTargets(name, "", getAllTargetInfoByNameVisitorFunc); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(targetInfoList) == 0 {
|
|
||||||
return nil, ErrNoSuchTarget(name)
|
|
||||||
}
|
|
||||||
return targetInfoList, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetChangelist returns the list of the repository's unpublished changes
|
// GetChangelist returns the list of the repository's unpublished changes
|
||||||
func (r *repository) GetChangelist() (changelist.Changelist, error) {
|
func (r *repository) GetChangelist() (changelist.Changelist, error) {
|
||||||
return r.changelist, nil
|
return r.changelist, nil
|
||||||
|
@ -671,51 +546,6 @@ func (r *repository) getRemoteStore() store.RemoteStore {
|
||||||
return r.remoteStore
|
return r.remoteStore
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoleWithSignatures is a Role with its associated signatures
|
|
||||||
type RoleWithSignatures struct {
|
|
||||||
Signatures []data.Signature
|
|
||||||
data.Role
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListRoles returns a list of RoleWithSignatures objects for this repo
|
|
||||||
// This represents the latest metadata for each role in this repo
|
|
||||||
func (r *repository) ListRoles() ([]RoleWithSignatures, error) {
|
|
||||||
// Update to latest repo state
|
|
||||||
if err := r.Update(false); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all role info from our updated keysDB, can be empty
|
|
||||||
roles := r.tufRepo.GetAllLoadedRoles()
|
|
||||||
|
|
||||||
var roleWithSigs []RoleWithSignatures
|
|
||||||
|
|
||||||
// Populate RoleWithSignatures with Role from keysDB and signatures from TUF metadata
|
|
||||||
for _, role := range roles {
|
|
||||||
roleWithSig := RoleWithSignatures{Role: *role, Signatures: nil}
|
|
||||||
switch role.Name {
|
|
||||||
case data.CanonicalRootRole:
|
|
||||||
roleWithSig.Signatures = r.tufRepo.Root.Signatures
|
|
||||||
case data.CanonicalTargetsRole:
|
|
||||||
roleWithSig.Signatures = r.tufRepo.Targets[data.CanonicalTargetsRole].Signatures
|
|
||||||
case data.CanonicalSnapshotRole:
|
|
||||||
roleWithSig.Signatures = r.tufRepo.Snapshot.Signatures
|
|
||||||
case data.CanonicalTimestampRole:
|
|
||||||
roleWithSig.Signatures = r.tufRepo.Timestamp.Signatures
|
|
||||||
default:
|
|
||||||
if !data.IsDelegation(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
|
|
||||||
roleWithSig.Signatures = r.tufRepo.Targets[role.Name].Signatures
|
|
||||||
}
|
|
||||||
}
|
|
||||||
roleWithSigs = append(roleWithSigs, roleWithSig)
|
|
||||||
}
|
|
||||||
return roleWithSigs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Publish pushes the local changes in signed material to the remote notary-server
|
// Publish pushes the local changes in signed material to the remote notary-server
|
||||||
// Conceptually it performs an operation similar to a `git rebase`
|
// Conceptually it performs an operation similar to a `git rebase`
|
||||||
func (r *repository) Publish() error {
|
func (r *repository) Publish() error {
|
||||||
|
@ -736,7 +566,7 @@ func (r *repository) Publish() error {
|
||||||
func (r *repository) publish(cl changelist.Changelist) error {
|
func (r *repository) publish(cl changelist.Changelist) error {
|
||||||
var initialPublish bool
|
var initialPublish bool
|
||||||
// update first before publishing
|
// update first before publishing
|
||||||
if err := r.Update(true); err != nil {
|
if err := r.updateTUF(true); err != nil {
|
||||||
// If the remote is not aware of the repo, then this is being published
|
// If the remote is not aware of the repo, then this is being published
|
||||||
// for the first time. Try to initialize the repository before publishing.
|
// for the first time. Try to initialize the repository before publishing.
|
||||||
if _, ok := err.(ErrRepositoryNotExist); ok {
|
if _, ok := err.(ErrRepositoryNotExist); ok {
|
||||||
|
@ -863,7 +693,14 @@ func (r *repository) oldKeysForLegacyClientSupport(legacyVersions int, initialPu
|
||||||
}
|
}
|
||||||
oldKeys := make(map[string]data.PublicKey)
|
oldKeys := make(map[string]data.PublicKey)
|
||||||
|
|
||||||
c, err := r.bootstrapClient(true)
|
c, err := bootstrapClient(TUFLoadOptions{
|
||||||
|
GUN: r.gun,
|
||||||
|
TrustPinning: r.trustPinning,
|
||||||
|
CryptoService: r.cryptoService,
|
||||||
|
Cache: r.cache,
|
||||||
|
RemoteStore: r.remoteStore,
|
||||||
|
AlwaysCheckInitialized: true,
|
||||||
|
})
|
||||||
// require a server connection to fetch old roots
|
// require a server connection to fetch old roots
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -1003,135 +840,6 @@ func (r *repository) saveMetadata(ignoreSnapshot bool) error {
|
||||||
return r.cache.Set(data.CanonicalSnapshotRole.String(), snapshotJSON)
|
return r.cache.Set(data.CanonicalSnapshotRole.String(), snapshotJSON)
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns a properly constructed ErrRepositoryNotExist error based on this
|
|
||||||
// repo's information
|
|
||||||
func (r *repository) errRepositoryNotExist() error {
|
|
||||||
host := r.baseURL
|
|
||||||
parsed, err := url.Parse(r.baseURL)
|
|
||||||
if err == nil {
|
|
||||||
host = parsed.Host // try to exclude the scheme and any paths
|
|
||||||
}
|
|
||||||
return ErrRepositoryNotExist{remote: host, gun: r.gun}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update bootstraps a trust anchor (root.json) before updating all the
|
|
||||||
// metadata from the repo.
|
|
||||||
func (r *repository) Update(forWrite bool) error {
|
|
||||||
c, err := r.bootstrapClient(forWrite)
|
|
||||||
if err != nil {
|
|
||||||
if _, ok := err.(store.ErrMetaNotFound); ok {
|
|
||||||
return r.errRepositoryNotExist()
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
repo, invalid, err := c.Update()
|
|
||||||
if err != nil {
|
|
||||||
// notFound.Resource may include a version or checksum so when the role is root,
|
|
||||||
// it will be root, <version>.root or root.<checksum>.
|
|
||||||
notFound, ok := err.(store.ErrMetaNotFound)
|
|
||||||
isRoot, _ := regexp.MatchString(`\.?`+data.CanonicalRootRole.String()+`\.?`, notFound.Resource)
|
|
||||||
if ok && isRoot {
|
|
||||||
return r.errRepositoryNotExist()
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// we can be assured if we are at this stage that the repo we built is good
|
|
||||||
// no need to test the following function call for an error as it will always be fine should the repo be good- it is!
|
|
||||||
r.tufRepo = repo
|
|
||||||
r.invalid = invalid
|
|
||||||
warnRolesNearExpiry(repo)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// bootstrapClient attempts to bootstrap a root.json to be used as the trust
|
|
||||||
// anchor for a repository. The checkInitialized argument indicates whether
|
|
||||||
// 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.
|
|
||||||
//
|
|
||||||
// Populates a tuf.RepoBuilder with this root metadata. If the root metadata
|
|
||||||
// downloaded is a newer version than what is on disk, then intermediate
|
|
||||||
// versions will be downloaded and verified in order to rotate trusted keys
|
|
||||||
// properly. Newer root metadata must always be signed with the previous
|
|
||||||
// threshold and keys.
|
|
||||||
//
|
|
||||||
// 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 for the remote server, which may not be actually
|
|
||||||
// operational (if the URL is invalid but a root.json is cached).
|
|
||||||
func (r *repository) bootstrapClient(checkInitialized bool) (*tufClient, error) {
|
|
||||||
minVersion := 1
|
|
||||||
// the old root on disk should not be validated against any trust pinning configuration
|
|
||||||
// because if we have an old root, it itself is the thing that pins trust
|
|
||||||
oldBuilder := tuf.NewRepoBuilder(r.gun, r.GetCryptoService(), trustpinning.TrustPinConfig{})
|
|
||||||
|
|
||||||
// by default, we want to use the trust pinning configuration on any new root that we download
|
|
||||||
newBuilder := tuf.NewRepoBuilder(r.gun, r.GetCryptoService(), r.trustPinning)
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
// If we have an old root, and it's valid, then we overwrite the newBuilder to be one
|
|
||||||
// preloaded with the old root or one which uses the old root for trust bootstrapping.
|
|
||||||
if rootJSON, err := r.cache.GetSized(data.CanonicalRootRole.String(), store.NoSizeLimit); err == nil {
|
|
||||||
// if we can't load the cached root, fail hard because that is how we pin trust
|
|
||||||
if err := oldBuilder.Load(data.CanonicalRootRole, rootJSON, minVersion, true); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// again, the root on disk is the source of trust pinning, so use an empty trust
|
|
||||||
// pinning configuration
|
|
||||||
newBuilder = tuf.NewRepoBuilder(r.gun, r.GetCryptoService(), trustpinning.TrustPinConfig{})
|
|
||||||
|
|
||||||
if err := newBuilder.Load(data.CanonicalRootRole, rootJSON, minVersion, false); err != nil {
|
|
||||||
// Ok, the old root is expired - we want to download a new one. But we want to use the
|
|
||||||
// old root to verify the new root, so bootstrap a new builder with the old builder
|
|
||||||
// but use the trustpinning to validate the new root
|
|
||||||
minVersion = oldBuilder.GetLoadedVersion(data.CanonicalRootRole)
|
|
||||||
newBuilder = oldBuilder.BootstrapNewBuilderWithNewTrustpin(r.trustPinning)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
remote := r.getRemoteStore()
|
|
||||||
|
|
||||||
if !newBuilder.IsLoaded(data.CanonicalRootRole) || checkInitialized {
|
|
||||||
// remoteErr was nil and we were not able to load a root from cache or
|
|
||||||
// are specifically checking for initialization of the repo.
|
|
||||||
|
|
||||||
// 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.GetSized(data.CanonicalRootRole.String(), store.NoSizeLimit)
|
|
||||||
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.
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !newBuilder.IsLoaded(data.CanonicalRootRole) {
|
|
||||||
// we always want to use the downloaded root if we couldn't load from cache
|
|
||||||
if err := newBuilder.Load(data.CanonicalRootRole, tmpJSON, minVersion, false); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = r.cache.Set(data.CanonicalRootRole.String(), 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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We can only get here if remoteErr != nil (hence we don't download any new root),
|
|
||||||
// and there was no root on disk
|
|
||||||
if !newBuilder.IsLoaded(data.CanonicalRootRole) {
|
|
||||||
return nil, ErrRepoNotInitialized{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return newTufClient(oldBuilder, newBuilder, remote, r.cache), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RotateKey removes all existing keys associated with the role. If no keys are
|
// RotateKey removes all existing keys associated with the role. If no keys are
|
||||||
// specified in keyList, then this creates and adds one new key or delegates
|
// specified in keyList, then this creates and adds one new key or delegates
|
||||||
// managing the key to the server. If key(s) are specified by keyList, then they are
|
// managing the key to the server. If key(s) are specified by keyList, then they are
|
||||||
|
@ -1273,7 +981,7 @@ func DeleteTrustData(baseDir string, gun data.GUN, URL string, rt http.RoundTrip
|
||||||
if deleteRemote {
|
if deleteRemote {
|
||||||
remote, err := getRemoteStore(URL, gun, rt)
|
remote, err := getRemoteStore(URL, gun, rt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Error("unable to instantiate a remote store: %v", err)
|
logrus.Errorf("unable to instantiate a remote store: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := remote.RemoveAll(); err != nil {
|
if err := remote.RemoveAll(); err != nil {
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/theupdateframework/notary"
|
"github.com/theupdateframework/notary"
|
||||||
"github.com/theupdateframework/notary/client/changelist"
|
"github.com/theupdateframework/notary/client/changelist"
|
||||||
store "github.com/theupdateframework/notary/storage"
|
|
||||||
"github.com/theupdateframework/notary/tuf/data"
|
"github.com/theupdateframework/notary/tuf/data"
|
||||||
"github.com/theupdateframework/notary/tuf/utils"
|
"github.com/theupdateframework/notary/tuf/utils"
|
||||||
)
|
)
|
||||||
|
@ -77,7 +76,7 @@ func (r *repository) AddDelegationPaths(name data.RoleName, paths []string) erro
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveDelegationKeysAndPaths creates changelist entries to remove provided delegation key IDs and paths.
|
// RemoveDelegationKeysAndPaths creates changelist entries to remove provided delegation key IDs and paths.
|
||||||
// This method composes RemoveDelegationPaths and RemoveDelegationKeys (each creates one changelist if called).
|
// This method composes RemoveDelegationPaths and RemoveDelegationKeys (each creates one changelist entry if called).
|
||||||
func (r *repository) RemoveDelegationKeysAndPaths(name data.RoleName, keyIDs, paths []string) error {
|
func (r *repository) RemoveDelegationKeysAndPaths(name data.RoleName, keyIDs, paths []string) error {
|
||||||
if len(paths) > 0 {
|
if len(paths) > 0 {
|
||||||
err := r.RemoveDelegationPaths(name, paths)
|
err := r.RemoveDelegationPaths(name, paths)
|
||||||
|
@ -201,41 +200,6 @@ func newDeleteDelegationChange(name data.RoleName, content []byte) *changelist.T
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDelegationRoles returns the keys and roles of the repository's delegations
|
|
||||||
// Also converts key IDs to canonical key IDs to keep consistent with signing prompts
|
|
||||||
func (r *repository) GetDelegationRoles() ([]data.Role, error) {
|
|
||||||
// Update state of the repo to latest
|
|
||||||
if err := r.Update(false); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// All top level delegations (ex: targets/level1) are stored exclusively in targets.json
|
|
||||||
_, ok := r.tufRepo.Targets[data.CanonicalTargetsRole]
|
|
||||||
if !ok {
|
|
||||||
return nil, store.ErrMetaNotFound{Resource: data.CanonicalTargetsRole.String()}
|
|
||||||
}
|
|
||||||
|
|
||||||
// make a copy for traversing nested delegations
|
|
||||||
allDelegations := []data.Role{}
|
|
||||||
|
|
||||||
// Define a visitor function to populate the delegations list and translate their key IDs to canonical IDs
|
|
||||||
delegationCanonicalListVisitor := func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} {
|
|
||||||
// For the return list, update with a copy that includes canonicalKeyIDs
|
|
||||||
// These aren't validated by the validRole
|
|
||||||
canonicalDelegations, err := translateDelegationsToCanonicalIDs(tgt.Signed.Delegations)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
allDelegations = append(allDelegations, canonicalDelegations...)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
err := r.tufRepo.WalkTargets("", "", delegationCanonicalListVisitor)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return allDelegations, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func translateDelegationsToCanonicalIDs(delegationInfo data.Delegations) ([]data.Role, error) {
|
func translateDelegationsToCanonicalIDs(delegationInfo data.Delegations) ([]data.Role, error) {
|
||||||
canonicalDelegations := make([]data.Role, len(delegationInfo.Roles))
|
canonicalDelegations := make([]data.Role, len(delegationInfo.Roles))
|
||||||
// Do a copy by value to ensure local delegation metadata is untouched
|
// Do a copy by value to ensure local delegation metadata is untouched
|
||||||
|
|
|
@ -6,42 +6,145 @@ import (
|
||||||
"github.com/theupdateframework/notary/tuf/signed"
|
"github.com/theupdateframework/notary/tuf/signed"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Repository represents the set of options that must be supported over a TUF repo.
|
// ReadOnly represents the set of options that must be supported over a TUF repo for
|
||||||
type Repository interface {
|
// reading
|
||||||
// General management operations
|
type ReadOnly interface {
|
||||||
Initialize(rootKeyIDs []string, serverManagedRoles ...data.RoleName) error
|
// ListTargets lists all targets for the current repository. The list of
|
||||||
InitializeWithCertificate(rootKeyIDs []string, rootCerts []data.PublicKey, serverManagedRoles ...data.RoleName) error
|
// roles should be passed in order from highest to lowest priority.
|
||||||
Publish() error
|
//
|
||||||
|
// IMPORTANT: if you pass a set of roles such as [ "targets/a", "targets/x"
|
||||||
// Target Operations
|
// "targets/a/b" ], even though "targets/a/b" is part of the "targets/a" subtree
|
||||||
AddTarget(target *Target, roles ...data.RoleName) error
|
// its entries will be strictly shadowed by those in other parts of the "targets/a"
|
||||||
RemoveTarget(targetName string, roles ...data.RoleName) error
|
// 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.
|
||||||
ListTargets(roles ...data.RoleName) ([]*TargetWithRole, error)
|
ListTargets(roles ...data.RoleName) ([]*TargetWithRole, error)
|
||||||
|
|
||||||
|
// GetTargetByName returns a target by the given name. If no roles are passed
|
||||||
|
// it uses the targets role and does a search of the entire delegation
|
||||||
|
// graph, finding the first entry in a breadth first search of the delegations.
|
||||||
|
// If roles are passed, they should be passed in descending priority and
|
||||||
|
// the target entry found in the subtree of the highest priority role
|
||||||
|
// will be returned.
|
||||||
|
// See the IMPORTANT section on ListTargets above. Those roles also apply here.
|
||||||
GetTargetByName(name string, roles ...data.RoleName) (*TargetWithRole, error)
|
GetTargetByName(name string, roles ...data.RoleName) (*TargetWithRole, error)
|
||||||
|
|
||||||
|
// GetAllTargetMetadataByName searches the entire delegation role tree to find
|
||||||
|
// the specified target by name for all roles, and returns a list of
|
||||||
|
// TargetSignedStructs for each time it finds the specified target.
|
||||||
|
// If given an empty string for a target name, it will return back all targets
|
||||||
|
// signed into the repository in every role
|
||||||
GetAllTargetMetadataByName(name string) ([]TargetSignedStruct, error)
|
GetAllTargetMetadataByName(name string) ([]TargetSignedStruct, error)
|
||||||
|
|
||||||
// Changelist operations
|
// ListRoles returns a list of RoleWithSignatures objects for this repo
|
||||||
|
// This represents the latest metadata for each role in this repo
|
||||||
|
ListRoles() ([]RoleWithSignatures, error)
|
||||||
|
|
||||||
|
// GetDelegationRoles returns the keys and roles of the repository's delegations
|
||||||
|
// Also converts key IDs to canonical key IDs to keep consistent with signing prompts
|
||||||
|
GetDelegationRoles() ([]data.Role, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Repository represents the set of options that must be supported over a TUF repo
|
||||||
|
// for both reading and writing.
|
||||||
|
type Repository interface {
|
||||||
|
ReadOnly
|
||||||
|
|
||||||
|
// ------------------- Publishing operations -------------------
|
||||||
|
|
||||||
|
// GetGUN returns the GUN associated with the repository
|
||||||
|
GetGUN() data.GUN
|
||||||
|
|
||||||
|
// SetLegacyVersion sets the number of versions back to fetch roots to sign with
|
||||||
|
SetLegacyVersions(int)
|
||||||
|
|
||||||
|
// ----- General management operations -----
|
||||||
|
|
||||||
|
// Initialize creates a new repository by using rootKey as the root Key for the
|
||||||
|
// TUF repository. The remote store/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 cache, not published to
|
||||||
|
// the remote store. To do that, use r.Publish() eventually.
|
||||||
|
Initialize(rootKeyIDs []string, serverManagedRoles ...data.RoleName) error
|
||||||
|
|
||||||
|
// InitializeWithCertificate initializes the repository with root keys and their
|
||||||
|
// corresponding certificates
|
||||||
|
InitializeWithCertificate(rootKeyIDs []string, rootCerts []data.PublicKey, serverManagedRoles ...data.RoleName) error
|
||||||
|
|
||||||
|
// Publish pushes the local changes in signed material to the remote notary-server
|
||||||
|
// Conceptually it performs an operation similar to a `git rebase`
|
||||||
|
Publish() error
|
||||||
|
|
||||||
|
// ----- Target Operations -----
|
||||||
|
|
||||||
|
// 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"
|
||||||
|
AddTarget(target *Target, roles ...data.RoleName) error
|
||||||
|
|
||||||
|
// RemoveTarget creates new changelist entries to remove a target from the given
|
||||||
|
// roles in the repository when the changelist gets applied at publish time.
|
||||||
|
// If roles are unspecified, the default role is "target".
|
||||||
|
RemoveTarget(targetName string, roles ...data.RoleName) error
|
||||||
|
|
||||||
|
// ----- Changelist operations -----
|
||||||
|
|
||||||
|
// GetChangelist returns the list of the repository's unpublished changes
|
||||||
GetChangelist() (changelist.Changelist, error)
|
GetChangelist() (changelist.Changelist, error)
|
||||||
|
|
||||||
// Role operations
|
// ----- Role operations -----
|
||||||
ListRoles() ([]RoleWithSignatures, error)
|
|
||||||
GetDelegationRoles() ([]data.Role, error)
|
// AddDelegation creates changelist entries to add provided delegation public keys and paths.
|
||||||
|
// This method composes AddDelegationRoleAndKeys and AddDelegationPaths (each creates one changelist if called).
|
||||||
AddDelegation(name data.RoleName, delegationKeys []data.PublicKey, paths []string) error
|
AddDelegation(name data.RoleName, delegationKeys []data.PublicKey, paths []string) error
|
||||||
|
|
||||||
|
// AddDelegationRoleAndKeys creates a changelist entry to add provided delegation public keys.
|
||||||
|
// This method is the simplest way to create a new delegation, because the delegation must have at least
|
||||||
|
// one key upon creation to be valid since we will reject the changelist while validating the threshold.
|
||||||
AddDelegationRoleAndKeys(name data.RoleName, delegationKeys []data.PublicKey) error
|
AddDelegationRoleAndKeys(name data.RoleName, delegationKeys []data.PublicKey) error
|
||||||
|
|
||||||
|
// AddDelegationPaths creates a changelist entry to add provided paths to an existing delegation.
|
||||||
|
// This method cannot create a new delegation itself because the role must meet the key threshold upon
|
||||||
|
// creation.
|
||||||
AddDelegationPaths(name data.RoleName, paths []string) error
|
AddDelegationPaths(name data.RoleName, paths []string) error
|
||||||
|
|
||||||
|
// RemoveDelegationKeysAndPaths creates changelist entries to remove provided delegation key IDs and
|
||||||
|
// paths. This method composes RemoveDelegationPaths and RemoveDelegationKeys (each creates one
|
||||||
|
// changelist entry if called).
|
||||||
RemoveDelegationKeysAndPaths(name data.RoleName, keyIDs, paths []string) error
|
RemoveDelegationKeysAndPaths(name data.RoleName, keyIDs, paths []string) error
|
||||||
|
|
||||||
|
// RemoveDelegationRole creates a changelist to remove all paths and keys from a role, and delete the
|
||||||
|
// role in its entirety.
|
||||||
RemoveDelegationRole(name data.RoleName) error
|
RemoveDelegationRole(name data.RoleName) error
|
||||||
|
|
||||||
|
// RemoveDelegationPaths creates a changelist entry to remove provided paths from an existing delegation.
|
||||||
RemoveDelegationPaths(name data.RoleName, paths []string) error
|
RemoveDelegationPaths(name data.RoleName, paths []string) error
|
||||||
|
|
||||||
|
// RemoveDelegationKeys creates a changelist entry to remove provided keys from an existing delegation.
|
||||||
|
// When this changelist is applied, if the specified keys are the only keys left in the role,
|
||||||
|
// the role itself will be deleted in its entirety.
|
||||||
|
// It can also delete a key from all delegations under a parent using a name
|
||||||
|
// with a wildcard at the end.
|
||||||
RemoveDelegationKeys(name data.RoleName, keyIDs []string) error
|
RemoveDelegationKeys(name data.RoleName, keyIDs []string) error
|
||||||
|
|
||||||
|
// ClearDelegationPaths creates a changelist entry to remove all paths from an existing delegation.
|
||||||
ClearDelegationPaths(name data.RoleName) error
|
ClearDelegationPaths(name data.RoleName) error
|
||||||
|
|
||||||
// Witness and other re-signing operations
|
// ----- Witness and other re-signing operations -----
|
||||||
|
|
||||||
|
// Witness creates change objects to witness (i.e. re-sign) the given
|
||||||
|
// roles on the next publish. One change is created per role
|
||||||
Witness(roles ...data.RoleName) ([]data.RoleName, error)
|
Witness(roles ...data.RoleName) ([]data.RoleName, error)
|
||||||
|
|
||||||
// Key Operations
|
// ----- Key Operations -----
|
||||||
|
|
||||||
|
// RotateKey removes all existing keys associated with the role. If no keys are
|
||||||
|
// specified in keyList, then this creates and adds one new key or delegates
|
||||||
|
// managing the key to the server. If key(s) are specified by keyList, then they are
|
||||||
|
// used for signing the role.
|
||||||
|
// These changes are staged in a changelist until publish is called.
|
||||||
RotateKey(role data.RoleName, serverManagesKey bool, keyList []string) error
|
RotateKey(role data.RoleName, serverManagesKey bool, keyList []string) error
|
||||||
|
|
||||||
|
// GetCryptoService is the getter for the repository's CryptoService, which is used
|
||||||
|
// to sign all updates.
|
||||||
GetCryptoService() signed.CryptoService
|
GetCryptoService() signed.CryptoService
|
||||||
SetLegacyVersions(int)
|
|
||||||
GetGUN() data.GUN
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,257 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
canonicaljson "github.com/docker/go/canonical/json"
|
||||||
|
store "github.com/theupdateframework/notary/storage"
|
||||||
|
"github.com/theupdateframework/notary/tuf"
|
||||||
|
"github.com/theupdateframework/notary/tuf/data"
|
||||||
|
"github.com/theupdateframework/notary/tuf/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Target represents a simplified version of the data TUF operates on, so external
|
||||||
|
// applications don't have to depend on TUF data types.
|
||||||
|
type Target struct {
|
||||||
|
Name string // the name of the target
|
||||||
|
Hashes data.Hashes // the hash of the target
|
||||||
|
Length int64 // the size in bytes of the target
|
||||||
|
Custom *canonicaljson.RawMessage // the custom data provided to describe the file at TARGETPATH
|
||||||
|
}
|
||||||
|
|
||||||
|
// TargetWithRole represents a Target that exists in a particular role - this is
|
||||||
|
// produced by ListTargets and GetTargetByName
|
||||||
|
type TargetWithRole struct {
|
||||||
|
Target
|
||||||
|
Role data.RoleName
|
||||||
|
}
|
||||||
|
|
||||||
|
// TargetSignedStruct is a struct that contains a Target, the role it was found in, and the list of signatures for that role
|
||||||
|
type TargetSignedStruct struct {
|
||||||
|
Role data.DelegationRole
|
||||||
|
Target Target
|
||||||
|
Signatures []data.Signature
|
||||||
|
}
|
||||||
|
|
||||||
|
//ErrNoSuchTarget is returned when no valid trust data is found.
|
||||||
|
type ErrNoSuchTarget string
|
||||||
|
|
||||||
|
func (f ErrNoSuchTarget) Error() string {
|
||||||
|
return fmt.Sprintf("No valid trust data for %s", string(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoleWithSignatures is a Role with its associated signatures
|
||||||
|
type RoleWithSignatures struct {
|
||||||
|
Signatures []data.Signature
|
||||||
|
data.Role
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReadOnly is the base method that returns a new notary repository for reading.
|
||||||
|
// It expects an initialized cache. In case of a nil remote store, a default
|
||||||
|
// offline store is used.
|
||||||
|
func NewReadOnly(repo *tuf.Repo) ReadOnly {
|
||||||
|
return &reader{tufRepo: repo}
|
||||||
|
}
|
||||||
|
|
||||||
|
type reader struct {
|
||||||
|
tufRepo *tuf.Repo
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListTargets lists all targets for the current repository. The list of
|
||||||
|
// roles should be passed in order from highest to lowest priority.
|
||||||
|
//
|
||||||
|
// IMPORTANT: if you pass a set of roles such as [ "targets/a", "targets/x"
|
||||||
|
// "targets/a/b" ], even though "targets/a/b" is part of the "targets/a" subtree
|
||||||
|
// its entries will be strictly shadowed by those in other parts of the "targets/a"
|
||||||
|
// 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 *reader) ListTargets(roles ...data.RoleName) ([]*TargetWithRole, error) {
|
||||||
|
if len(roles) == 0 {
|
||||||
|
roles = []data.RoleName{data.CanonicalTargetsRole}
|
||||||
|
}
|
||||||
|
targets := make(map[string]*TargetWithRole)
|
||||||
|
for _, role := range roles {
|
||||||
|
// Define an array of roles to skip for this walk (see IMPORTANT comment above)
|
||||||
|
skipRoles := utils.RoleNameSliceRemove(roles, role)
|
||||||
|
|
||||||
|
// Define a visitor function to populate the targets map in priority order
|
||||||
|
listVisitorFunc := func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} {
|
||||||
|
// We found targets so we should try to add them to our targets map
|
||||||
|
for targetName, targetMeta := range tgt.Signed.Targets {
|
||||||
|
// Follow the priority by not overriding previously set targets
|
||||||
|
// and check that this path is valid with this role
|
||||||
|
if _, ok := targets[targetName]; ok || !validRole.CheckPaths(targetName) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
targets[targetName] = &TargetWithRole{
|
||||||
|
Target: Target{
|
||||||
|
Name: targetName,
|
||||||
|
Hashes: targetMeta.Hashes,
|
||||||
|
Length: targetMeta.Length,
|
||||||
|
Custom: targetMeta.Custom,
|
||||||
|
},
|
||||||
|
Role: validRole.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
r.tufRepo.WalkTargets("", role, listVisitorFunc, skipRoles...)
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetList []*TargetWithRole
|
||||||
|
for _, v := range targets {
|
||||||
|
targetList = append(targetList, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return targetList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTargetByName returns a target by the given name. If no roles are passed
|
||||||
|
// it uses the targets role and does a search of the entire delegation
|
||||||
|
// graph, finding the first entry in a breadth first search of the delegations.
|
||||||
|
// If roles are passed, they should be passed in descending priority and
|
||||||
|
// the target entry found in the subtree of the highest priority role
|
||||||
|
// will be returned.
|
||||||
|
// See the IMPORTANT section on ListTargets above. Those roles also apply here.
|
||||||
|
func (r *reader) GetTargetByName(name string, roles ...data.RoleName) (*TargetWithRole, error) {
|
||||||
|
if len(roles) == 0 {
|
||||||
|
roles = append(roles, data.CanonicalTargetsRole)
|
||||||
|
}
|
||||||
|
var resultMeta data.FileMeta
|
||||||
|
var resultRoleName data.RoleName
|
||||||
|
var foundTarget bool
|
||||||
|
for _, role := range roles {
|
||||||
|
// Define an array of roles to skip for this walk (see IMPORTANT comment above)
|
||||||
|
skipRoles := utils.RoleNameSliceRemove(roles, role)
|
||||||
|
|
||||||
|
// Define a visitor function to find the specified target
|
||||||
|
getTargetVisitorFunc := func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} {
|
||||||
|
if tgt == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// We found the target and validated path compatibility in our walk,
|
||||||
|
// so we should stop our walk and set the resultMeta and resultRoleName variables
|
||||||
|
if resultMeta, foundTarget = tgt.Signed.Targets[name]; foundTarget {
|
||||||
|
resultRoleName = validRole.Name
|
||||||
|
return tuf.StopWalk{}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Check that we didn't error, and that we assigned to our target
|
||||||
|
if err := r.tufRepo.WalkTargets(name, role, getTargetVisitorFunc, skipRoles...); err == nil && foundTarget {
|
||||||
|
return &TargetWithRole{Target: Target{Name: name, Hashes: resultMeta.Hashes, Length: resultMeta.Length, Custom: resultMeta.Custom}, Role: resultRoleName}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, ErrNoSuchTarget(name)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllTargetMetadataByName searches the entire delegation role tree to find the specified target by name for all
|
||||||
|
// roles, and returns a list of TargetSignedStructs for each time it finds the specified target.
|
||||||
|
// If given an empty string for a target name, it will return back all targets signed into the repository in every role
|
||||||
|
func (r *reader) GetAllTargetMetadataByName(name string) ([]TargetSignedStruct, error) {
|
||||||
|
var targetInfoList []TargetSignedStruct
|
||||||
|
|
||||||
|
// Define a visitor function to find the specified target
|
||||||
|
getAllTargetInfoByNameVisitorFunc := func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} {
|
||||||
|
if tgt == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// We found a target and validated path compatibility in our walk,
|
||||||
|
// so add it to our list if we have a match
|
||||||
|
// if we have an empty name, add all targets, else check if we have it
|
||||||
|
var targetMetaToAdd data.Files
|
||||||
|
if name == "" {
|
||||||
|
targetMetaToAdd = tgt.Signed.Targets
|
||||||
|
} else {
|
||||||
|
if meta, ok := tgt.Signed.Targets[name]; ok {
|
||||||
|
targetMetaToAdd = data.Files{name: meta}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for targetName, resultMeta := range targetMetaToAdd {
|
||||||
|
targetInfo := TargetSignedStruct{
|
||||||
|
Role: validRole,
|
||||||
|
Target: Target{Name: targetName, Hashes: resultMeta.Hashes, Length: resultMeta.Length, Custom: resultMeta.Custom},
|
||||||
|
Signatures: tgt.Signatures,
|
||||||
|
}
|
||||||
|
targetInfoList = append(targetInfoList, targetInfo)
|
||||||
|
}
|
||||||
|
// continue walking to all child roles
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that we didn't error, and that we found the target at least once
|
||||||
|
if err := r.tufRepo.WalkTargets(name, "", getAllTargetInfoByNameVisitorFunc); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(targetInfoList) == 0 {
|
||||||
|
return nil, ErrNoSuchTarget(name)
|
||||||
|
}
|
||||||
|
return targetInfoList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRoles returns a list of RoleWithSignatures objects for this repo
|
||||||
|
// This represents the latest metadata for each role in this repo
|
||||||
|
func (r *reader) ListRoles() ([]RoleWithSignatures, error) {
|
||||||
|
// Get all role info from our updated keysDB, can be empty
|
||||||
|
roles := r.tufRepo.GetAllLoadedRoles()
|
||||||
|
|
||||||
|
var roleWithSigs []RoleWithSignatures
|
||||||
|
|
||||||
|
// Populate RoleWithSignatures with Role from keysDB and signatures from TUF metadata
|
||||||
|
for _, role := range roles {
|
||||||
|
roleWithSig := RoleWithSignatures{Role: *role, Signatures: nil}
|
||||||
|
switch role.Name {
|
||||||
|
case data.CanonicalRootRole:
|
||||||
|
roleWithSig.Signatures = r.tufRepo.Root.Signatures
|
||||||
|
case data.CanonicalTargetsRole:
|
||||||
|
roleWithSig.Signatures = r.tufRepo.Targets[data.CanonicalTargetsRole].Signatures
|
||||||
|
case data.CanonicalSnapshotRole:
|
||||||
|
roleWithSig.Signatures = r.tufRepo.Snapshot.Signatures
|
||||||
|
case data.CanonicalTimestampRole:
|
||||||
|
roleWithSig.Signatures = r.tufRepo.Timestamp.Signatures
|
||||||
|
default:
|
||||||
|
if !data.IsDelegation(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
|
||||||
|
roleWithSig.Signatures = r.tufRepo.Targets[role.Name].Signatures
|
||||||
|
}
|
||||||
|
}
|
||||||
|
roleWithSigs = append(roleWithSigs, roleWithSig)
|
||||||
|
}
|
||||||
|
return roleWithSigs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDelegationRoles returns the keys and roles of the repository's delegations
|
||||||
|
// Also converts key IDs to canonical key IDs to keep consistent with signing prompts
|
||||||
|
func (r *reader) GetDelegationRoles() ([]data.Role, error) {
|
||||||
|
// All top level delegations (ex: targets/level1) are stored exclusively in targets.json
|
||||||
|
_, ok := r.tufRepo.Targets[data.CanonicalTargetsRole]
|
||||||
|
if !ok {
|
||||||
|
return nil, store.ErrMetaNotFound{Resource: data.CanonicalTargetsRole.String()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// make a copy for traversing nested delegations
|
||||||
|
allDelegations := []data.Role{}
|
||||||
|
|
||||||
|
// Define a visitor function to populate the delegations list and translate their key IDs to canonical IDs
|
||||||
|
delegationCanonicalListVisitor := func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} {
|
||||||
|
// For the return list, update with a copy that includes canonicalKeyIDs
|
||||||
|
// These aren't validated by the validRole
|
||||||
|
canonicalDelegations, err := translateDelegationsToCanonicalIDs(tgt.Signed.Delegations)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
allDelegations = append(allDelegations, canonicalDelegations...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err := r.tufRepo.WalkTargets("", "", delegationCanonicalListVisitor)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return allDelegations, nil
|
||||||
|
}
|
|
@ -3,9 +3,11 @@ package client
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/theupdateframework/notary"
|
"github.com/theupdateframework/notary"
|
||||||
|
"github.com/theupdateframework/notary/cryptoservice"
|
||||||
store "github.com/theupdateframework/notary/storage"
|
store "github.com/theupdateframework/notary/storage"
|
||||||
"github.com/theupdateframework/notary/trustpinning"
|
"github.com/theupdateframework/notary/trustpinning"
|
||||||
"github.com/theupdateframework/notary/tuf"
|
"github.com/theupdateframework/notary/tuf"
|
||||||
|
@ -21,16 +23,6 @@ type tufClient struct {
|
||||||
newBuilder tuf.RepoBuilder
|
newBuilder tuf.RepoBuilder
|
||||||
}
|
}
|
||||||
|
|
||||||
// newTufClient initialized a tufClient with the given repo, remote source of content, and cache
|
|
||||||
func newTufClient(oldBuilder, newBuilder tuf.RepoBuilder, remote store.RemoteStore, cache store.MetadataStore) *tufClient {
|
|
||||||
return &tufClient{
|
|
||||||
oldBuilder: oldBuilder,
|
|
||||||
newBuilder: newBuilder,
|
|
||||||
remote: remote,
|
|
||||||
cache: cache,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update performs an update to the TUF repo as defined by the TUF spec
|
// Update performs an update to the TUF repo as defined by the TUF spec
|
||||||
func (c *tufClient) Update() (*tuf.Repo, *tuf.Repo, error) {
|
func (c *tufClient) Update() (*tuf.Repo, *tuf.Repo, error) {
|
||||||
// 1. Get timestamp
|
// 1. Get timestamp
|
||||||
|
@ -139,7 +131,7 @@ func (c *tufClient) updateRoot() error {
|
||||||
|
|
||||||
// Write newest to cache
|
// Write newest to cache
|
||||||
if err := c.cache.Set(data.CanonicalRootRole.String(), raw); err != nil {
|
if err := c.cache.Set(data.CanonicalRootRole.String(), raw); err != nil {
|
||||||
logrus.Debugf("unable to write %s to cache: %d.%s", newestVersion, data.CanonicalRootRole, err)
|
logrus.Debugf("unable to write %d.%s to cache: %s", newestVersion, data.CanonicalRootRole, err)
|
||||||
}
|
}
|
||||||
logrus.Debugf("finished updating root files")
|
logrus.Debugf("finished updating root files")
|
||||||
return nil
|
return nil
|
||||||
|
@ -323,3 +315,149 @@ func (c *tufClient) tryLoadRemote(consistentInfo tuf.ConsistentInfo, old []byte)
|
||||||
}
|
}
|
||||||
return raw, nil
|
return raw, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TUFLoadOptions are provided to LoadTUFRepo, which loads a TUF repo from cache,
|
||||||
|
// from a remote store, or both
|
||||||
|
type TUFLoadOptions struct {
|
||||||
|
GUN data.GUN
|
||||||
|
TrustPinning trustpinning.TrustPinConfig
|
||||||
|
CryptoService signed.CryptoService
|
||||||
|
Cache store.MetadataStore
|
||||||
|
RemoteStore store.RemoteStore
|
||||||
|
AlwaysCheckInitialized bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// bootstrapClient attempts to bootstrap a root.json to be used as the trust
|
||||||
|
// anchor for a repository. The checkInitialized argument indicates whether
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
// Populates a tuf.RepoBuilder with this root metadata. If the root metadata
|
||||||
|
// downloaded is a newer version than what is on disk, then intermediate
|
||||||
|
// versions will be downloaded and verified in order to rotate trusted keys
|
||||||
|
// properly. Newer root metadata must always be signed with the previous
|
||||||
|
// threshold and keys.
|
||||||
|
//
|
||||||
|
// Fails if the remote server is reachable and does not know the repo
|
||||||
|
// (i.e. before any metadata has been published), in which case the error is
|
||||||
|
// store.ErrMetaNotFound, or if the root metadata (from whichever source is used)
|
||||||
|
// is not trusted.
|
||||||
|
//
|
||||||
|
// Returns a TUFClient for the remote server, which may not be actually
|
||||||
|
// operational (if the URL is invalid but a root.json is cached).
|
||||||
|
func bootstrapClient(l TUFLoadOptions) (*tufClient, error) {
|
||||||
|
minVersion := 1
|
||||||
|
// the old root on disk should not be validated against any trust pinning configuration
|
||||||
|
// because if we have an old root, it itself is the thing that pins trust
|
||||||
|
oldBuilder := tuf.NewRepoBuilder(l.GUN, l.CryptoService, trustpinning.TrustPinConfig{})
|
||||||
|
|
||||||
|
// by default, we want to use the trust pinning configuration on any new root that we download
|
||||||
|
newBuilder := tuf.NewRepoBuilder(l.GUN, l.CryptoService, l.TrustPinning)
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
// If we have an old root, and it's valid, then we overwrite the newBuilder to be one
|
||||||
|
// preloaded with the old root or one which uses the old root for trust bootstrapping.
|
||||||
|
if rootJSON, err := l.Cache.GetSized(data.CanonicalRootRole.String(), store.NoSizeLimit); err == nil {
|
||||||
|
// if we can't load the cached root, fail hard because that is how we pin trust
|
||||||
|
if err := oldBuilder.Load(data.CanonicalRootRole, rootJSON, minVersion, true); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// again, the root on disk is the source of trust pinning, so use an empty trust
|
||||||
|
// pinning configuration
|
||||||
|
newBuilder = tuf.NewRepoBuilder(l.GUN, l.CryptoService, trustpinning.TrustPinConfig{})
|
||||||
|
|
||||||
|
if err := newBuilder.Load(data.CanonicalRootRole, rootJSON, minVersion, false); err != nil {
|
||||||
|
// Ok, the old root is expired - we want to download a new one. But we want to use the
|
||||||
|
// old root to verify the new root, so bootstrap a new builder with the old builder
|
||||||
|
// but use the trustpinning to validate the new root
|
||||||
|
minVersion = oldBuilder.GetLoadedVersion(data.CanonicalRootRole)
|
||||||
|
newBuilder = oldBuilder.BootstrapNewBuilderWithNewTrustpin(l.TrustPinning)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !newBuilder.IsLoaded(data.CanonicalRootRole) || l.AlwaysCheckInitialized {
|
||||||
|
// remoteErr was nil and we were not able to load a root from cache or
|
||||||
|
// are specifically checking for initialization of the repo.
|
||||||
|
|
||||||
|
// 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 := l.RemoteStore.GetSized(data.CanonicalRootRole.String(), store.NoSizeLimit)
|
||||||
|
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.
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !newBuilder.IsLoaded(data.CanonicalRootRole) {
|
||||||
|
// we always want to use the downloaded root if we couldn't load from cache
|
||||||
|
if err := newBuilder.Load(data.CanonicalRootRole, tmpJSON, minVersion, false); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = l.Cache.Set(data.CanonicalRootRole.String(), 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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can only get here if remoteErr != nil (hence we don't download any new root),
|
||||||
|
// and there was no root on disk
|
||||||
|
if !newBuilder.IsLoaded(data.CanonicalRootRole) {
|
||||||
|
return nil, ErrRepoNotInitialized{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tufClient{
|
||||||
|
oldBuilder: oldBuilder,
|
||||||
|
newBuilder: newBuilder,
|
||||||
|
remote: l.RemoteStore,
|
||||||
|
cache: l.Cache,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadTUFRepo bootstraps a trust anchor (root.json) from cache (if provided) before updating
|
||||||
|
// all the metadata for the repo from the remote (if provided). It loads a TUF repo from cache,
|
||||||
|
// from a remote store, or both.
|
||||||
|
func LoadTUFRepo(options TUFLoadOptions) (*tuf.Repo, *tuf.Repo, error) {
|
||||||
|
// set some sane defaults, so nothing has to be provided necessarily
|
||||||
|
if options.RemoteStore == nil {
|
||||||
|
options.RemoteStore = store.OfflineStore{}
|
||||||
|
}
|
||||||
|
if options.Cache == nil {
|
||||||
|
options.Cache = store.NewMemoryStore(nil)
|
||||||
|
}
|
||||||
|
if options.CryptoService == nil {
|
||||||
|
options.CryptoService = cryptoservice.EmptyService
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := bootstrapClient(options)
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := err.(store.ErrMetaNotFound); ok {
|
||||||
|
return nil, nil, ErrRepositoryNotExist{
|
||||||
|
remote: options.RemoteStore.Location(),
|
||||||
|
gun: options.GUN,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
repo, invalid, err := c.Update()
|
||||||
|
if err != nil {
|
||||||
|
// notFound.Resource may include a version or checksum so when the role is root,
|
||||||
|
// it will be root, <version>.root or root.<checksum>.
|
||||||
|
notFound, ok := err.(store.ErrMetaNotFound)
|
||||||
|
isRoot, _ := regexp.MatchString(`\.?`+data.CanonicalRootRole.String()+`\.?`, notFound.Resource)
|
||||||
|
if ok && isRoot {
|
||||||
|
return nil, nil, ErrRepositoryNotExist{
|
||||||
|
remote: options.RemoteStore.Location(),
|
||||||
|
gun: options.GUN,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
warnRolesNearExpiry(repo)
|
||||||
|
return repo, invalid, nil
|
||||||
|
}
|
||||||
|
|
|
@ -21,6 +21,9 @@ var (
|
||||||
// ErrRootKeyNotEncrypted is returned if a root key being imported is
|
// ErrRootKeyNotEncrypted is returned if a root key being imported is
|
||||||
// unencrypted
|
// unencrypted
|
||||||
ErrRootKeyNotEncrypted = errors.New("only encrypted root keys may be imported")
|
ErrRootKeyNotEncrypted = errors.New("only encrypted root keys may be imported")
|
||||||
|
|
||||||
|
// EmptyService is an empty crypto service
|
||||||
|
EmptyService = NewCryptoService()
|
||||||
)
|
)
|
||||||
|
|
||||||
// CryptoService implements Sign and Create, holding a specific GUN and keystore to
|
// CryptoService implements Sign and Create, holding a specific GUN and keystore to
|
||||||
|
|
|
@ -3,7 +3,7 @@ package notary
|
||||||
import (
|
import (
|
||||||
"crypto"
|
"crypto"
|
||||||
// Need to import md5 so can test availability.
|
// Need to import md5 so can test availability.
|
||||||
_ "crypto/md5"
|
_ "crypto/md5" // #nosec
|
||||||
)
|
)
|
||||||
|
|
||||||
// FIPSEnabled returns true if running in FIPS mode.
|
// FIPSEnabled returns true if running in FIPS mode.
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
module github.com/theupdateframework/notary
|
||||||
|
|
||||||
|
go 1.12
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/BurntSushi/toml v0.3.1 // indirect
|
||||||
|
github.com/Shopify/logrus-bugsnag v0.0.0-20170309145241-6dbc35f2c30d
|
||||||
|
github.com/beorn7/perks v0.0.0-20150223135152-b965b613227f // indirect
|
||||||
|
github.com/bitly/go-simplejson v0.5.0 // indirect
|
||||||
|
github.com/bugsnag/bugsnag-go v1.0.5-0.20150529004307-13fd6b8acda0
|
||||||
|
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b // indirect
|
||||||
|
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 // indirect
|
||||||
|
github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004
|
||||||
|
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73 // indirect
|
||||||
|
github.com/docker/distribution v2.7.1+incompatible
|
||||||
|
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c
|
||||||
|
github.com/docker/go-connections v0.4.0
|
||||||
|
github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916 // indirect
|
||||||
|
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
|
||||||
|
github.com/dvsekhvalnov/jose2go v0.0.0-20170216131308-f21a8cedbbae
|
||||||
|
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 // indirect
|
||||||
|
github.com/go-sql-driver/mysql v1.3.0
|
||||||
|
github.com/gogo/protobuf v1.0.0 // indirect
|
||||||
|
github.com/golang/protobuf v1.3.4
|
||||||
|
github.com/google/certificate-transparency-go v1.0.10-0.20180222191210-5ab67e519c93 // indirect
|
||||||
|
github.com/gorilla/mux v1.7.0
|
||||||
|
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||||
|
github.com/jinzhu/gorm v0.0.0-20170222002820-5409931a1bb8
|
||||||
|
github.com/jinzhu/inflection v0.0.0-20170102125226-1c35d901db3d // indirect
|
||||||
|
github.com/jinzhu/now v1.1.1 // indirect
|
||||||
|
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 // indirect
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
||||||
|
github.com/lib/pq v0.0.0-20150723085316-0dad96c0b94f
|
||||||
|
github.com/magiconair/properties v1.5.3 // indirect
|
||||||
|
github.com/mattn/go-sqlite3 v1.6.0
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||||
|
github.com/miekg/pkcs11 v1.0.2
|
||||||
|
github.com/mitchellh/mapstructure v0.0.0-20150613213606-2caf8efc9366 // indirect
|
||||||
|
github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420 // indirect
|
||||||
|
github.com/opencontainers/image-spec v1.0.1 // indirect
|
||||||
|
github.com/pkg/errors v0.8.1 // indirect
|
||||||
|
github.com/prometheus/client_golang v0.9.0-pre1.0.20180209125602-c332b6f63c06
|
||||||
|
github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5 // indirect
|
||||||
|
github.com/prometheus/common v0.0.0-20180110214958-89604d197083 // indirect
|
||||||
|
github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7 // indirect
|
||||||
|
github.com/sirupsen/logrus v1.4.1
|
||||||
|
github.com/spf13/cast v0.0.0-20150508191742-4d07383ffe94 // indirect
|
||||||
|
github.com/spf13/cobra v0.0.1
|
||||||
|
github.com/spf13/jwalterweatherman v0.0.0-20141219030609-3d60171a6431 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.0 // indirect
|
||||||
|
github.com/spf13/viper v0.0.0-20150530192845-be5ff3e4840c
|
||||||
|
github.com/stretchr/testify v1.5.1
|
||||||
|
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3
|
||||||
|
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e
|
||||||
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221
|
||||||
|
google.golang.org/grpc v1.0.5
|
||||||
|
gopkg.in/rethinkdb/rethinkdb-go.v6 v6.2.1
|
||||||
|
)
|
|
@ -12,7 +12,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/theupdateframework/notary"
|
"github.com/theupdateframework/notary"
|
||||||
"golang.org/x/crypto/ssh/terminal"
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -49,7 +49,7 @@ var (
|
||||||
// Upon successful passphrase retrievals, the passphrase will be cached such that
|
// Upon successful passphrase retrievals, the passphrase will be cached such that
|
||||||
// subsequent prompts will produce the same passphrase.
|
// subsequent prompts will produce the same passphrase.
|
||||||
func PromptRetriever() notary.PassRetriever {
|
func PromptRetriever() notary.PassRetriever {
|
||||||
if !terminal.IsTerminal(int(os.Stdin.Fd())) {
|
if !term.IsTerminal(int(os.Stdin.Fd())) {
|
||||||
return func(string, string, bool, int) (string, bool, error) {
|
return func(string, string, bool, int) (string, bool, error) {
|
||||||
return "", false, ErrNoInput
|
return "", false, ErrNoInput
|
||||||
}
|
}
|
||||||
|
@ -200,8 +200,8 @@ func GetPassphrase(in *bufio.Reader) ([]byte, error) {
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
if terminal.IsTerminal(int(os.Stdin.Fd())) {
|
if term.IsTerminal(int(os.Stdin.Fd())) {
|
||||||
passphrase, err = terminal.ReadPassword(int(os.Stdin.Fd()))
|
passphrase, err = term.ReadPassword(int(os.Stdin.Fd()))
|
||||||
} else {
|
} else {
|
||||||
passphrase, err = in.ReadBytes('\n')
|
passphrase, err = in.ReadBytes('\n')
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,14 +137,16 @@ func (f *FilesystemStore) GetSized(name string, size int64) ([]byte, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
file, err := os.OpenFile(p, os.O_RDONLY, notary.PrivNoExecPerms)
|
file, err := os.Open(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
err = ErrMetaNotFound{Resource: name}
|
err = ErrMetaNotFound{Resource: name}
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer func() {
|
||||||
|
_ = file.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
if size == NoSizeLimit {
|
if size == NoSizeLimit {
|
||||||
size = notary.MaxDownloadSize
|
size = notary.MaxDownloadSize
|
||||||
|
|
|
@ -111,6 +111,18 @@ type HTTPStore struct {
|
||||||
roundTrip http.RoundTripper
|
roundTrip http.RoundTripper
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewNotaryServerStore returns a new HTTPStore against a URL which should represent a notary
|
||||||
|
// server
|
||||||
|
func NewNotaryServerStore(serverURL string, gun data.GUN, roundTrip http.RoundTripper) (RemoteStore, error) {
|
||||||
|
return NewHTTPStore(
|
||||||
|
serverURL+"/v2/"+gun.String()+"/_trust/tuf/",
|
||||||
|
"",
|
||||||
|
"json",
|
||||||
|
"key",
|
||||||
|
roundTrip,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// NewHTTPStore initializes a new store against a URL and a number of configuration options.
|
// NewHTTPStore initializes a new store against a URL and a number of configuration options.
|
||||||
//
|
//
|
||||||
// In case of a nil `roundTrip`, a default offline store is used instead.
|
// In case of a nil `roundTrip`, a default offline store is used instead.
|
||||||
|
@ -363,5 +375,5 @@ func (s HTTPStore) RotateKey(role data.RoleName) ([]byte, error) {
|
||||||
|
|
||||||
// Location returns a human readable name for the storage location
|
// Location returns a human readable name for the storage location
|
||||||
func (s HTTPStore) Location() string {
|
func (s HTTPStore) Location() string {
|
||||||
return s.baseURL.String()
|
return s.baseURL.Host
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ type MetadataStore interface {
|
||||||
SetMulti(map[string][]byte) error
|
SetMulti(map[string][]byte) error
|
||||||
RemoveAll() error
|
RemoveAll() error
|
||||||
Remove(name string) error
|
Remove(name string) error
|
||||||
|
Location() string
|
||||||
}
|
}
|
||||||
|
|
||||||
// PublicKeyStore must be implemented by a key service
|
// PublicKeyStore must be implemented by a key service
|
||||||
|
|
|
@ -359,7 +359,7 @@ func (rb *repoBuilder) GenerateSnapshot(prev *data.SignedSnapshot) ([]byte, int,
|
||||||
|
|
||||||
// loadedNotChecksummed should currently contain the root awaiting checksumming,
|
// loadedNotChecksummed should currently contain the root awaiting checksumming,
|
||||||
// since it has to have been loaded. Since the snapshot was generated using
|
// since it has to have been loaded. Since the snapshot was generated using
|
||||||
// the root and targets data (there may not be any) that that have been loaded,
|
// the root and targets data (there may not be any) that have been loaded,
|
||||||
// remove all of them from rb.loadedNotChecksummed
|
// remove all of them from rb.loadedNotChecksummed
|
||||||
for tgtName := range rb.repo.Targets {
|
for tgtName := range rb.repo.Targets {
|
||||||
delete(rb.loadedNotChecksummed, data.RoleName(tgtName))
|
delete(rb.loadedNotChecksummed, data.RoleName(tgtName))
|
||||||
|
|
|
@ -12,9 +12,9 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/agl/ed25519"
|
|
||||||
"github.com/docker/go/canonical/json"
|
"github.com/docker/go/canonical/json"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PublicKey is the necessary interface for public keys
|
// PublicKey is the necessary interface for public keys
|
||||||
|
@ -484,9 +484,10 @@ func (k RSAPrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts)
|
||||||
|
|
||||||
// Sign creates an ed25519 signature
|
// Sign creates an ed25519 signature
|
||||||
func (k ED25519PrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) (signature []byte, err error) {
|
func (k ED25519PrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) (signature []byte, err error) {
|
||||||
priv := [ed25519.PrivateKeySize]byte{}
|
priv := make([]byte, ed25519.PrivateKeySize)
|
||||||
copy(priv[:], k.private[ed25519.PublicKeySize:])
|
// The ed25519 key is serialized as public key then private key, so just use private key here.
|
||||||
return ed25519.Sign(&priv, msg)[:], nil
|
copy(priv, k.private[ed25519.PublicKeySize:])
|
||||||
|
return ed25519.Sign(ed25519.PrivateKey(priv), msg)[:], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign on an UnknownPrivateKey raises an error because the client does not
|
// Sign on an UnknownPrivateKey raises an error because the client does not
|
||||||
|
|
|
@ -55,7 +55,7 @@ func (e ErrInvalidRole) Error() string {
|
||||||
|
|
||||||
// ValidRole only determines the name is semantically
|
// ValidRole only determines the name is semantically
|
||||||
// correct. For target delegated roles, it does NOT check
|
// correct. For target delegated roles, it does NOT check
|
||||||
// the the appropriate parent roles exist.
|
// the appropriate parent roles exist.
|
||||||
func ValidRole(name RoleName) bool {
|
func ValidRole(name RoleName) bool {
|
||||||
if IsDelegation(name) {
|
if IsDelegation(name) {
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -59,7 +59,7 @@ func IsValidSnapshotStructure(s Snapshot) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSnapshot initilizes a SignedSnapshot with a given top level root
|
// NewSnapshot initializes a SignedSnapshot with a given top level root
|
||||||
// and targets objects
|
// and targets objects
|
||||||
func NewSnapshot(root *Signed, targets *Signed) (*SignedSnapshot, error) {
|
func NewSnapshot(root *Signed, targets *Signed) (*SignedSnapshot, error) {
|
||||||
logrus.Debug("generating new snapshot...")
|
logrus.Debug("generating new snapshot...")
|
||||||
|
|
|
@ -54,7 +54,7 @@ func isValidTargetsStructure(t Targets, roleName RoleName) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTargets intiializes a new empty SignedTargets object
|
// NewTargets initializes a new empty SignedTargets object
|
||||||
func NewTargets() *SignedTargets {
|
func NewTargets() *SignedTargets {
|
||||||
return &SignedTargets{
|
return &SignedTargets{
|
||||||
Signatures: make([]Signature, 0),
|
Signatures: make([]Signature, 0),
|
||||||
|
|
|
@ -186,7 +186,7 @@ type FileMeta struct {
|
||||||
|
|
||||||
// Equals returns true if the other FileMeta object is equivalent to this one
|
// Equals returns true if the other FileMeta object is equivalent to this one
|
||||||
func (f FileMeta) Equals(o FileMeta) bool {
|
func (f FileMeta) Equals(o FileMeta) bool {
|
||||||
if o.Length != f.Length || len(f.Hashes) != len(f.Hashes) {
|
if o.Length != f.Length || len(o.Hashes) != len(f.Hashes) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if f.Custom == nil && o.Custom != nil || f.Custom != nil && o.Custom == nil {
|
if f.Custom == nil && o.Custom != nil || f.Custom != nil && o.Custom == nil {
|
||||||
|
|
|
@ -39,7 +39,7 @@ type CryptoService interface {
|
||||||
KeyService
|
KeyService
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verifier defines an interface for verfying signatures. An implementer
|
// Verifier defines an interface for verifying signatures. An implementer
|
||||||
// of this interface should verify signatures for one and only one
|
// of this interface should verify signatures for one and only one
|
||||||
// signing scheme.
|
// signing scheme.
|
||||||
type Verifier interface {
|
type Verifier interface {
|
||||||
|
|
|
@ -87,7 +87,8 @@ func Sign(service CryptoService, s *data.Signed, signingKeys []data.PublicKey,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, sig := range s.Signatures {
|
for i := range s.Signatures {
|
||||||
|
sig := s.Signatures[i]
|
||||||
if _, ok := signingKeyIDs[sig.KeyID]; ok {
|
if _, ok := signingKeyIDs[sig.KeyID]; ok {
|
||||||
// key is in the set of key IDs for which a signature has been created
|
// key is in the set of key IDs for which a signature has been created
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -10,9 +10,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/agl/ed25519"
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/theupdateframework/notary/tuf/data"
|
"github.com/theupdateframework/notary/tuf/data"
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -39,26 +39,26 @@ func (v Ed25519Verifier) Verify(key data.PublicKey, sig []byte, msg []byte) erro
|
||||||
if key.Algorithm() != data.ED25519Key {
|
if key.Algorithm() != data.ED25519Key {
|
||||||
return ErrInvalidKeyType{}
|
return ErrInvalidKeyType{}
|
||||||
}
|
}
|
||||||
var sigBytes [ed25519.SignatureSize]byte
|
sigBytes := make([]byte, ed25519.SignatureSize)
|
||||||
if len(sig) != ed25519.SignatureSize {
|
if len(sig) != ed25519.SignatureSize {
|
||||||
logrus.Debugf("signature length is incorrect, must be %d, was %d.", ed25519.SignatureSize, len(sig))
|
logrus.Debugf("signature length is incorrect, must be %d, was %d.", ed25519.SignatureSize, len(sig))
|
||||||
return ErrInvalid
|
return ErrInvalid
|
||||||
}
|
}
|
||||||
copy(sigBytes[:], sig)
|
copy(sigBytes, sig)
|
||||||
|
|
||||||
var keyBytes [ed25519.PublicKeySize]byte
|
keyBytes := make([]byte, ed25519.PublicKeySize)
|
||||||
pub := key.Public()
|
pub := key.Public()
|
||||||
if len(pub) != ed25519.PublicKeySize {
|
if len(pub) != ed25519.PublicKeySize {
|
||||||
logrus.Errorf("public key is incorrect size, must be %d, was %d.", ed25519.PublicKeySize, len(pub))
|
logrus.Errorf("public key is incorrect size, must be %d, was %d.", ed25519.PublicKeySize, len(pub))
|
||||||
return ErrInvalidKeyLength{msg: fmt.Sprintf("ed25519 public key must be %d bytes.", ed25519.PublicKeySize)}
|
return ErrInvalidKeyLength{msg: fmt.Sprintf("ed25519 public key must be %d bytes.", ed25519.PublicKeySize)}
|
||||||
}
|
}
|
||||||
n := copy(keyBytes[:], key.Public())
|
n := copy(keyBytes, key.Public())
|
||||||
if n < ed25519.PublicKeySize {
|
if n < ed25519.PublicKeySize {
|
||||||
logrus.Errorf("failed to copy the key, must have %d bytes, copied %d bytes.", ed25519.PublicKeySize, n)
|
logrus.Errorf("failed to copy the key, must have %d bytes, copied %d bytes.", ed25519.PublicKeySize, n)
|
||||||
return ErrInvalid
|
return ErrInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ed25519.Verify(&keyBytes, msg, &sigBytes) {
|
if !ed25519.Verify(ed25519.PublicKey(keyBytes), msg, sigBytes) {
|
||||||
logrus.Debugf("failed ed25519 verification")
|
logrus.Debugf("failed ed25519 verification")
|
||||||
return ErrInvalid
|
return ErrInvalid
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ import (
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/sha1"
|
"crypto/sha1" // #nosec
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
"crypto/x509/pkix"
|
||||||
"encoding/asn1"
|
"encoding/asn1"
|
||||||
|
|
|
@ -30,7 +30,7 @@ func RoleNameSliceContains(ss []data.RoleName, s data.RoleName) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// RoleNameSliceRemove removes the the given RoleName from the slice, returning a new slice
|
// RoleNameSliceRemove removes the given RoleName from the slice, returning a new slice
|
||||||
func RoleNameSliceRemove(ss []data.RoleName, s data.RoleName) []data.RoleName {
|
func RoleNameSliceRemove(ss []data.RoleName, s data.RoleName) []data.RoleName {
|
||||||
res := []data.RoleName{}
|
res := []data.RoleName{}
|
||||||
for _, v := range ss {
|
for _, v := range ss {
|
||||||
|
|
|
@ -16,10 +16,10 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/agl/ed25519"
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/theupdateframework/notary"
|
"github.com/theupdateframework/notary"
|
||||||
"github.com/theupdateframework/notary/tuf/data"
|
"github.com/theupdateframework/notary/tuf/data"
|
||||||
|
"golang.org/x/crypto/ed25519"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CanonicalKeyID returns the ID of the public bytes version of a TUF key.
|
// CanonicalKeyID returns the ID of the public bytes version of a TUF key.
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
github.com/Shopify/logrus-bugsnag 6dbc35f2c30d1e37549f9673dd07912452ab28a5
|
|
||||||
github.com/sirupsen/logrus f006c2ac4710855cf0f916dd6b77acf6b048dc6e # v1.0.3
|
|
||||||
github.com/agl/ed25519 278e1ec8e8a6e017cd07577924d6766039146ced
|
|
||||||
github.com/bugsnag/bugsnag-go 13fd6b8acda029830ef9904df6b63be0a83369d0
|
|
||||||
github.com/bugsnag/panicwrap e2c28503fcd0675329da73bf48b33404db873782
|
|
||||||
github.com/bugsnag/osext 0dd3f918b21bec95ace9dc86c7e70266cfc5c702
|
|
||||||
github.com/docker/distribution edc3ab29cdff8694dd6feb85cfeb4b5f1b38ed9c
|
|
||||||
github.com/opencontainers/go-digest a6d0ee40d4207ea02364bd3b9e8e77b9159ba1eb
|
|
||||||
github.com/docker/go-connections 7395e3f8aa162843a74ed6d48e79627d9792ac55
|
|
||||||
github.com/docker/go d30aec9fd63c35133f8f79c3412ad91a3b08be06
|
|
||||||
github.com/dvsekhvalnov/jose2go f21a8cedbbae609f623613ec8f81125c243212e6 # v1.3
|
|
||||||
github.com/go-sql-driver/mysql a0583e0143b1624142adab07e0e97fe106d99561 # v1.3
|
|
||||||
github.com/gorilla/mux 53c1911da2b537f792e7cafcb446b05ffe33b996 # v1.6.1
|
|
||||||
github.com/jinzhu/gorm 5409931a1bb87e484d68d649af9367c207713ea2
|
|
||||||
github.com/jinzhu/inflection 1c35d901db3da928c72a72d8458480cc9ade058f
|
|
||||||
github.com/lib/pq 0dad96c0b94f8dee039aa40467f767467392a0af
|
|
||||||
github.com/mattn/go-sqlite3 6c771bb9887719704b210e87e934f08be014bdb1 # v1.6.0
|
|
||||||
github.com/miekg/pkcs11 5f6e0d0dad6f472df908c8e968a98ef00c9224bb
|
|
||||||
github.com/prometheus/client_golang 449ccefff16c8e2b7229f6be1921ba22f62461fe
|
|
||||||
github.com/prometheus/client_model fa8ad6fec33561be4280a8f0514318c79d7f6cb6 # model-0.0.2-12-gfa8ad6f
|
|
||||||
github.com/prometheus/procfs b1afdc266f54247f5dc725544f5d351a8661f502
|
|
||||||
github.com/prometheus/common 4fdc91a58c9d3696b982e8a680f4997403132d44
|
|
||||||
github.com/golang/protobuf c3cefd437628a0b7d31b34fe44b3a7a540e98527
|
|
||||||
github.com/spf13/cobra 7b2c5ac9fc04fc5efafb60700713d4fa609b777b # v0.0.1
|
|
||||||
github.com/spf13/viper be5ff3e4840cf692388bde7a057595a474ef379e
|
|
||||||
golang.org/x/crypto 76eec36fa14229c4b25bb894c2d0e591527af429
|
|
||||||
golang.org/x/net 6a513affb38dc9788b449d59ffed099b8de18fa0
|
|
||||||
golang.org/x/sys 314a259e304ff91bd6985da2a7149bbf91237993
|
|
||||||
google.golang.org/grpc 708a7f9f3283aa2d4f6132d287d78683babe55c8 # v1.0.5
|
|
||||||
github.com/pkg/errors 839d9e913e063e28dfd0e6c7b7512793e0a48be9
|
|
||||||
|
|
||||||
github.com/spf13/pflag e57e3eeb33f795204c1ca35f56c44f83227c6e66 # v1.0.0
|
|
||||||
github.com/spf13/cast 4d07383ffe94b5e5a6fa3af9211374a4507a0184
|
|
||||||
gopkg.in/yaml.v2 5420a8b6744d3b0345ab293f6fcba19c978f1183 # v2.2.1
|
|
||||||
gopkg.in/fatih/pool.v2 cba550ebf9bce999a02e963296d4bc7a486cb715
|
|
||||||
github.com/gorilla/context 14f550f51af52180c2eefed15e5fd18d63c0a64a # unused
|
|
||||||
github.com/spf13/jwalterweatherman 3d60171a64319ef63c78bd45bd60e6eab1e75f8b
|
|
||||||
github.com/mitchellh/mapstructure 2caf8efc93669b6c43e0441cdc6aed17546c96f3
|
|
||||||
github.com/magiconair/properties 624009598839a9432bd97bb75552389422357723 # v1.5.3
|
|
||||||
github.com/kr/text 6807e777504f54ad073ecef66747de158294b639
|
|
||||||
github.com/kr/pretty bc9499caa0f45ee5edb2f0209fbd61fbf3d9018f # go.weekly.2011-12-22-18-gbc9499c
|
|
||||||
github.com/hailocab/go-hostpool e80d13ce29ede4452c43dea11e79b9bc8a15b478
|
|
||||||
github.com/docker/libtrust aabc10ec26b754e797f9028f4589c5b7bd90dc20
|
|
||||||
github.com/beorn7/perks b965b613227fddccbfffe13eae360ed3fa822f8d
|
|
||||||
github.com/BurntSushi/toml a368813c5e648fee92e5f6c30e3944ff9d5e8895
|
|
||||||
|
|
||||||
github.com/matttproud/golang_protobuf_extensions d0c3fe89de86839aecf2e0579c40ba3bb336a453
|
|
||||||
github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
|
|
||||||
|
|
||||||
gopkg.in/dancannon/gorethink.v3 e324d6ad938205da6c1e8a0179dc97a5b1a92185 https://github.com/docker/gorethink # v3.0.0-logrus
|
|
||||||
# dependencies of gorethink.v3
|
|
||||||
gopkg.in/gorethink/gorethink.v2 ac5be4ae8538d44ae8843b97fc9f90860cb48a85 https://github.com/docker/gorethink # v2.2.2-logrus
|
|
||||||
github.com/cenk/backoff 32cd0c5b3aef12c76ed64aaf678f6c79736be7dc # v1.0.0
|
|
||||||
|
|
||||||
# Testing requirements
|
|
||||||
github.com/stretchr/testify 089c7181b8c728499929ff09b62d3fdd8df8adff
|
|
||||||
github.com/cloudflare/cfssl 4e2dcbde500472449917533851bf4bae9bdff562 # v1.3.1
|
|
||||||
github.com/google/certificate-transparency-go 5ab67e519c93568ac3ee50fd6772a5bcf8aa460d
|
|
||||||
github.com/gogo/protobuf 1adfc126b41513cc696b209667c8656ea7aac67c # v1.0.0
|
|
Loading…
Reference in New Issue